Пример #1
0
        public void Generate()
        {
            // TODO: Int32 is overloading
            SimplexSolver solver = new SimplexSolver();

            mealPortions = new List <int>();
            int Calories, cost, Proteins, Carbohydrates, Fats;

            solver.AddRow("calories", out Calories);
            solver.SetIntegrality(Calories, true);
            solver.AddRow("proteins", out Proteins);
            solver.SetIntegrality(Calories, true);
            solver.AddRow("carbohydrates", out Carbohydrates);
            solver.SetIntegrality(Calories, true);
            solver.AddRow("fats", out Fats);
            solver.SetIntegrality(Fats, true);
            solver.AddRow("cost", out cost);

            Random rand = new Random();

            foreach (Meal m in this.meals)
            {
                int portions = new int();
                solver.AddVariable(m.Name, out portions);
                solver.SetBounds(portions, 0, 5);
                solver.SetIntegrality(portions, true);
                solver.SetCoefficient(Calories, portions, (int)m.Calories_Per_Portions);
                int prots = ((int)(m.Proteins * m.Portion_Size / 100) - this.proteins) * (-1);
                solver.SetCoefficient(Proteins, portions, prots);
                int carbs = ((int)(m.Carbohydrates * m.Portion_Size / 100) - this.carbohydrates) * (-1);
                solver.SetCoefficient(Carbohydrates, portions, carbs);
                int fa = ((int)(m.Fats * m.Portion_Size / 100) - this.fats) * (-1);
                solver.SetCoefficient(Fats, portions, fa);
                solver.SetCoefficient(cost, portions, rand.Next(0, 100));
                mealPortions.Add(portions);
            }

            solver.SetBounds(Calories, this.calories, this.calories);
            //solver.SetBounds(Proteins, 0, int.MaxValue);
            //solver.SetBounds(Carbohydrates, 0, int.MaxValue);
            //solver.SetBounds(Fats, 0, int.MaxValue);
            solver.AddGoal(cost, 1, true);
            solver.Solve(new SimplexSolverParams());
            for (int i = 0; i < this.mealPortions.Count; i++)
            {
                this.mealPortions[i] = (int)solver.GetValue(this.mealPortions[i]).ToDouble();
            }
        }
Пример #2
0
Файл: Otm.cs Проект: falreis/tcc
        public void Get()
        {
            SimplexSolver solver = new SimplexSolver();

            double[] estimatedProfitOfProjectX  = new double[] { 1, 1.8, 1.6, 0.8, 1.4 };
            double[] capitalRequiredForProjectX = new double[] { 6, 12, 10, 4, 8 };
            double   availableCapital           = 20;

            int[] chooseProjectX = new int[5];

            int profit;

            solver.AddRow("profit", out profit);
            solver.AddGoal(profit, 0, false);

            int expenditure;

            solver.AddRow("expenditure", out expenditure);
            solver.SetBounds(expenditure, 0, availableCapital);

            for (int i = 0; i < 5; i++)
            {
                solver.AddVariable(string.Format("project{0}", i), out chooseProjectX[i]);
                solver.SetBounds(chooseProjectX[i], 0, 1);
                solver.SetIntegrality(chooseProjectX[i], true);
                solver.SetCoefficient(profit, chooseProjectX[i], estimatedProfitOfProjectX[i]);
                solver.SetCoefficient(expenditure, chooseProjectX[i], capitalRequiredForProjectX[i]);
            }

            SimplexSolverParams param = new SimplexSolverParams();

            param.MixedIntegerGenerateCuts = true;
            solver.Solve(param);

            Console.WriteLine(solver.MipResult);
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("Project {0} is {1} selected.", i, solver.GetValue(chooseProjectX[i]) == 1 ? "" : "not ");
            }
            Console.WriteLine("The estimated total profit is: ${0} million.", (double)solver.GetValue(profit).ToDouble());
            Console.WriteLine("The total expenditure is: ${0} million.", solver.GetValue(expenditure).ToDouble());
        }
