/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ // SPDX-License-Identifier: GPL-3.0 #pragma once //#define DEBUG 1 #include #include #include #include #include #include namespace solidity::util { using rational = boost::rational; using Model = std::map; using ReasonSet = std::set; /** * Constraint of the form * - data[1] * x_1 + data[2] * x_2 + ... <= data[0] (LESS_OR_EQUAL) * - data[1] * x_1 + data[2] * x_2 + ... < data[0] (LESS_THAN) * - data[1] * x_1 + data[2] * x_2 + ... = data[0] (EQUAL) * The set and order of variables is implied. */ struct Constraint { LinearExpression data; enum Kind { EQUAL, LESS_THAN, LESS_OR_EQUAL }; Kind kind = LESS_OR_EQUAL; bool operator<(Constraint const& _other) const; bool operator==(Constraint const& _other) const; }; /** * A two-dimensional rational number "a + b*delta" that can be used to perform strict comparisons: * x > 0 is transformed into x >= 1*delta, where delta is assumed to be "small". Its value * is never explicitly computed / set, it is just a symbolic parameter. */ struct RationalWithDelta { RationalWithDelta(rational _x = {}): m_main(move(_x)) {} static RationalWithDelta delta() { RationalWithDelta x(0); x.m_delta = 1; return x; } RationalWithDelta& operator+=(RationalWithDelta const& _other) { m_main += _other.m_main; m_delta += _other.m_delta; return *this; } RationalWithDelta& operator-=(RationalWithDelta const& _other) { m_main -= _other.m_main; m_delta -= _other.m_delta; return *this; } RationalWithDelta operator-(RationalWithDelta const& _other) const { RationalWithDelta ret = *this; ret -= _other; return ret; } RationalWithDelta& operator*=(rational const& _factor) { m_main *= _factor; m_delta *= _factor; return *this; } RationalWithDelta operator*(rational const& _factor) const { RationalWithDelta ret = *this; ret *= _factor; return ret; } RationalWithDelta& operator/=(rational const& _factor) { m_main /= _factor; m_delta /= _factor; return *this; } RationalWithDelta operator/(rational const& _factor) const { RationalWithDelta ret = *this; ret /= _factor; return ret; } bool operator<=(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) <= std::tie(_other.m_main, _other.m_delta); } bool operator>=(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) >= std::tie(_other.m_main, _other.m_delta); } bool operator<(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) < std::tie(_other.m_main, _other.m_delta); } bool operator>(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) > std::tie(_other.m_main, _other.m_delta); } bool operator==(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) == std::tie(_other.m_main, _other.m_delta); } bool operator!=(RationalWithDelta const& _other) const { return std::tie(m_main, m_delta) != std::tie(_other.m_main, _other.m_delta); } std::string toString() const; rational m_main; rational m_delta; }; } namespace solidity::util { enum class LPResult { Unknown, Unbounded, ///< System has a solution, but it can have an arbitrary objective value. Feasible, ///< System has a solution (it might be unbounded, though). Infeasible ///< System does not have any solution. }; class LPSolver { public: void addConstraint(Constraint const& _constraint); void addLowerBound(size_t _variable, RationalWithDelta _bound); void addUpperBound(size_t _variable, RationalWithDelta _bound); /// Add the conditional constraint but do not activate it yet. void addConditionalConstraint(Constraint const& _constraint, size_t _reason); void activateConstraint(size_t _reason); void setTrailSize(size_t _trailSize); void setVariableName(size_t _variable, std::string _name); std::pair check(); std::string toString() const; std::map model() const; private: struct Bounds { std::optional lower; std::optional upper; }; struct Variable { #ifdef DEBUG std::string name = {}; #endif RationalWithDelta value = {}; Bounds bounds = {}; std::optional lowerReason; std::optional upperReason; }; /// Consumes a constraint and returns a controlling variable (can be a new slack /// but does not need to) and corresponding bounds. /// If it adds a slack variable, updates the factors and properly sets the value /// for the slack variable (which will be a new basic variable). std::pair constraintIntoVariableBounds(Constraint const& _constraint); void addBounds(size_t _variable, Bounds _bounds); std::set collectReasonsForVariable(size_t _variable); bool correctNonbasic(); /// Set value of non-basic variable. void update(size_t _varIndex, RationalWithDelta const& _value); /// @returns the index of the first basic variable violating its bounds. std::optional firstConflictingBasicVariable() const; std::optional firstReplacementVar(size_t _basicVarToReplace, bool _increasing) const; /// @returns the set of reasons in case "firstReplacementVar" failed. std::set reasonsForUnsat(size_t _basicVarToReplace, bool _increasing) const; void pivot(size_t _old, size_t _new); void pivotAndUpdate(size_t _oldBasicVar, RationalWithDelta const& _newValue, size_t _newBasicVar); void addOuterVariable(size_t _outerIndex); /// Adds a new outer variable if it is not known yet and returns the inner index in any case. size_t maybeAddOuterVariable(size_t _outerIndex); size_t addNewVariable(); /// Counter to enable unique names for the slack variables. size_t m_slackVariableCounter = 0; std::optional result = std::nullopt; size_t trailSize = 0; SparseMatrix factors; std::vector variables; /// Stack of (trail size, variable index, bounds, lower reason, upper reason). std::vector, std::optional>> storedBounds; /// Last known satisfying values for variables. std::vector previousGoodValues; std::set variablesPotentiallyOutOfBounds; /// Variable index to constraint it controls. std::map basicVariables; /// Maps outer indices to inner indices. std::map varMapping = {}; /// Mapping from reason (constraint ID) to variable it controls and bounds for it. /// A variable can be controlled by multiple constraints. /// TODO do we want to store the reverse mapping? std::map> reasonToBounds; std::set reasons; }; }