GurobiDirective gurobiDirective = new GurobiDirective(); gurobiDirective.OutputFlag = true; Solution solution = context.Solve(gurobiDirective);
Optimize a model with 185 Rows, 380 Columns and 1199 NonZeroes Presolve removed 1 rows and 185 columns Presolve time: 0.30s Presolved: 184 Rows, 195 Columns, 832 Nonzeros Objective GCD is 1 Root relaxation: objective 2.783286e+03, 36 iterations, 0.02 seconds Nodes | Current Node | Objective Bounds | Work Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time 0 0 2783.2857 0 14 - 2783.2857 - - 0s 0 0 3006.1964 0 29 - 3006.1964 - - 0s 0 0 3006.1964 0 29 - 3006.1964 - - 0s 0 0 3006.1964 0 25 - 3006.1964 - - 0s 0 0 3006.1964 0 25 - 3006.1964 - - 0s H 0 0 6029.0000 3006.1964 50.1% - 0s 0 0 3006.1964 0 25 6029.0000 3006.1964 50.1% - 0s H 0 2 3805.0000 3006.1964 21.0% - 0s 0 2 3006.1964 0 25 3805.0000 3006.1964 21.0% - 0s * 75 54 29 3644.0000 3077.0000 15.6% 8.2 0s * 129 80 27 3381.0000 3077.0000 8.99% 8.7 0s * 158 62 30 3323.0000 3077.0000 7.40% 8.1 0s Cutting planes: Learned: 3 Gomory: 1 MIR: 3 Explored 1231 nodes (5693 simplex iterations) in 0.69 seconds Thread count was 2 (of 2 available processors) Optimal solution found (tolerance 1.00e-04) Best objective 3.3230000000e+03, best bound 3.3230000000e+03, gap 0.0000%
gurobiDirective.Heuristics = 0.1; gurobiDirective.Presolve = PresolveLevel.None;
There is a full reference in the Gurobi Solver Programming Primer that ships with Solver Foundation. My point is that you do not get a "least common denominator" solution - you get all the bells and whistles.
Gurobi is just one example - there are a wide range of plug-ins available, covering most of the popular LP and MIP solvers - check out solverfoundation.com for the full list. To use a plug-in solver, you need to: 1) install the solver (duh), 2) change your exe.config or web.config to point to the solver. No code changes are required. You can find additional documentation on the default MIP directives and the third-party Solver configuration in the SFS Programming Primer.
Last thing - modern programming languages like C# are cool because they promote reusability and maintainability. Don't forget about that when you are writing SFS code. It's common for models in a particular vertical (say, finance or transportation) to have common "blocks" of goals or constraints that are re-used over and over again. It's pretty easy to build reusable model libraries using extension methods. For example, take the assignment constraints in my TSP example. I could factor them out:
public static class ModelingExtensions { public static void AssignmentConstraintsNoDiag(this Model model, Set s, Decision assign) { model.AddConstraint("A1", Model.ForEach(s, i => Model.Sum(Model.ForEachWhere(s, j => assign[i, j], j => i != j)) == 1)); model.AddConstraint("A2", Model.ForEach(s, j => Model.Sum(Model.ForEachWhere(s, i => assign[i, j], i => i != j)) == 1)); } }
On the Solver Foundation MSDN forum there was a question about how to read model data from a DB and use it within a Solver Foundation model. In this post I will extend my production planning sample to use LINQ to SQL. To follow along at home you will need to have a recent version of SQL Server installed locally, and some basic knowledge of how to create SQL tables. You should also have compiled and run the code from my previous post.
Step 1: Create and populate the DB
The first step is to create tables corresponding to the entities in my model. I created a very simple DB with three tables: Countries, Products, and Yields. The Yields table has foreign key constraints to the Countries and Products tables. Here is a diagram:
To populate the DB I just wrote a script that inserts my problem data, and ran itin SQL Management Studio. Here's the script (and forgive my SQL):
GO DELETE FROM Yields DELETE FROM Products DELETE FROM Countries GO INSERT INTO Countries (Id, Name, Limit, Cost) VALUES (0, 'SA', 9000, 20) INSERT INTO Countries (Id, Name, Limit, Cost) VALUES (1, 'VZ', 6000, 15) GO INSERT INTO Products (Id, Name, Demand) VALUES (0, 'Gas', 1900) INSERT INTO Products (Id, Name, Demand) VALUES (1, 'Jet Fuel', 1500) INSERT INTO Products (Id, Name, Demand) VALUES (2, 'Lubricant', 500) GO INSERT INTO Yields (CountryId, ProductId, Value) VALUES (0, 0, 0.3) INSERT INTO Yields (CountryId, ProductId, Value) VALUES (1, 0, 0.4) INSERT INTO Yields (CountryId, ProductId, Value) VALUES (0, 1, 0.4) INSERT INTO Yields (CountryId, ProductId, Value) VALUES (1, 1, 0.2) INSERT INTO Yields (CountryId, ProductId, Value) VALUES (0, 2, 0.2) INSERT INTO Yields (CountryId, ProductId, Value) VALUES (1, 2, 0.3)
Step 2: Create Entity and DataContext classes in Visual Studio
Scott Guthrie's blog (and the MSDN docs) show you exactly how to do this:
Step 3: Modify Solver Foundation Services data binding code
This is in fact very easy because Solver Foundation Services was designed to work well with LINQ. Take the PetrochemDataBinding sample from last time, and change the SetBinding statements to work with the PetrochemDataContext class instead of a hardcoded DataSet. The code is almost identical:
private static void PetrochemLinqDataBinding() { SolverContext context = SolverContext.GetContext(); context.ClearModel(); Model model = context.CreateModel(); PetrochemDataContext db = new PetrochemDataContext(); Set products = new Set(Domain.Any, "products"); Set countries = new Set(Domain.Any, "countries"); Parameter demand = new Parameter(Domain.Real, "demand", products); demand.SetBinding(db.Products, "Demand", "Id"); Parameter yield = new Parameter(Domain.Real, "yield", products, countries); yield.SetBinding(db.Yields, "Value", "ProductId", "CountryId"); Parameter limit = new Parameter(Domain.Real, "limit", countries); limit.SetBinding(db.Countries, "Limit", "Id"); Parameter cost = new Parameter(Domain.Real, "cost", countries); cost.SetBinding(db.Countries, "Cost", "Id"); model.AddParameters(demand, yield, limit, cost); Decision produce = new Decision(Domain.RealNonnegative, "produce", countries); model.AddDecision(produce); model.AddGoal("goal", GoalKind.Minimize, Model.Sum(Model.ForEach(countries, c => cost[c] * produce[c]))); model.AddConstraint("Demand", Model.ForEach(products, p => Model.Sum(Model.ForEach(countries, c => yield[p, c] * produce[c])) >= demand[p]) ); model.AddConstraint("Production limit", Model.ForEach(countries, c => produce[c] <= limit[c]) ); Solution solution = context.Solve(new SimplexDirective()); Report report = solution.GetReport(); Console.WriteLine(report); }
That's all there is to it! Note that instead of passing the entire collection (e.g. db.Countries) you could easily use LINQ statements or stored procedures, or whatever you like.