Пример #3
0
        /// <summary>
        /// Funkcja z pomocą której następuje uruchomienie solvera i wykonanie obliczeń optymalizujących cenę
        /// </summary>
        /// <param name="alloys">Lista stopów biorących udział w procesie wytapiania</param>
        /// <param name="smelt">Wytop który chcemy uzyskać</param>
        private async void calculate_price(List <Alloy> alloys, List <Smelt> smelt)
        {
            int[] tabVar = new int[alloys.Count];  //il stopow w wytopie
            int[] tabEq  = new int[31];            //il pierwiastkow + war + f. celu

            try
            {
                //warunek nieujemnosci zmiennych
                for (int i = 0; i < tabVar.Count(); i++)
                {
                    solver.AddVariable(alloys.ElementAt(i).name, out tabVar[i]);
                    solver.SetBounds(tabVar[i], 0, Rational.PositiveInfinity);
                }

                //E(Aij*Xij>bi) i=1,2,..,n
                for (int i = 0; i < tabEq.Count() - 2; i++)
                {
                    solver.AddRow("r" + i.ToString(), out tabEq[i]);
                    for (int j = 0; j < selectedAlloys.Count; j++)
                    {
                        //zmienna, kazda zawartosc pierwiastka x jego wsp. parowania
                        solver.SetCoefficient(tabEq[i], tabVar[j], selectedAlloys.ElementAt(j).tabOfElements[i] * (1 - smelt.ElementAt(0).evoporation[i] / 100));
                    }
                    solver.SetBounds(tabEq[i], smelt.ElementAt(0).min_Norm[i] * smelt.ElementAt(0).Weight, smelt.ElementAt(0).max_Norm[i] * smelt.ElementAt(0).Weight);
                }

                solver.AddRow("weightCond", out tabEq[29]);           //war masowy
                solver.AddRow("cost", out tabEq[30]);                 //f celu

                for (int i = 0; i < alloys.Count; i++)
                {
                    solver.SetCoefficient(tabEq[29], tabVar[i], 1);
                    if (alloys.ElementAt(i).Price == 0)
                    {
                        solver.SetCoefficient(tabEq[30], tabVar[i], 1);
                    }
                    else
                    {
                        solver.SetCoefficient(tabEq[30], tabVar[i], alloys.ElementAt(i).Price);
                    }
                }
                solver.SetBounds(tabEq[29], smelt.ElementAt(0).Weight, smelt.ElementAt(0).Weight);
                solver.AddGoal(tabEq[30], 1, true);

                //po obliczeniach pokaż ekran z wynikami
                await Navigation.PushAsync(new ProcessResults(solver, alloys, smelt, SwitchPriceWeight.IsToggled));
            }
            catch (Exception ex)
            {
                await DisplayAlert("Error", "Cos w solverze nie tak:\n" + ex.ToString(), "OK");
            }
        }
Пример #4
0
        public void SetOREData(float ratio)
        {
            foreach (var kv in oreDictionary.ORE_Dictionary)
            {
                if (useFullORE || kv.Value.isMain)
                {
                    kv.Value.ratio = ratio;

                    solver.SetCoefficient(a, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["三钛合金"]));
                    solver.SetCoefficient(b, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["类晶体胶矿"]));
                    solver.SetCoefficient(c, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["类银超金属"]));
                    solver.SetCoefficient(d, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["同位聚合体"]));
                    solver.SetCoefficient(e, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["超星诺克石"]));
                    solver.SetCoefficient(f, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["晶状石英岩"]));
                    solver.SetCoefficient(g, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["超噬矿"]));
                    solver.SetCoefficient(h, kv.Value.id, Math.Round(kv.Value.ratio * kv.Value.mineral["莫尔石"]));

                    solver.SetCoefficient(cost, kv.Value.id, kv.Value.price);
                }
            }
        }
