#ifndef __MINIZINC_NL_COMPONENTS_HH__ #define __MINIZINC_NL_COMPONENTS_HH__ //#include #include #include #include #include #include using namespace std; /* A NL File is composed of a header and several segments. Adding items in the nl file is done through adding segment (or adding item in a segment). As for the header, segment are printable. Segment are identified by a prefix, which should be one of (taken from table 13 in 'writing nl files'): F imported function description S suffix values V defined variable definition (must precede V,C,L,O segments where used) (yes, I know, "V must preceed V"...) C algebraic constraint body L logical constraint expression O objective function d dual initial guess x primal initial guess r bounds on algebraic constraint bodies (“ranges”), can only appears once b bounds on variable, can only appears once k Jacobian column counts (must precede all J segments) J Jacobian sparsity, linear terms G Gradient sparsity, linear terms */ namespace MiniZinc { // --- --- --- Tooling /** Exception when translating * Code mostly taken from https://www.softwariness.com/articles/assertions-in-cpp/ */ class NLException : public exception { public: // --- --- --- Fields const char* expression; const char* file; int line; string message; string report; /** Exception constructor. Use with the macro assert/should_not_happen. * If not, WARNING: stream must be a std::ostringstream& * We only use a ostreamé so we can use the standard "<<" operator, which is returning a ostream& */ NLException(const char* expression, const char* file, int line, ostream& stream) : expression(expression), file(file), line(line) { message = static_cast(stream).str(); std::ostringstream outputStream; if (expression == NULL) { outputStream << "Something should not have happen in file '" << file << "' line " << line << ". Message:" << endl; if (!message.empty()) { outputStream << message << endl; } else { outputStream << "No message provided..." << endl; } } else { string expressionString = expression; if (expressionString == "false" || expressionString == "0" || expressionString == "FALSE") { outputStream << "Unreachable code assertion"; } else { outputStream << "Assertion '" << expression << "'"; } outputStream << " failed in file '" << file << "' line " << line << endl; } outputStream << "Note: the NL component is still in development!" << endl; report = outputStream.str(); } /** Exception interface */ virtual const char* what() const noexcept { return report.c_str(); } ~NLException() noexcept = default; }; #ifdef assert #undef assert #endif /** Should not happen macro */ #define should_not_happen(MESSAGE) \ do { \ ostringstream oss; \ oss << MESSAGE; \ throw NLException(NULL, __FILE__, __LINE__, oss); \ } while (false) /* CMake febug build flag: double negation... because... ? */ #ifndef NDEBUG #define DEBUG_MSG(STR) \ do { \ std::cerr << "%[NL DEBUG] " << STR << endl; \ } while (false) #define assert(EXPRESSION) \ do { \ if (!(EXPRESSION)) { \ ostringstream oss; \ throw NLException(#EXPRESSION, __FILE__, __LINE__, oss); \ } \ } while (false) #else #define DEBUG_MSG(STR) \ do { \ } while (false) #define assert(EXPRESSION) \ do { \ } while (false) #endif // --- --- --- Components // Declaration class NLFile; /** A Bound. * A bound can represent various constraint on a variable or a constraint. * Because it apply to both variables and constraints, we keep it general enough. * Note that the targeted variable or constraint is implicitely represented by its position in the final NL File. * As a result, this information does not appear here. * Bounds are used in the 'b' and 'r' segments. # Text # Starting the segment # Variable # Tag in enum NLS_Bounditem::Bound 0 1.1 3.4 # 1.1 =< V =< 3.4 First variable LB_UB 1 2.5 # V =< 2.5 Second variable UB 2 7 # 7 =< V etc... LB 3 # no constraint NONE 4 9.4 # V = 9.4 EQ * Notes: - bound values are stored as 'double', even for integer variables. * - we do not make that class Printable as it is better "printed" with a name for the targeted variable/constraint */ class NLBound { public: /** Bound kind. Declaration matches specification above. */ enum Bound { LB_UB = 0, UB = 1, LB = 2, NONE = 3, EQ = 4 }; /** *** *** *** Fields *** *** *** **/ Bound tag = NONE; double lb = 0; double ub = 0; /** *** *** *** Constructors & helpers *** *** *** **/ NLBound() = default; NLBound(Bound tag, double lb, double ub); static NLBound make_bounded(double lb, double ub); static NLBound make_ub_bounded(double ub); static NLBound make_lb_bounded(double lb); static NLBound make_nobound(); static NLBound make_equal(double val); /** *** *** *** Update the lower or upper bound *** *** *** **/ // Note: this method are "additive only": we cannot use them to remove a a bound. void update_lb(double new_lb); void update_ub(double new_ub); void update_eq(double new_eq); /** *** *** *** Printing Methods *** *** *** **/ /** Print the bound with a comment containing the name of the variable/constraint. */ ostream& print_on(ostream& o, string vname) const; /** Printing with 'body' as the name of the variable/constraint. */ ostream& print_on(ostream& o) const; }; /** A Declared variable. * A variable is identified by its name, which is supposed to be unique in the MZN representation. * In an NL file, variables are identified by their index. However, those index are dependent on * the variable ordering, which can only be known once all variables are known. Hence, the * computation of the index can only be achieved at a later stage. A variable is always associated * to a bound, even if none are specified (See LNBound above)/ */ class NLVar { public: /** Variable name. */ string name; /** Is the variable an integer variable? Else is a floating point variable. */ bool is_integer = false; /** Is this variable flagged to be reported? */ bool to_report = false; /** Is the variable appearing in a nonlinear constraint (including logical constraint, L segment). */ bool is_in_nl_constraint = false; /** Is the variable appearing non linearly in the objective? */ bool is_in_nl_objective = false; /** Number of occurrences in Jacobian. */ long jacobian_count = 0; /** The bound over this variable. * Used when producing the unique 'b' segment of the NL file. */ NLBound bound; /* *** *** *** Constructors *** *** *** */ NLVar() = default; /** Constructor with declare time information */ NLVar(const string& name, bool is_integer, bool to_report, NLBound bound) : name(name), is_integer(is_integer), to_report(to_report), bound(bound) {} /** Copy constructor, with update on bound */ NLVar copy_with_bound(NLBound bound) const; }; /** A NLArray: * We do not use "real" array. * This type only serves when sending the result back to minizinc */ class NLArray { public: /** Array item; if the string is empty, use the value. */ class Item { public: string variable; double value; }; /** Array name */ string name; /** Dimensions part, e.g. array2d( '0..4', '0..5' [ .... ]) */ vector dimensions; /** Related variables */ vector items; /** Is this an array or integers or floats? */ bool is_integer = false; }; /** A token from an 'expression graph'. * An expression graph is express in Polish Prefix Notation: operator followed by operand. * A token represent an operator or an operand. * See the definition of the various enum. */ class NLToken { public: /** Kind of token. */ enum Kind { NUMERIC, // "n42.42" a numeric constant, double VARIABLE, // "v4" reference to a decision variable 0<= i < nb_vars (see header) or a // defined variable for i>=nb_vars STRING, // "h11:some string" Probably unused in our case. FUNCALL, // "f0 3" Call a defined function (index 0, 3 args). Probably unused in // our case. OP, // "o5" An operation defined by its operation code MOP // "o7\n3" Operator with multiple operand. The number of operands (3) is on // the next line ("\n")? }; /** Opcode for operator with a fixed number of operands. */ enum OpCode { OPPLUS = 0, OPMINUS = 1, OPMULT = 2, OPDIV = 3, OPREM = 4, OPPOW = 5, OPLESS = 6, FLOOR = 13, CEIL = 14, ABS = 15, OPUMINUS = 16, OPOR = 20, OPAND = 21, LT = 22, LE = 23, EQ = 24, GE = 28, GT = 29, NE = 30, OPNOT = 34, OPIFnl = 35, OP_tanh = 37, OP_tan = 38, OP_sqrt = 39, OP_sinh = 40, OP_sin = 41, OP_log10 = 42, OP_log = 43, OP_exp = 44, OP_cosh = 45, OP_cos = 46, OP_atanh = 47, OP_atan2 = 48, OP_atan = 49, OP_asinh = 50, OP_asin = 51, OP_acosh = 52, OP_acos = 53, OPintDIV = 55, OPprecision = 56, OPround = 57, OPtrunc = 58, OPATLEAST = 62, OPATMOST = 63, OPPLTERM = 64, OPIFSYM = 65, OPEXACTLY = 66, OPNOTATLEAST = 67, OPNOTATMOST = 68, OPNOTEXACTLY = 69, OPIMPELSE = 72, OP_IFF = 73, OPSOMESAME = 75, OP1POW = 76, OP2POW = 77, OPCPOW = 78, OPFUNCALL = 79, OPNUM = 80, OPHOL = 81, OPVARVAL = 82, N_OPS = 83, }; /** Opcodes for operand taking multiple arguments. */ enum MOpCode { MINLIST = 11, MAXLIST = 12, OPSUMLIST = 54, OPCOUNT = 59, OPNUMBEROF = 60, OPNUMBEROFs = 61, ANDLIST = 70, ORLIST = 71, OPALLDIFF = 74, }; /** Obtain the name of an operator from its opcode. */ static const char* get_name(OpCode oc); /** Obtain the name of an operator (with multiple operands) from its opcode. */ static const char* get_name(MOpCode moc); /* *** *** *** Fields *** *** *** */ Kind kind; double numeric_value; // if kind==NUMERIC int nb_args; // if kind==FUNCALL or kind==MOP string str; // if kind==STRING or kind=VARIABLE (variable name) or kind=FUNCALL (function name) OpCode oc; // if kind==OP MOpCode moc; // if kind==MOP /* *** *** *** Constructor and helpers *** *** *** */ NLToken() = default; static NLToken n(double value); static NLToken v(string vname); static NLToken o(OpCode opc); static NLToken mo(MOpCode mopc, int nb); /* *** *** *** Query *** *** *** */ bool is_variable() const; bool is_constant() const; /* *** *** *** Printable *** *** *** */ ostream& print_on(ostream& o, const NLFile& nl_file) const; }; /** A algebraic constraint. * Contains both a linear and a non linear part. * We do not handle network constraints. */ class NLAlgCons { public: /** Constraint name, also acts as identifier. */ string name; /** Bound on the algebraic constraint. * Used when producing the unique r of the NL file. */ NLBound range; /** Expression graph, used for the non linear part. * Used to produce a new, standalone, C segment. * If the expression graph is empty (linear constraint), produce the expression graph 'n0' */ vector expression_graph = {}; /** Jacobian, used for the linear part. Identify a variable by its name and associate a * coefficent. Used to produce a new, standalone, J segment. */ vector> jacobian = {}; /** Method to build the var_coeff vector. * The NLFile is used to access the variables through their name in order to increase their * jacobian count. */ void set_jacobian(const vector& vnames, const vector& coeffs, NLFile* nl_file); /* *** *** *** Helpers *** *** *** */ /** A constraint is considered linear if the expression_graph is empty. */ bool is_linear() const; /* *** *** *** Printable *** *** *** */ ostream& print_on(ostream& o, const NLFile& nl_file) const; }; /** A logical constraint. * Contains only a non linear part. * We do not handle network constraints. * Logical constraint stands on their own and do not need any identifier. * However, for consistency sake, we still keep their name. */ class NLLogicalCons { public: /** Constraint name, also acts as identifier. */ string name; /** Index */ int index = -1; /** Expression graph, used for the non linear part. * Used to produce a new, standalone, L segment. * If the expression graph is empty (linear constraint), produce the expression graph 'n0' */ vector expression_graph = {}; /* *** *** *** Constructor *** *** *** */ NLLogicalCons(int idx) : index(idx) {} /* *** *** *** Printable *** *** *** */ ostream& print_on(ostream& o, const NLFile& nl_file) const; }; /** The header. */ class NLHeader { public: /* *** *** *** Printable *** *** *** */ ostream& print_on(ostream& o, const NLFile& nl_file) const; }; /** An Objective * In an NL file, we can have several of those. * However, in flatzinc, only one is allowed, so we only have one. * Note that in NL, we do not have a "satisfy" objective, only a minimize or maximize one. * We translate the "satisfy" with "minimize n0". */ class NLObjective { public: enum MinMax { UNDEF = -2, SATISFY = -1, MINIMIZE = 0, MAXIMIZE = 1, }; /* *** *** *** Fields *** *** *** */ MinMax minmax = UNDEF; vector expression_graph = {}; // If empty, produce a 'n0' when printing /* *** *** *** Gradient *** *** *** */ /** Gradient, used for the linear part. Identify a variable by its name and associate a * coefficent. Used to produce a new, standalone, G segment. */ vector> gradient = {}; /** Method to build the var_coeff vector. */ void set_gradient(const vector& vnames, const vector& coeffs); int gradient_count() const; /* *** *** *** Helpers *** *** *** */ bool is_defined() const; bool is_linear() const; bool is_optimisation() const; /* *** *** *** Constructor *** *** *** */ NLObjective() = default; /* *** *** *** Printable *** *** *** */ ostream& print_on(ostream& o, const NLFile& nl_file) const; }; } // namespace MiniZinc #endif