forked from openkylin/z3
388 lines
14 KiB
C#
388 lines
14 KiB
C#
|
||
/*++
|
||
Copyright (c) 2015 Microsoft Corporation
|
||
|
||
--*/
|
||
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Diagnostics;
|
||
using Microsoft.Z3;
|
||
using Microsoft.SolverFoundation.Common;
|
||
using Microsoft.SolverFoundation.Services;
|
||
|
||
namespace Microsoft.SolverFoundation.Plugin.Z3
|
||
{
|
||
internal enum Z3Result
|
||
{
|
||
Optimal,
|
||
LocalOptimal,
|
||
Feasible,
|
||
Interrupted,
|
||
Infeasible
|
||
}
|
||
|
||
/// <summary>
|
||
/// The basic solver class to take care of transformation from an MSF instance to an Z3 instance
|
||
/// </summary>
|
||
internal class Z3BaseSolver
|
||
{
|
||
/// <summary>Representing MSF model</summary>
|
||
private IRowVariableModel _model;
|
||
|
||
/// <summary>The Z3 solver we are currently using</summary>
|
||
private Context _context = null;
|
||
|
||
/// <summary>Default optimization solver</summary>
|
||
private Optimize _optSolver = null;
|
||
|
||
/// <summary>Marks when we are inside the Solve() method</summary>
|
||
private bool _isSolving = false;
|
||
|
||
/// <summary>A map from MSF variable ids to Z3 variables</summary>
|
||
private Dictionary<int, Expr> _variables = new Dictionary<int, Expr>();
|
||
|
||
/// <summary>A map from MSF variable ids to Z3 goal ids</summary>
|
||
private Dictionary<IGoal, Optimize.Handle> _goals = new Dictionary<IGoal, Optimize.Handle>();
|
||
|
||
internal Z3BaseSolver(IRowVariableModel model)
|
||
{
|
||
_model = model;
|
||
}
|
||
|
||
internal Context Context
|
||
{
|
||
get { return _context; }
|
||
}
|
||
|
||
internal Dictionary<int, Expr> Variables
|
||
{
|
||
get { return _variables; }
|
||
}
|
||
|
||
internal Dictionary<IGoal, Optimize.Handle> Goals
|
||
{
|
||
get { return _goals; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Destructs a currently active Z3 solver and the associated data.
|
||
/// </summary>
|
||
internal void DestructSolver(bool checkInSolve)
|
||
{
|
||
if (_context != null)
|
||
{
|
||
if (checkInSolve && !_isSolving)
|
||
{
|
||
_variables.Clear();
|
||
if (!_isSolving)
|
||
{
|
||
_optSolver.Dispose();
|
||
_context.Dispose();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Console.Error.WriteLine("Z3 destruction is invoked while in Solving phase.");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Constructs a Z3 solver to be used.
|
||
/// </summary>
|
||
internal void ConstructSolver(Z3BaseParams parameters)
|
||
{
|
||
// If Z3 is there already, kill it
|
||
if (_context != null)
|
||
{
|
||
DestructSolver(false);
|
||
}
|
||
|
||
_context = new Context();
|
||
_optSolver = _context.MkOptimize();
|
||
var p = _context.MkParams();
|
||
|
||
switch (parameters.OptKind)
|
||
{
|
||
case OptimizationKind.BoundingBox:
|
||
p.Add("priority", _context.MkSymbol("box"));
|
||
break;
|
||
case OptimizationKind.Lexicographic:
|
||
p.Add("priority", _context.MkSymbol("lex"));
|
||
break;
|
||
case OptimizationKind.ParetoOptimal:
|
||
p.Add("priority", _context.MkSymbol("pareto"));
|
||
break;
|
||
default:
|
||
Debug.Assert(false, String.Format("Unknown optimization option {0}", parameters.OptKind));
|
||
break;
|
||
}
|
||
|
||
switch (parameters.CardinalityAlgorithm)
|
||
{
|
||
case CardinalityAlgorithm.FuMalik:
|
||
p.Add("maxsat_engine", _context.MkSymbol("fu_malik"));
|
||
break;
|
||
case CardinalityAlgorithm.CoreMaxSAT:
|
||
p.Add("maxsat_engine", _context.MkSymbol("core_maxsat"));
|
||
break;
|
||
default:
|
||
Debug.Assert(false, String.Format("Unknown cardinality algorithm option {0}", parameters.CardinalityAlgorithm));
|
||
break;
|
||
}
|
||
|
||
switch (parameters.PseudoBooleanAlgorithm)
|
||
{
|
||
case PseudoBooleanAlgorithm.WeightedMaxSAT:
|
||
p.Add("wmaxsat_engine", _context.MkSymbol("wmax"));
|
||
break;
|
||
case PseudoBooleanAlgorithm.IterativeWeightedMaxSAT:
|
||
p.Add("wmaxsat_engine", _context.MkSymbol("iwmax"));
|
||
break;
|
||
case PseudoBooleanAlgorithm.BisectionWeightedMaxSAT:
|
||
p.Add("wmaxsat_engine", _context.MkSymbol("bwmax"));
|
||
break;
|
||
case PseudoBooleanAlgorithm.WeightedPartialMaxSAT2:
|
||
p.Add("wmaxsat_engine", _context.MkSymbol("wpm2"));
|
||
break;
|
||
default:
|
||
Debug.Assert(false, String.Format("Unknown pseudo-boolean algorithm option {0}", parameters.PseudoBooleanAlgorithm));
|
||
break;
|
||
}
|
||
|
||
switch (parameters.ArithmeticStrategy)
|
||
{
|
||
case ArithmeticStrategy.Basic:
|
||
p.Add("engine", _context.MkSymbol("basic"));
|
||
break;
|
||
case ArithmeticStrategy.Farkas:
|
||
p.Add("engine", _context.MkSymbol("farkas"));
|
||
break;
|
||
default:
|
||
Debug.Assert(false, String.Format("Unknown arithmetic strategy option {0}", parameters.ArithmeticStrategy));
|
||
break;
|
||
}
|
||
|
||
_optSolver.Parameters = p;
|
||
}
|
||
|
||
internal ArithExpr GetVariable(int vid)
|
||
{
|
||
Expr variable;
|
||
if (!_variables.TryGetValue(vid, out variable))
|
||
{
|
||
AddVariable(vid);
|
||
variable = _variables[vid];
|
||
}
|
||
return (ArithExpr)variable;
|
||
}
|
||
|
||
internal void AssertBool(BoolExpr row)
|
||
{
|
||
_optSolver.Assert(row);
|
||
}
|
||
|
||
internal void AssertArith(int vid, ArithExpr variable)
|
||
{
|
||
// Get the bounds on the row
|
||
Rational lower, upper;
|
||
_model.GetBounds(vid, out lower, out upper);
|
||
|
||
// Case of equality
|
||
if (lower == upper)
|
||
{
|
||
// Create the equality term
|
||
Expr eqConst = GetNumeral(lower, variable.Sort);
|
||
BoolExpr constraint = _context.MkEq(eqConst, variable);
|
||
// Assert the constraint
|
||
_optSolver.Assert(constraint);
|
||
}
|
||
else
|
||
{
|
||
// If upper bound is finite assert the upper bound constraint
|
||
if (lower.IsFinite)
|
||
{
|
||
// Create the lower Bound constraint
|
||
ArithExpr lowerTerm = GetNumeral(lower, variable.Sort);
|
||
BoolExpr constraint = _context.MkLe(lowerTerm, variable);
|
||
// Assert the constraint
|
||
_optSolver.Assert(constraint);
|
||
}
|
||
// If lower bound is finite assert the lower bound constraint
|
||
if (upper.IsFinite)
|
||
{
|
||
// Create the upper bound constraint
|
||
ArithExpr upperTerm = GetNumeral(upper, variable.Sort);
|
||
BoolExpr constraint = _context.MkGe(upperTerm, variable);
|
||
// Assert the constraint
|
||
_optSolver.Assert(constraint);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds a MSF variable with the corresponding assertion to the Z3 variables.
|
||
/// </summary>
|
||
/// <param name="vid">The MSF id of the variable</param>
|
||
internal void AddVariable(int vid)
|
||
{
|
||
// Is the variable an integer
|
||
bool isInteger = _model.GetIntegrality(vid);
|
||
|
||
// Construct the sort we will be using
|
||
Sort sort = isInteger ? (Sort)_context.IntSort : (Sort)_context.RealSort;
|
||
|
||
// Get the variable key
|
||
object key = _model.GetKeyFromIndex(vid);
|
||
|
||
// Try to construct the name
|
||
string name;
|
||
if (key != null) name = String.Format("x_{0}_{1}", key, vid);
|
||
else name = String.Format("x_{0}", vid);
|
||
ArithExpr variable = (ArithExpr)_context.MkConst(name, sort);
|
||
|
||
// Create the variable and add it to the map
|
||
Debug.Assert(!_variables.ContainsKey(vid), "Variable names should be unique.");
|
||
_variables.Add(vid, variable);
|
||
|
||
AssertArith(vid, variable);
|
||
}
|
||
|
||
internal ArithExpr GetNumeral(Rational rational, Sort sort = null)
|
||
{
|
||
return Utils.GetNumeral(_context, rational, sort);
|
||
}
|
||
|
||
internal void Solve(Z3BaseParams parameters, IEnumerable<IGoal> modelGoals,
|
||
Action<int> addRow, Func<int, ArithExpr> mkGoalRow, Action<Z3Result> setResult)
|
||
{
|
||
_variables.Clear();
|
||
_goals.Clear();
|
||
|
||
try
|
||
{
|
||
// Mark that we are in solving phase
|
||
_isSolving = true;
|
||
|
||
// Construct Z3
|
||
ConstructSolver(parameters);
|
||
|
||
// Add all the variables
|
||
foreach (int vid in _model.VariableIndices)
|
||
{
|
||
AddVariable(vid);
|
||
}
|
||
|
||
// Add all the rows
|
||
foreach (int rid in _model.RowIndices)
|
||
{
|
||
addRow(rid);
|
||
}
|
||
|
||
// Add enabled goals to optimization problem
|
||
foreach (IGoal g in modelGoals)
|
||
{
|
||
if (!g.Enabled) continue;
|
||
|
||
ArithExpr gr = mkGoalRow(g.Index);
|
||
if (g.Minimize)
|
||
_goals.Add(g, _optSolver.MkMinimize(gr));
|
||
else
|
||
_goals.Add(g, _optSolver.MkMaximize(gr));
|
||
}
|
||
|
||
if (_goals.Any() && parameters.SMT2LogFile != null)
|
||
{
|
||
Debug.WriteLine("Dumping SMT2 benchmark to log file...");
|
||
File.WriteAllText(parameters.SMT2LogFile, _optSolver.ToString());
|
||
}
|
||
|
||
bool aborted = parameters.QueryAbort();
|
||
|
||
if (!aborted)
|
||
{
|
||
// Start the abort thread
|
||
AbortWorker abortWorker = new AbortWorker(_context, parameters.QueryAbort);
|
||
Thread abortThread = new Thread(abortWorker.Start);
|
||
abortThread.Start();
|
||
|
||
// Now solve the problem
|
||
Status status = _optSolver.Check();
|
||
|
||
// Stop the abort thread
|
||
abortWorker.Stop();
|
||
abortThread.Join();
|
||
|
||
switch (status)
|
||
{
|
||
case Status.SATISFIABLE:
|
||
Microsoft.Z3.Model model = _optSolver.Model;
|
||
Debug.Assert(model != null, "Should be able to get Z3 model.");
|
||
// Remember the solution values
|
||
foreach (KeyValuePair<int, Expr> pair in _variables)
|
||
{
|
||
var value = Utils.ToRational(model.Eval(pair.Value, true));
|
||
_model.SetValue(pair.Key, value);
|
||
}
|
||
// Remember all objective values
|
||
foreach (var pair in _goals)
|
||
{
|
||
var optimalValue = Utils.ToRational(pair.Value.Upper);
|
||
_model.SetValue(pair.Key.Index, optimalValue);
|
||
}
|
||
model.Dispose();
|
||
setResult(_goals.Any() ? Z3Result.Optimal : Z3Result.Feasible);
|
||
break;
|
||
case Status.UNSATISFIABLE:
|
||
setResult(Z3Result.Infeasible);
|
||
break;
|
||
case Status.UNKNOWN:
|
||
if (abortWorker.Aborted)
|
||
{
|
||
Microsoft.Z3.Model subOptimalModel = _optSolver.Model;
|
||
if (subOptimalModel != null && subOptimalModel.NumConsts != 0)
|
||
{
|
||
// Remember the solution values
|
||
foreach (KeyValuePair<int, Expr> pair in _variables)
|
||
{
|
||
var value = Utils.ToRational(subOptimalModel.Eval(pair.Value, true));
|
||
_model.SetValue(pair.Key, value);
|
||
}
|
||
// Remember all objective values
|
||
foreach (var pair in _goals)
|
||
{
|
||
var optimalValue = Utils.ToRational(pair.Value.Upper);
|
||
_model.SetValue(pair.Key.Index, optimalValue);
|
||
}
|
||
subOptimalModel.Dispose();
|
||
|
||
setResult(Z3Result.LocalOptimal);
|
||
}
|
||
else
|
||
setResult(Z3Result.Infeasible);
|
||
}
|
||
else
|
||
setResult(Z3Result.Interrupted);
|
||
break;
|
||
default:
|
||
Debug.Assert(false, "Unrecognized Z3 Status");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
_isSolving = false;
|
||
}
|
||
|
||
// Now kill Z3
|
||
DestructSolver(true);
|
||
}
|
||
}
|
||
}
|