Пример #5
0
        static void Main(string[] args)
        {
            SimplexSolver solver = new SimplexSolver();
            int           savid, vzvid;

            solver.AddVariable("Saudi", out savid);
            solver.AddVariable("Ven", out vzvid);
            solver.SetBounds(savid, 0, 9000);
            solver.SetBounds(vzvid, 0, 6000);

            int c1rid, c2rid, c3rid, goalrid;

            solver.AddRow("c1", out c1rid);
            solver.AddRow("c2", out c2rid);
            solver.AddRow("c3", out c3rid);
            solver.AddRow("goal", out goalrid);

            // add coefficients to constraint rows
            solver.SetCoefficient(c1rid, savid, 0.3);
            solver.SetCoefficient(c1rid, vzvid, 0.4);
            solver.SetBounds(c1rid, 2000, Rational.PositiveInfinity);
            solver.SetCoefficient(c2rid, savid, 0.4);
            solver.SetCoefficient(c2rid, vzvid, 0.2);
            solver.SetBounds(c2rid, 1500, Rational.PositiveInfinity);
            solver.SetCoefficient(c3rid, savid, 0.2);
            solver.SetCoefficient(c3rid, vzvid, 0.3);
            solver.SetBounds(c3rid, 500, Rational.PositiveInfinity);

            // add objective (goal) to model and specify minimization (==true)
            solver.SetCoefficient(goalrid, savid, 20);
            solver.SetCoefficient(goalrid, vzvid, 15);
            solver.AddGoal(goalrid, 1, true);

            solver.Solve(new SimplexSolverParams());

            Console.WriteLine("SA {0}, VZ {1}, C1 {2}, C2 {3}, C3 {4}, Goal {5}",
                              solver.GetValue(savid).ToDouble(), solver.GetValue(vzvid).ToDouble(),
                              solver.GetValue(c1rid).ToDouble(), solver.GetValue(c2rid).ToDouble(),
                              solver.GetValue(c3rid).ToDouble(), solver.GetValue(goalrid).ToDouble());

            Console.ReadLine();
        }
