/*++ Copyright (c) 2015 Microsoft Corporation --*/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.IO; using Microsoft.Z3; using Microsoft.SolverFoundation.Common; using Microsoft.SolverFoundation.Services; using Microsoft.SolverFoundation.Plugin; namespace Microsoft.SolverFoundation.Plugin.Z3 { /// /// The class is implementation of the MSF mixed linear programming solver /// using the Microsoft Z3 solver as the backend. /// public class Z3MILPSolver : LinearModel, ILinearSolver, ILinearSolution, IReportProvider { #region Private members private LinearResult _result; private LinearSolutionQuality _solutionQuality; private Z3BaseSolver _solver; #endregion Private members #region Solver construction and destruction /// Constructor that initializes the base classes public Z3MILPSolver() : base(null) { _result = LinearResult.Feasible; _solver = new Z3BaseSolver(this); } /// Constructor that initializes the base classes public Z3MILPSolver(ISolverEnvironment context) : this() { } /// /// Shutdown can be called when when the solver is not active, i.e. /// when it is done with Solve() or it has gracefully returns from Solve() /// after an abort. /// public void Shutdown() { _solver.DestructSolver(true); } #endregion Solver construction and destruction #region Obtaining information about the solution public ILinearSolverReport GetReport(LinearSolverReportType reportType) { // We don't support sensitivity report return null; } #endregion Obtaining information about the solution #region Construction of the problem /// /// Get corresponding Z3 formula of a MSF row. /// /// The MSF row id private ArithExpr MkGoalRow(int rid) { // Start with the 0 term List row = new List(); // Now, add all the entries of this row foreach (LinearEntry entry in GetRowEntries(rid)) { // Get the variable and constant in the row ArithExpr e = _solver.GetVariable(entry.Index); if (!entry.Value.IsOne) { e = _solver.Context.MkMul(_solver.GetNumeral(entry.Value, e.Sort), e); } row.Add(e); } switch (row.Count) { case 0: return _solver.GetNumeral(new Rational()); case 1: return row[0]; default: return _solver.Context.MkAdd(row.ToArray()); } } /// /// Adds a MSF row to the Z3 assertions. /// /// The MSF row id private void AddRow(int rid) { // Start with the 0 term ArithExpr row = MkGoalRow(rid); _solver.AssertArith(rid, row); } /// /// Set results based on internal solver status /// private void SetResult(Z3Result status) { switch (status) { case Z3Result.Optimal: _result = LinearResult.Optimal; _solutionQuality = LinearSolutionQuality.Exact; break; case Z3Result.LocalOptimal: _result = LinearResult.Feasible; _solutionQuality = LinearSolutionQuality.Approximate; break; case Z3Result.Feasible: _result = LinearResult.Feasible; _solutionQuality = LinearSolutionQuality.Exact; break; case Z3Result.Infeasible: _result = LinearResult.InfeasiblePrimal; _solutionQuality = LinearSolutionQuality.None; break; case Z3Result.Interrupted: _result = LinearResult.Interrupted; _solutionQuality = LinearSolutionQuality.None; break; default: Debug.Assert(false, "Unrecognized Z3 Result"); break; } } #endregion Construction of the problem #region Solving the problem /// /// Starts solving the problem using the Z3 solver. /// /// Parameters to the solver /// The solution to the problem public ILinearSolution Solve(ISolverParameters parameters) { // Get the Z3 parameters var z3Params = parameters as Z3BaseParams; Debug.Assert(z3Params != null, "Parameters should be an instance of Z3BaseParams."); _solver.Solve(z3Params, Goals, AddRow, MkGoalRow, SetResult); return this; } #endregion Solving the problem #region ILinearSolution Members public Rational GetSolutionValue(int goalIndex) { var goal = Goals.ElementAt(goalIndex); Debug.Assert(goal != null, "Goal should be an element of the goal list."); return GetValue(goal.Index); } public void GetSolvedGoal(int goalIndex, out object key, out int vid, out bool minimize, out bool optimal) { var goal = Goals.ElementAt(goalIndex); Debug.Assert(goal != null, "Goal should be an element of the goal list."); key = goal.Key; vid = goal.Index; minimize = goal.Minimize; optimal = _result == LinearResult.Optimal; } // LpResult is LP relaxation assignment. public LinearResult LpResult { get { return _result; } } public Rational MipBestBound { get { Debug.Assert(GoalCount > 0, "MipBestBound is only applicable for optimization instances."); return GetSolutionValue(0); } } public LinearResult MipResult { get { return _result; } } public LinearResult Result { get { return _result; } } public LinearSolutionQuality SolutionQuality { get { return _solutionQuality; } } public int SolvedGoalCount { get { return GoalCount; } } #endregion public Report GetReport(SolverContext context, Solution solution, SolutionMapping solutionMapping) { LinearSolutionMapping lpSolutionMapping = solutionMapping as LinearSolutionMapping; if (lpSolutionMapping == null && solutionMapping != null) throw new ArgumentException("solutionMapping is not a LinearSolutionMapping", "solutionMapping"); return new Z3LinearSolverReport(context, this, solution, lpSolutionMapping); } } /// /// Class implementing the LinearReport. /// public class Z3LinearSolverReport : LinearReport { public Z3LinearSolverReport(SolverContext context, ISolver solver, Solution solution, LinearSolutionMapping solutionMapping) : base(context, solver, solution, solutionMapping) { } } }