Пример #6
0
        /// <summary>
        /// Solves a sample (randomly generated?) cutting stock problem.
        /// Given a bolt of cloth of fixed width, and demand for cut strips of the cloth, determine the min "loss" cut patterns to use and how many
        /// of them.
        /// Loss is defined as the scrap thrown away.
        /// It is acceptable to have extra cut widths made.  They do not contribute to the cost.  (this may be unrealistic in the real world)
        /// Solver runs by 1st creating an enumeration of possible cut patterns using a CspSolver, then choosing between the patterns and selecting a qty of the patterns such that the
        /// amount of scrap is minimized and all demand is met using the SimplexSolver MIP code.
        ///
        /// In an industrial case, there would likely be more constraints in the generation of the cut patterns.  There can be other restrictions such as "these can't be done together"
        /// or "these MUST be done together (matching pattern or color?)".  This can easily be added to the CspSolver model.
        /// Also, there are likely other characteristics of the cuts or the master problem which would need adaptations.
        ///
        /// Further, the limit on the columns generated is implemented in a very arbitrary order.  It is more likely that some ordering of the
        /// value of the columns is needed.  In most industrial occurances, the dual variables from the LP relaxation would likely be used to
        /// guide the generation of columns in an interative fasion rather than a one-time shot at the beginning.
        ///
        /// YMMV
        /// </summary>
        public static void ShortCuttingStock()
        {
            Console.WriteLine("*** Short Cutting Stock ***");
            int    NumItems    = 5;     // how many cut widths to generate
            int    ClothWidth  = 40;    // width of the stock to cut the widths from
            double efficiency  = 0.7;   // reject cut patterns less than this % used of the clothwidth
            int    maxPatterns = 100;   // max # of patterns to generate
            bool   verbose     = true;  // set this to true if you want some (useful?) output
            bool   saveMpsFile = false; // set this to true if you want it to save an mps file in c:\\temp\\cutstock.mps

            int itemSizeMin   = 5;      // minimum size for random generation of cut
            int itemSizeMax   = 10;     // maximum size for random generation of cut
            int itemDemandMin = 10;     // minimum random demand for each cut
            int itemDemandMax = 40;     // maximum random demand for each cut

            int seed = 12447;           // use System.DateTime.Now.Millisecond; instead if you want a random problem.

            if (verbose)
            {
                System.Console.WriteLine(String.Format("Random seed={0}\tmaxWidth={1}", seed, ClothWidth));
            }

            Random rand = new Random(seed);

            int[] cuts   = new int[NumItems];
            int[] demand = new int[NumItems];
            // item weights and demands
            for (int cnt = 0; cnt < NumItems; cnt++)
            {
                cuts[cnt]   = rand.Next(itemSizeMin, itemSizeMax);;
                demand[cnt] = rand.Next(itemDemandMin, itemDemandMax);
                if (verbose)
                {
                    System.Console.WriteLine(String.Format("item[{0}]\tweight={1}\tdemand={2}", cnt, cuts[cnt], demand[cnt]));
                }
            }
            List <int[]> patterns;

            SolveKnapsack(maxPatterns, cuts, ClothWidth, efficiency, out patterns);
            SimplexSolver solver2 = new SimplexSolver();
            int           vId     = 0;

            int[] usage = new int[patterns.Count];
            // construct rows that make sure that the demand is met for each kind of cut
            for (int cnt = 0; cnt < NumItems; cnt++)
            {
                solver2.AddRow(String.Format("item{0}", cnt), out vId);
                solver2.SetBounds(vId, demand[cnt], Rational.PositiveInfinity);
            }
            int patCnt = 0;

            if (verbose)
            {
                System.Console.WriteLine(String.Format("Generated {0} patterns", patterns.Count));
            }
            // set usage coeffs (A matrix entries) -- put the patterns as columns in the MIP.
            Dictionary <int, int> patIdForCol = new Dictionary <int, int>();

            foreach (int[] pattern in patterns)
            {
                int    pId     = 0;
                String varName = String.Format("Pattern{0}", patCnt);
                solver2.AddVariable(varName, out pId);
                patIdForCol[pId] = patCnt;
                solver2.SetIntegrality(pId, true);
                solver2.SetBounds(pId, 0, Rational.PositiveInfinity);
                for (int cnt = 0; cnt < NumItems; cnt++)
                {
                    solver2.SetCoefficient(cnt, pId, pattern[cnt]); // set the coefficient in the matrix
                                                                    // accumulate the quantity used for this pattern.  It will be used to figure out the scrap later.
                    usage[patCnt] += pattern[cnt] * cuts[cnt];
                }
                patCnt++;
            }
            // set objective coeffs.  --- the cost is the scrap
            solver2.AddRow("Scrap", out vId);
            for (int cnt = 0; cnt < patterns.Count; cnt++)
            {
                int colId = solver2.GetIndexFromKey(String.Format("Pattern{0}", cnt));
                solver2.SetCoefficient(vId, colId, (ClothWidth - usage[cnt]));
            }
            solver2.AddGoal(vId, 0, true);
            // invoke the IP solver.
            SimplexSolverParams parms = new SimplexSolverParams();

            parms.MixedIntegerGenerateCuts = true;
            parms.MixedIntegerPresolve     = true;

            if (saveMpsFile)
            {
                MpsWriter writer = new MpsWriter(solver2);
                using (TextWriter textWriter = new StreamWriter(File.OpenWrite("c:\\temp\\cutstock.mps")))
                {
                    writer.WriteMps(textWriter, true);
                }
            }
            solver2.Solve(parms);
            if (solver2.LpResult == LinearResult.Optimal &&
                solver2.MipResult == LinearResult.Optimal)
            {
                //Rational[] solutionVals = solver2.GetValues();
                int goalIndex = 0;
                // output if desired.
                if (verbose)
                {
                    System.Console.WriteLine("Solver complete, printing cut plan.");
                    foreach (int cnt in solver2.VariableIndices)
                    {
                        Rational val = solver2.GetValue(cnt);
                        if (val != 0)
                        {
                            if (solver2.IsGoal(cnt))
                            {
                                goalIndex = cnt;
                                System.Console.WriteLine(String.Format("Goal:{0}\t:   {1}\t", val, solver2.GetKeyFromIndex(cnt)));
                            }
                            else if (solver2.IsRow(cnt))
                            {
                                System.Console.WriteLine(String.Format("{0}:\tValue=   {1}\t", solver2.GetKeyFromIndex(cnt), val));
                            }
                            else
                            {
                                System.Console.Write(String.Format("{0}\tQuantity={1}:\t", solver2.GetKeyFromIndex(cnt), val));
                                for (int cnt2 = 0; cnt2 < NumItems; cnt2++)
                                {
                                    System.Console.Write(String.Format("{0} ", patterns[patIdForCol[cnt]][cnt2]));
                                }
                                System.Console.WriteLine(String.Format("\tUsage:{0} / {2} efficiency={1}%", usage[cnt - NumItems], (int)(100 * (double)usage[cnt - NumItems] / (double)ClothWidth), ClothWidth));
                            }
                        }
                    }
                    System.Console.WriteLine(String.Format("Total scrap={0}", solver2.GetSolutionValue(goalIndex)));
                }
            }
            else
            {
                System.Console.WriteLine("Generated problem is infeasible.  It is likely that more generated columns are needed.");
            }

            Console.WriteLine();
        }
Пример #7
0
        public List <Bag.Item> LinearProgramming(List <Item> items)
        {
            List <Bag.Item> choosenItems = new List <Bag.Item>();
            int             itemsCount   = items.Count;
            double          capacity     = MaxWeightAllowed;

            SimplexSolver solver = new SimplexSolver();

            //double[] estimatedProfitOfProjectX = new double[] { 1, 1.8, 1.6, 0.8, 1.4 };
            //double[] capitalRequiredForProjectX = new double[] { 6, 12, 10, 4, 8 };
            //double availableCapital = 20;
            //int[] chooseProjectX = new int[5];

            double[] values  = new double[itemsCount];
            double[] weights = new double[itemsCount];

            for (int i = 0; i < itemsCount; i++)
            {
                values[i]  = items[i].Value;
                weights[i] = items[i].Weight;
            }

            int[] chosenItems = new int[itemsCount];

            //int profit;
            //solver.AddRow("profit", out profit);
            //solver.AddGoal(profit, 0, false);

            int MaximumValue;

            solver.AddRow("MaximumValue", out MaximumValue);
            solver.AddGoal(MaximumValue, 0, false);

            //int expenditure;
            //solver.AddRow("expenditure", out expenditure);
            //solver.SetBounds(expenditure, 0, availableCapital);

            int MaximumWeight;

            solver.AddRow("MaximumWeight", out MaximumWeight);
            solver.SetBounds(MaximumWeight, 0, capacity);

            //for (int i = 0; i < 5; i++)
            //{
            //    solver.AddVariable(string.Format("project{0}", i),
            //                        out chooseProjectX[i]);
            //    solver.SetBounds(chooseProjectX[i], 0, 1);
            //    solver.SetIntegrality(chooseProjectX[i], true);
            //    solver.SetCoefficient(profit, chooseProjectX[i],
            //                           estimatedProfitOfProjectX[i]);
            //    solver.SetCoefficient(expenditure, chooseProjectX[i],
            //                           capitalRequiredForProjectX[i]);
            //}

            for (int i = 0; i < itemsCount; i++)
            {
                solver.AddVariable(string.Format("Item{0}", i),
                                   out chosenItems[i]);
                solver.SetBounds(chosenItems[i], 0, 1);
                solver.SetIntegrality(chosenItems[i], true);
                solver.SetCoefficient(MaximumValue, chosenItems[i],
                                      values[i]);
                solver.SetCoefficient(MaximumWeight, chosenItems[i],
                                      weights[i]);
            }

            //SimplexSolverParams param = new SimplexSolverParams();
            //param.MixedIntegerGenerateCuts = true;
            //solver.Solve(param);

            SimplexSolverParams param = new SimplexSolverParams();

            param.MixedIntegerGenerateCuts = true;
            solver.Solve(param);

            //Console.WriteLine(solver.MipResult);
            //for (int i = 0; i < 5; i++)
            //{
            //    Console.WriteLine("Project {0} is {1} selected.", i,
            //                    solver.GetValue(chooseProjectX[i]) == 1 ? "" : "not ");
            //}
            //Console.WriteLine("The estimated total profit is: ${0} million.",
            //                  (double)solver.GetValue(profit).ToDouble());
            //Console.WriteLine("The total expenditure is: ${0} million.",
            //                   solver.GetValue(expenditure).ToDouble());

            //string result = "";
            for (int i = 0; i < itemsCount; i++)
            {
                if (solver.GetValue(chosenItems[i]) == 1)
                {
                    //result += "Selected Item " + i + " ";
                    choosenItems.Add(items[i]);
                }
            }

            //result += " value "+(double)solver.GetValue(MaximumValue).ToDouble()+ " weight "+ solver.GetValue(MaximumWeight).ToDouble();


            return(choosenItems);
        }
Пример #8
0
        public SimplexSolver PrepareSimplexSolver(int maxAgents, out Dictionary <Models.Shift, int> shiftsExt, out int vidGoal)
        {
            var SX = new SimplexSolver();

            var maxRq = HalfHourRequirements.Max(x => x.RequiredForce);
            var sumRq = HalfHourRequirements.Sum(x => x.RequiredForce);

            shiftsExt = null;

            ////goal: total no of agents
            //int ridgoal;
            //SX.AddRow(key: "goal", vid: out ridgoal);
            //SX.SetBounds(ridgoal, lower: 0, upper: maxAgents); //total agents must range from 0 to max agents available
            //SX.AddGoal(vid: ridgoal, pri: 1, fMinimize: true); //minimize total number of agents for that day

            //goal: total no of agents, minimize overtime
            int ridgoal;

            SX.AddRow(key: "goal", vid: out ridgoal);
            SX.SetBounds(ridgoal, lower: 0, upper: Rational.PositiveInfinity); // upper: maxAgents); //total agents must range from 0 to max agents available
            SX.AddGoal(vid: ridgoal, pri: 2, fMinimize: true);                 //minimize total number of agents for that day

            //goal2: Minimize excess force
            int ridexcess;

            SX.AddRow(key: "excess", vid: out ridexcess);
            SX.SetBounds(ridexcess, lower: 0, upper: sumRq);     //allow excess from 0 to sum of all requirements
            SX.AddGoal(vid: ridexcess, pri: 1, fMinimize: true); //try to minimize excess time

            var shiftsX = new Dictionary <Models.Shift, int>();
            int i       = 0;

            Shifts.ForEach(x => {
                int vid;
                SX.AddVariable(key: string.Format("{0}. {1} {2:hh}:{2:mm} - {3:hh}:{3:mm}", i + 1, x.Name, x.Start, x.End), vid: out vid);
                //the no of agents on each shift may not exceed to max requirements
                SX.SetBounds(vid: vid, lower: 0, upper: maxRq);
                shiftsX.Add(x, vid);
                //SX.SetCoefficient(vidRow: ridgoal, vidVar: vid, num: 1); //normal weight: all shifts are the same
                SX.SetCoefficient(vidRow: ridgoal, vidVar: vid, num: x.Duration.Hours <= 8 ? 1 : 10); //weights per shift: overtime = *10 more expensive than normal shifts
                i++;
            });

            //Constraint - Agents from every active shift on every half hour must be >= requirement for that half hour
            HalfHourRequirements.ForEach(hh =>
            {
                List <int> vidShiftsActive = new List <int>();
                foreach (var entry in shiftsX)
                {
                    if (entry.Key.IncludesHalfHour(hh.Start))
                    {
                        vidShiftsActive.Add(entry.Value);
                    }
                }

                //add constraint for sum of force of active shifts on that halfhour
                //if we need agents but no shifts exists for a halfhour, do not add a constraint
                if (vidShiftsActive.Count > 0)
                {
                    int ridHalf;
                    SX.AddRow(key: string.Format("{0:hh}:{0:mm} [{1}]", hh.Start, hh.RequiredForce), vid: out ridHalf);
                    //specify whichs shifts contributes to half hour's total force
                    vidShiftsActive.ForEach(vidShift =>
                    {
                        SX.SetCoefficient(vidRow: ridHalf, vidVar: vidShift, num: 1);
                        SX.SetCoefficient(vidRow: ridexcess, vidVar: vidShift, num: 1);
                    });
                    //each 30' span, must have at least as many required agents
                    SX.SetBounds(vid: ridHalf, lower: hh.RequiredForce, upper: Rational.PositiveInfinity);
                }
            });

            shiftsExt = shiftsX;
            vidGoal   = ridgoal;

            return(SX);
        }
Пример #9
0
        public static RecipeGraph FromLibrary(Library library, IEnumerable <Item> inputs, IEnumerable <ItemAmount> outputs, Func <Item, double> costFunction)
        {
            if (library == null)
            {
                throw new ArgumentNullException("library");
            }
            if (inputs == null)
            {
                throw new ArgumentNullException("inputs");
            }
            if (outputs == null)
            {
                throw new ArgumentNullException("outputs");
            }
            if (costFunction == null)
            {
                throw new ArgumentNullException("costFunction");
            }

            var solver     = new SimplexSolver();
            var itemRows   = new Dictionary <Item, int>();
            var recipeVars = new Dictionary <Recipe, int>();
            var wasteGoals = new Dictionary <Item, ILinearGoal>();

            foreach (var item in library.Items)
            {
                int id;
                solver.AddRow(item, out id);
                itemRows.Add(item, id);
                solver.SetBounds(id, 0, Rational.PositiveInfinity);
                if (!inputs.Contains(item))
                {
                    wasteGoals.Add(item, solver.AddGoal(id, 1, true));
                }
            }

            // Bound output to requested values
            foreach (var itemAmount in outputs)
            {
                var item   = itemAmount.Item;
                var amount = itemAmount.Amount;
                solver.SetBounds(itemRows[item], amount, amount);
            }

            foreach (var recipe in library.Recipes)
            {
                int id;
                solver.AddVariable(recipe, out id);
                recipeVars.Add(recipe, id);
                solver.SetBounds(id, 0, Rational.PositiveInfinity);

                foreach (var input in recipe.Ingredients)
                {
                    solver.SetCoefficient(itemRows[input.Item], id, -input.Amount);
                }
                foreach (var output in recipe.Results)
                {
                    solver.SetCoefficient(itemRows[output.Item], id, output.Amount);
                }
            }

            // Add input minimize goals
            foreach (var item in inputs)
            {
                var row = itemRows[item];
                solver.SetBounds(row, Rational.NegativeInfinity, 0);
                solver.AddGoal(row, (int)(10000 * costFunction(item)), false);
            }

            solver.Solve(new SimplexSolverParams());

            if (solver.SolutionQuality != Microsoft.SolverFoundation.Services.LinearSolutionQuality.Exact)
            {
                throw new InvalidOperationException("Cannot solve problem");
            }

            List <Tuple <Recipe, double> > usedRecipes = new List <Tuple <Recipe, double> >();

            foreach (var recipe in library.Recipes)
            {
                var value = (double)solver.GetValue(recipeVars[recipe]);
                if (value > 0)
                {
                    usedRecipes.Add(new Tuple <Recipe, double>(recipe, value));
                }
            }

            var sortedRecipes = usedRecipes.SortTopological((a, b) => a.Item1.Results.Select((i) => i.Item).Intersect(b.Item1.Ingredients.Select((i) => i.Item)).Any(), true);
            List <SourceStep>           inputSteps     = new List <SourceStep>();
            List <SinkStep>             outputSteps    = new List <SinkStep>();
            List <SinkStep>             wasteSteps     = new List <SinkStep>();
            Dictionary <Item, FlowStep> itemSteps      = new Dictionary <Item, FlowStep>();
            List <FlowStep>             flowSteps      = new List <FlowStep>();
            List <TransformStep>        transformSteps = new List <TransformStep>();

            foreach (var item in library.Items)
            {
                var value = (double)solver.GetValue(itemRows[item]);
                if (value > 0)
                {
                    var sink = new SinkStep(new ItemAmount(item, value / 2));
                    if (outputs.Select((o) => o.Item).Contains(item))
                    {
                        outputSteps.Add(sink);
                    }
                    else
                    {
                        wasteSteps.Add(sink);
                    }
                    itemSteps.Add(item, sink);
                }
                else if (value < 0)
                {
                    var source = new SourceStep(new ItemAmount(item, -value));
                    inputSteps.Add(source);
                    itemSteps.Add(item, source);
                }
            }

            foreach (var recipe in sortedRecipes)
            {
                foreach (var result in recipe.Item1.Results)
                {
                    var item = result.Item;
                    if (!itemSteps.ContainsKey(item))
                    {
                        var flowstep = new FlowStep(new ItemAmount(item, 0));
                        itemSteps.Add(item, flowstep);
                        flowSteps.Add(flowstep);
                    }
                }
            }

            foreach (var recipe in sortedRecipes)
            {
                var previous = recipe.Item1.Ingredients.Select((i) => i.Item);
                var step     = new TransformStep(recipe.Item1, recipe.Item2);
                foreach (var item in previous)
                {
                    var prev = itemSteps[item];
                    step.Previous.Add(prev);
                }

                foreach (var amount in recipe.Item1.Results)
                {
                    var item = amount.Item;

                    itemSteps[item].Previous.Add(step);
                    itemSteps[item].Item += amount * recipe.Item2;
                }
                transformSteps.Add(step);
            }

            return(new RecipeGraph(wasteSteps, inputSteps, outputSteps, flowSteps, transformSteps));
        }