From e7115bbe4de71c7738b9be1c669ef67dee559625 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Fri, 5 Jan 2024 15:34:43 +0700 Subject: [PATCH 1/3] update amplmp source to version 690e9e7 (master by end of 2023) --- src/amplmp/include/mp/basic-expr-visitor.h | 14 +- src/amplmp/include/mp/common.h | 355 +++++++++---------- src/amplmp/include/mp/error.h | 53 ++- src/amplmp/include/mp/format.h | 1 + src/amplmp/include/mp/nl-header-c.h | 339 ++++++++++++++++++ src/amplmp/include/mp/nl-header.h | 177 ++++++++++ src/amplmp/include/mp/nl-reader.h | 377 ++++++++++----------- src/amplmp/include/mp/nl.h | 16 +- src/amplmp/include/mp/os.h | 2 - src/amplmp/include/mp/posix.h | 12 +- src/amplmp/src/expr-writer.h | 4 +- src/amplmp/src/format.cpp | 4 +- src/amplmp/src/nl-reader.cpp | 83 ++++- src/amplmp/src/os.cpp | 3 + src/scip/reader_nl.cpp | 18 +- 15 files changed, 1036 insertions(+), 422 deletions(-) create mode 100644 src/amplmp/include/mp/nl-header-c.h create mode 100644 src/amplmp/include/mp/nl-header.h diff --git a/src/amplmp/include/mp/basic-expr-visitor.h b/src/amplmp/include/mp/basic-expr-visitor.h index d4d6668027..1e75042c04 100644 --- a/src/amplmp/include/mp/basic-expr-visitor.h +++ b/src/amplmp/include/mp/basic-expr-visitor.h @@ -56,14 +56,16 @@ namespace mp { -// A basic expression visitor that can be used with different expression -// hierarchies described by ExprTypes. -// -// BasicExprVisitor uses the curiously recurring template pattern: -// http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern +/// A basic expression visitor that can be used with different expression +/// hierarchies described by ExprTypes. +/// +/// BasicExprVisitor uses the curiously recurring template pattern: +/// http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern template class BasicExprVisitor { public: + virtual ~BasicExprVisitor() { } + MP_DEFINE_EXPR_TYPES(ExprTypes); Result Visit(Expr e); @@ -181,7 +183,7 @@ class BasicExprVisitor { return MP_DISPATCH(VisitUnary(e)); } - // Visits a binary expression or a function taking two arguments. + /// Visits a binary expression or a function taking two arguments. Result VisitBinary(BinaryExpr e) { return MP_DISPATCH(VisitNumeric(e)); } diff --git a/src/amplmp/include/mp/common.h b/src/amplmp/include/mp/common.h index 536c18e8c3..115f116806 100644 --- a/src/amplmp/include/mp/common.h +++ b/src/amplmp/include/mp/common.h @@ -23,9 +23,12 @@ #ifndef MP_COMMON_H_ #define MP_COMMON_H_ -#include "mp/error.h" // for MP_ASSERT +#include #include // for std::size_t +#include "mp/error.h" // for MP_ASSERT +#include "mp/nl-header.h" + /** The mp namespace. */ namespace mp { @@ -64,7 +67,7 @@ class ComplInfo { int flags_; public: - // Flags for the constructor. + /// Flags for the constructor. enum { /** Constraint upper bound is infinity (finite variable lower bound). */ INF_UB = 1, @@ -85,12 +88,13 @@ class ComplInfo { /** Constraint lower bound. */ double con_lb() const { return (flags_ & INF_LB) != 0 ? - -std::numeric_limits::infinity() : 0; + -INFINITY : 0; } /** Constraint upper bound. */ double con_ub() const { - return (flags_ & INF_UB) != 0 ? std::numeric_limits::infinity() : 0; + return (flags_ & INF_UB) != 0 ? + INFINITY : 0; } }; @@ -102,6 +106,11 @@ enum Kind { CON = 1, /**< Applies to constraints. */ OBJ = 2, /**< Applies to objectives. */ PROBLEM = 3 /**< Applies to problems. */ + , + VAR_BIT = 0x80, // for building a bit mask + CON_BIT = 0x100, + OBJ_BIT = 0x200, + PROB_BIT= 0x400 }; // SV disabled following line as not compilable with ancient MSVC 10.0 @@ -116,6 +125,8 @@ enum { }; } + +/// MP low-level stuff namespace internal { enum { SUFFIX_KIND_MASK = 3, // Mask for suffix kinds. @@ -124,36 +135,159 @@ enum { } namespace sol { -/** Solution status. */ + +/** + * Solution status. + * + * For printing solve result codes with the `-!` command-line switch, + * every solution driver should register its solve_result codes + * via BasicSolver::AddSolveResults(). Only non-major codes + * should normally be registered (those with _LAST defined + * are pre-registered automatically.) + * + * The meaning ideally should be the same for existing codes + * (use description from the comment.) For extra codes, use + * passing ranges (e.g., for stopping with a feasible solution + * on a limit, use 400-449), otherwise SPECIFIC+ codes. + */ enum Status { - NOT_CHECKED = -200, - /** Unknown status. */ + /** If not touched. Don't register this code. */ + NOT_SET = -200, + + /** Unknown status. Don't register this code. */ UNKNOWN = -1, /** - An optimal solution found for an optimization problem or a feasible - solution found for a satisfaction problem. + * Solved. + * An optimal solution found for an optimization problem or a feasible + * solution found for a satisfaction problem. + * Codes 0-99. */ SOLVED = 0, + /** End of the 'solved' range. */ + SOLVED_LAST = 99, - /** Solution returned but it can be non-optimal or even infeasible. */ + /** Solved? + Solution candidate returned but error likely. + Codes 100-199. */ UNCERTAIN = 100, + /** End of the 'uncertain' range. */ + UNCERTAIN_LAST = 199, - /** Problem is infeasible. */ - INFEASIBLE = 200, - - /** Problem is unbounded. */ - UNBOUNDED = 300, - - /** Stopped by a limit, e.g. on iterations or time. */ - LIMIT = 400, + /** MP Solution check failed. Codes 150-159. */ + MP_SOLUTION_CHECK = UNCERTAIN + 50, + /** End of the 'mp-solution-check' range. */ + MP_SOLUTION_CHECK_LAST = UNCERTAIN + 59, - /** A solver error. */ + /** Problem is infeasible. Codes 200-299. */ + INFEASIBLE = 200, + /** End of the 'infeasible' range. */ + INFEASIBLE_LAST = 299, + /** Problem is infeasible, IIS computation not attempted. */ + INFEASIBLE_NO_IIS = INFEASIBLE + 1, + /** Problem is infeasible, IIS returned. */ + INFEASIBLE_IIS = INFEASIBLE + 2, + /** Problem is infeasible, IIS finder failed. */ + INFEASIBLE_IIS_FAILED = INFEASIBLE + 3, + + /** Problem is unbounded, feasible solution returned. + Codes 300-349. */ + UNBOUNDED_FEAS = 300, + /** End of the 'unbounded-feas' range. */ + UNBOUNDED_FEAS_LAST = 349, + /** Deprecated. */ + UNBOUNDED = UNBOUNDED_FEAS, + + /** Problem is unbounded, no feasible solution returned. + Codes 350-399. + For undecidedly inf/unb, use LIMIT_INF_UNB. */ + UNBOUNDED_NO_FEAS = 350, + /** End of the 'unbounded-no-feas' range. */ + UNBOUNDED_NO_FEAS_LAST = 399, + + /** Limit. + * Feasible solution, stopped by a limit, e.g., on iterations or Ctrl-C. + * Codes 400-449. */ + LIMIT_FEAS = 400, + /** End of the 'limit_feas' range. */ + LIMIT_FEAS_LAST = 449, + /** Deprecated. */ + LIMIT = LIMIT_FEAS, + /** User interrupt, feasible solution. */ + LIMIT_FEAS_INTERRUPT = LIMIT_FEAS + 1, + /** Time limit, feasible solution. */ + LIMIT_FEAS_TIME = LIMIT_FEAS + 2, + /** Iteration limit, feasible solution. */ + LIMIT_FEAS_ITER = LIMIT_FEAS + 3, + /** Node limit, feasible solution. */ + LIMIT_FEAS_NODES = LIMIT_FEAS + 4, + /** Best obj/bound reached, feasible solution. */ + LIMIT_FEAS_BESTOBJ_BESTBND = LIMIT_FEAS + 5, + /** Best obj reached, feasible solution. */ + LIMIT_FEAS_BESTOBJ = LIMIT_FEAS + 6, + /** Best bound reached, feasible solution. */ + LIMIT_FEAS_BESTBND = LIMIT_FEAS + 7, + /** Solution number bound reached. */ + LIMIT_FEAS_NUMSOLS = LIMIT_FEAS + 8, + /** Work limit reached, feasible solution. */ + LIMIT_FEAS_WORK = LIMIT_FEAS + 9, + /** Soft memory limit reached, feasible solution. */ + LIMIT_FEAS_SOFTMEM = LIMIT_FEAS + 10, + /** Unrecoverable failure, feasible solution found. */ + LIMIT_FEAS_FAILURE = LIMIT_FEAS + 20, + + /** Limit. + Problem is infeasible or unbounded. + Codes 450-469. */ + LIMIT_INF_UNB = LIMIT_FEAS + 50, + /** End of the 'limit inf/unb' range. */ + LIMIT_INF_UNB_LAST = LIMIT_FEAS + 69, + /** Deprecated. */ + INF_OR_UNB = LIMIT_INF_UNB, + + /** Limit. + No feasible solution returned. + Codes 470-499. */ + LIMIT_NO_FEAS = LIMIT_FEAS + 70, + /** End of the 'limit-no-feas' range. */ + LIMIT_NO_FEAS_LAST = LIMIT_FEAS + 99, + /** User interrupt, no feasible solution. */ + LIMIT_NO_FEAS_INTERRUPT = LIMIT_NO_FEAS + 1, + /** Time limit, no feasible solution. */ + LIMIT_NO_FEAS_TIME = LIMIT_NO_FEAS + 2, + /** Iteration limit, no feasible solution. */ + LIMIT_NO_FEAS_ITER = LIMIT_NO_FEAS + 3, + /** Node limit, no feasible solution. */ + LIMIT_NO_FEAS_NODES = LIMIT_NO_FEAS + 4, + /** Objective cutoff, no feasible solution. */ + LIMIT_NO_FEAS_CUTOFF = LIMIT_NO_FEAS + 5, + /** Best bound reached, no feasible solution. */ + LIMIT_NO_FEAS_BESTBND = LIMIT_NO_FEAS + 7, + /** Work limit reached, no feasible solution. */ + LIMIT_NO_FEAS_WORK = LIMIT_NO_FEAS + 9, + /** Soft memory limit reached, no feasible solution. */ + LIMIT_NO_FEAS_SOFTMEM = LIMIT_NO_FEAS + 10, + + /** Failure, without a feasible solution. + Codes 500-999. + With a feasible solution, use LIMIT_FEAS_FAILURE. */ FAILURE = 500, + /** End of the 'failure' range. */ + FAILURE_LAST = 999, + + /** Failure. A numeric issue without a feasible solution. + * With a feasible solution, use UNCERTAIN. */ + NUMERIC = FAILURE + 50, - /** Interrupted by the user. */ - INTERRUPTED = 600 + /** Specific. + * Use for specific codes not fitting in above categories. */ + SPECIFIC = 600, + /** Deprecated. + * Use LIMIT_FEAS_INTERRUPT, LIMIT_NOFEAS_INTERRUPT instead. + */ + INTERRUPTED = SPECIFIC }; + } /** Expression information. */ @@ -365,7 +499,6 @@ enum Kind { */ ATANH, - /** The last unary numeric expression kind. */ LAST_UNARY = ATANH, @@ -874,8 +1007,11 @@ int nl_opcode(expr::Kind kind); } // namespace expr #define MP_CONST_DISPATCH(call) static_cast(this)->call +#define MPCD(call) MP_CONST_DISPATCH(call) #define MP_DISPATCH(call) static_cast(this)->call +#define MPD(call) MP_DISPATCH(call) #define MP_DISPATCH_STATIC(call) Impl::call +#define MPDS(call) MP_DISPATCH_STATIC(call) namespace internal { @@ -909,7 +1045,7 @@ class ExprInfo { const char *str; }; -// Maximum NL opcode. +/// Maximum NL opcode. enum { MAX_OPCODE = 82 }; class OpCodeInfo { @@ -939,177 +1075,9 @@ inline const char *expr::str(expr::Kind kind) { return internal::ExprInfo::INFO[kind].str; } -/** Information about an optimization problem. */ -struct ProblemInfo { - /** Total number of variables. */ - int num_vars; - - /** - Number of algebraic constraints including ranges and equality constraints. - It doesn't include logical constraints. - */ - int num_algebraic_cons; - - /** Total number of objectives. */ - int num_objs; - - /** Number of ranges (constraints with -Infinity < LHS < RHS < Infinity). */ - int num_ranges; - /** - Number of equality constraints or -1 if unknown (AMPL prior to 19970627). - */ - int num_eqns; - - /** Number of logical constraints. */ - int num_logical_cons; - - /** Returns the number of integer variables (includes binary variable). */ - int num_integer_vars() const { - return num_linear_binary_vars + num_linear_integer_vars + - num_nl_integer_vars_in_both + num_nl_integer_vars_in_cons + - num_nl_integer_vars_in_objs; - } - - /** Returns the number of continuous variables. */ - int num_continuous_vars() const { return num_vars - num_integer_vars(); } - - // Nonlinear and complementarity information - // ----------------------------------------- - - /** Total number of nonlinear constraints. */ - int num_nl_cons; - - /** Total number of nonlinear objectives. */ - int num_nl_objs; - - /** Total number of complementarity conditions. */ - int num_compl_conds; - - /** Number of nonlinear complementarity conditions. */ - int num_nl_compl_conds; - - /** Number of complementarities involving double inequalities. */ - int num_compl_dbl_ineqs; - - /** Number of complemented variables with a nonzero lower bound. */ - int num_compl_vars_with_nz_lb; - - // Information about network constraints - // ------------------------------------- - - /** Number of nonlinear network constraints. */ - int num_nl_net_cons; - - /** Number of linear network constraints. */ - int num_linear_net_cons; - - // Information about nonlinear variables - // ------------------------------------- - - /** - Number of nonlinear variables in constraints including nonlinear - variables in both constraints and objectives. - */ - int num_nl_vars_in_cons; - - /** - Number of nonlinear variables in objectives including nonlinear - variables in both constraints and objectives. - */ - int num_nl_vars_in_objs; - - /** Number of nonlinear variables in both constraints and objectives. */ - int num_nl_vars_in_both; - - // Miscellaneous - // ------------- - - /** Number of linear network variables (arcs). */ - int num_linear_net_vars; - - /** Number of functions. */ - int num_funcs; - - // Information about discrete variables - // ------------------------------------ - - /** Number of linear binary variables. */ - int num_linear_binary_vars; - - /** Number of linear non-binary integer variables. */ - int num_linear_integer_vars; - - /** - Number of integer nonlinear variables in both constraints and objectives. - */ - int num_nl_integer_vars_in_both; - - /** Number of integer nonlinear variables just in constraints. */ - int num_nl_integer_vars_in_cons; - - /** Number of integer nonlinear variables just in objectives. */ - int num_nl_integer_vars_in_objs; - - // Information about nonzeros - // -------------------------- - - /** Number of nonzeros in constraints' Jacobian. */ - std::size_t num_con_nonzeros; - - /** Number of nonzeros in all objective gradients. */ - std::size_t num_obj_nonzeros; - - // Information about names - // ----------------------- - - /** Length of longest constraint name if names are available. */ - int max_con_name_len; - - /** Length of longest variable name if names are available. */ - int max_var_name_len; - - // Information about common expressions - // ------------------------------------ - - /** - Number of common expressions that appear both in constraints - and objectives. - */ - int num_common_exprs_in_both; - - /** - Number of common expressions that appear in multiple constraints - and don't appear in objectives. - */ - int num_common_exprs_in_cons; - - /** - Number of common expressions that appear in multiple objective - and don't appear in constraints. - */ - int num_common_exprs_in_objs; - - /** - Number of common expressions that only appear in a single constraint - and don't appear in objectives. - */ - int num_common_exprs_in_single_cons; - - /** - Number of common expressions that only appear in a single objective - and don't appear in constraints. - */ - int num_common_exprs_in_single_objs; - - /** Returns the total number of common expressions. */ - int num_common_exprs() const { - return num_common_exprs_in_both + num_common_exprs_in_cons + - num_common_exprs_in_objs + num_common_exprs_in_single_cons + - num_common_exprs_in_single_objs; - } -}; /* SV disabled enum classes as not compilable with ancient MSVC 10.0 +/// .iis suffix values enum class IISStatus { non = 0, low = 1, @@ -1122,8 +1090,9 @@ enum class IISStatus { bug = 8 }; +/// Basic status values (suffix .sstatus) enum class BasicStatus { - none= 0, + none= 0, // 'not set' bas = 1, sup = 2, low = 3, diff --git a/src/amplmp/include/mp/error.h b/src/amplmp/include/mp/error.h index 11df79b1ca..82930c3f62 100644 --- a/src/amplmp/include/mp/error.h +++ b/src/amplmp/include/mp/error.h @@ -23,18 +23,42 @@ #ifndef MP_ERROR_H_ #define MP_ERROR_H_ +#include + #include "mp/format.h" namespace mp { #ifndef MP_ASSERT -# define MP_ASSERT(condition, message) assert((condition) && message) + /// Debug assert + #define MP_ASSERT(condition, message) \ + assert((condition) && message) #endif -#define MP_RAISE(msg) throw std::runtime_error(msg) -#define MP_WARNING(msg) Print(msg) -// A general error. +/// Assert even for Release +#define MP_ASSERT_ALWAYS(condition, message) \ + do { if (!(condition)) MP_RAISE(message); } while(0) + +/// Use this to raise UnsupportedError +#define MP_UNSUPPORTED(name) \ + throw MakeUnsupportedError( name ) + +/// Raise error +#define MP_RAISE(msg) throw mp::Error(msg) + +/// Raise with exit code +#define MP_RAISE_WITH_CODE(exit_code, msg) \ + throw mp::Error(msg, exit_code) + +/// Raise infeasibility +#define MP_INFEAS(msg) \ + MP_RAISE_WITH_CODE(200, std::string("Model infeasible: ") + msg) + +/// Silence unused parameter warnings +#define MP_UNUSED(x) (void)(x) +/// A general error. class Error : public fmt::internal::RuntimeError { + int exit_code_ = EXIT_FAILURE; protected: Error() {} @@ -48,22 +72,37 @@ class Error : public fmt::internal::RuntimeError { } public: + /// Costruct from message formatting arguments? FMT_VARIADIC_(char, , Error, init, fmt::CStringRef) - ~Error() throw() {} + + /// Construct from message and optional exit code + Error(fmt::CStringRef msg, int c=-1) : exit_code_(c) + { SetMessage(msg.c_str()); } + + /// The exit code + int exit_code() const { return exit_code_; } }; -// The operation is not supported by the object. +/// The operation is not supported by the object. class UnsupportedError : public Error { public: FMT_VARIADIC_(char, , UnsupportedError, init, fmt::CStringRef) }; -// Makes UnsupportedError with prefix "unsupported: ". +/// Makes UnsupportedError with prefix "unsupported: ". +/// Use via macro MP_UNSUPPORTED inline UnsupportedError MakeUnsupportedError( fmt::CStringRef format_str, fmt::ArgList args) { return UnsupportedError("unsupported: {}", fmt::format(format_str, args)); } FMT_VARIADIC(UnsupportedError, MakeUnsupportedError, fmt::CStringRef) + +/// An option error +class OptionError : public Error { +public: + explicit OptionError(fmt::CStringRef message) : Error(message) {} +}; + } // namespace mp #endif // MP_ERROR_H_ diff --git a/src/amplmp/include/mp/format.h b/src/amplmp/include/mp/format.h index dbf20741a9..7bec3a197a 100644 --- a/src/amplmp/include/mp/format.h +++ b/src/amplmp/include/mp/format.h @@ -464,6 +464,7 @@ class BasicStringRef { operator std::basic_string() const { return std::basic_string(data_, size_); } + /** Returns a pointer to the string data. */ const Char *data() const { return data_; } diff --git a/src/amplmp/include/mp/nl-header-c.h b/src/amplmp/include/mp/nl-header-c.h new file mode 100644 index 0000000000..01d61d1215 --- /dev/null +++ b/src/amplmp/include/mp/nl-header-c.h @@ -0,0 +1,339 @@ +/** + * Base C structs for NL header. + * + * Used both in C and C++. + * + */ +#ifndef NLHEADERC_H +#define NLHEADERC_H + +#include + +#ifdef __cplusplus // Can be used from C++ +extern "C" { +#endif + + +/** Information about an optimization problem. + * Full documentation on the NL format: + * technical report "Writing .nl Files" + * (https://ampl.github.io/nlwrite.pdf.) + */ +typedef struct NLProblemInfo_C { + /** Total number of variables. */ + int num_vars; + + /** + Number of algebraic constraints including ranges and equality constraints. + It doesn't include logical constraints. + */ + int num_algebraic_cons; + + /** Total number of objectives. */ + int num_objs; + + /** Number of ranges (constraints with -Infinity < LHS < RHS < Infinity). */ + int num_ranges; + + /** + Number of equality constraints or -1 if unknown (AMPL prior to 19970627). + */ + int num_eqns; + + /** Number of logical constraints. */ + int num_logical_cons; + + /** Number of random variables. */ + int num_rand_vars; + + /** Number of random defined variables. */ + int num_rand_common_exprs; + + /** Number of random constraints. */ + int num_rand_cons; + + /** Number of random objectives. */ + int num_rand_objs; + + /** Number of random calls. */ + int num_rand_calls; + + /** Number of stages. */ + int num_stages; + + // Nonlinear and complementarity information + // ----------------------------------------- + + /** Total number of nonlinear constraints. */ + int num_nl_cons; + + /** Total number of nonlinear objectives. */ + int num_nl_objs; + + /** Total number of complementarity conditions. */ + int num_compl_conds; + + /** Number of nonlinear complementarity conditions. */ + int num_nl_compl_conds; + + /** Number of complementarities involving double inequalities. */ + int num_compl_dbl_ineqs; + + /** Number of complemented variables with a nonzero lower bound. */ + int num_compl_vars_with_nz_lb; + + // Information about network constraints + // ------------------------------------- + + /** Number of nonlinear network constraints. */ + int num_nl_net_cons; + + /** Number of linear network constraints. */ + int num_linear_net_cons; + + // Information about nonlinear variables + // ------------------------------------- + + /** + Number of nonlinear variables in constraints including nonlinear + variables in both constraints and objectives. + */ + int num_nl_vars_in_cons; + + /** + Number of nonlinear variables in objectives including nonlinear + variables in both constraints and objectives. + */ + int num_nl_vars_in_objs; + + /** Number of nonlinear variables in both constraints and objectives. */ + int num_nl_vars_in_both; + + // Miscellaneous + // ------------- + + /** Number of linear network variables (arcs). */ + int num_linear_net_vars; + + /** Number of functions. */ + int num_funcs; + + // Information about discrete variables + // ------------------------------------ + + /** Number of linear binary variables. */ + int num_linear_binary_vars; + + /** Number of linear non-binary integer variables. */ + int num_linear_integer_vars; + + /** + Number of integer nonlinear variables in both constraints and objectives. + */ + int num_nl_integer_vars_in_both; + + /** Number of integer nonlinear variables just in constraints. */ + int num_nl_integer_vars_in_cons; + + /** Number of integer nonlinear variables just in objectives. */ + int num_nl_integer_vars_in_objs; + + // Information about nonzeros + // -------------------------- + + /** Number of nonzeros in constraints' Jacobian. */ + size_t num_con_nonzeros; + + /** Number of nonzeros in all objective gradients. */ + size_t num_obj_nonzeros; + + // Information about names. + // Does not have to be filled for NLWriter2. + // ----------------------- + + /** Length of longest constraint or objective name + * if names are available. */ + int max_con_name_len; + + /** Length of longest variable name if names are available. */ + int max_var_name_len; + + // Information about common expressions + // ------------------------------------ + + /** + Number of common expressions that appear both in constraints + and objectives. + */ + int num_common_exprs_in_both; + + /** + Number of common expressions that appear in multiple constraints + and don't appear in objectives. + */ + int num_common_exprs_in_cons; + + /** + Number of common expressions that appear in multiple objectives + and don't appear in constraints. + */ + int num_common_exprs_in_objs; + + /** + Number of common expressions that only appear in a single constraint + and don't appear in objectives. + */ + int num_common_exprs_in_single_cons; + + /** + Number of common expressions that only appear in a single objective + and don't appear in constraints. + */ + int num_common_exprs_in_single_objs; + +} NLProblemInfo_C; + + +enum { + /** Maximum number of options reserved for AMPL use in NL and SOL formats. */ + MAX_AMPL_OPTIONS = 9 +}; + + +enum { + VBTOL_OPTION_INDEX = 1, + USE_VBTOL_FLAG = 3 +}; + + +/** Floating-point arithmetic kind. */ +typedef enum NLArithKind { + + /** Unknown floating-point arithmetic. */ + NL_ARITH_UNKNOWN = 0, + + /** + \rst + Standard `IEEE-754 floating point + `_ - little endian. + \endrst + */ + NL_ARITH_IEEE_LITTLE_ENDIAN = 1, + + /** Standard IEEE-754 floating point - big endian. */ + NL_ARITH_IEEE_BIG_ENDIAN = 2, + + /** + \rst + `IBM floating point + `_. + \endrst + */ + NL_ARITH_IBM = 3, + + /** VAX floating point (legacy). */ + NL_ARITH_VAX = 4, + + /** Cray floating point. */ + NL_ARITH_CRAY = 5, + + /** Last floating point. */ + NL_ARITH_LAST = NL_ARITH_CRAY +} NLArithKind; + + +/** Input/output format */ +typedef int NLFormat; + +enum { + /** + Text format. The text format is fully portable meaning that an .nl file + can be written on a machine of one architecture and then read on a + machine of a different architecture. + */ + NL_FORMAT_TEXT = 0, + + /** + Binary format. The binary format is not generally portable and should + normally be used on a single machine. + */ + NL_FORMAT_BINARY = 1 +}; + + +/** NL Flags. */ +enum { + /** Flag that specifies whether to write output suffixes to a .sol file. */ + WANT_OUTPUT_SUFFIXES = 1 +}; + + +/** + * NL technical info + */ +typedef struct NLInfo_C { + + /** Input/output format. */ + NLFormat format; + + /** The number of options reserved for AMPL use. */ + int num_ampl_options; + + /** + Values of options reserved for AMPL use. + Leave the default values if not using AMPL. + */ + long ampl_options[MAX_AMPL_OPTIONS]; + + /** + Extra info for writing a solution reserved for AMPL use. + Leave the default value if not using AMPL. + */ + double ampl_vbtol; + + /** + * Problem name. + */ + const char* prob_name; + + /** + \rst + Floating-point arithmetic kind used with binary format to check + if an .nl file is written using a compatible representation of + floating-point numbers. It is not used with the text format and normally + set to `NL_ARITH_UNKNOWN` there. + \endrst + */ + int arith_kind; + + /** + \rst + Flags. Can be either 0 or `mp::NLHeader::WANT_OUTPUT_SUFFIXES`. + \endrst + */ + int flags; +} NLInfo_C; + + +/** + \rst + An NL `header `_ + which contains information about problem dimensions, such as the number of + variables and constraints, and the input format. + \endrst + */ +typedef struct NLHeader_C { + NLProblemInfo_C pi; + NLInfo_C nli; +} NLHeader_C; + + +/// Default NLHeader_C +NLHeader_C MakeNLHeader_C_Default(void); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // NLHEADERC_H diff --git a/src/amplmp/include/mp/nl-header.h b/src/amplmp/include/mp/nl-header.h new file mode 100644 index 0000000000..9be218c408 --- /dev/null +++ b/src/amplmp/include/mp/nl-header.h @@ -0,0 +1,177 @@ +/** + Common definitions for NL reader and NL writer. + + Copyright (C) 2014 - 2023 AMPL Optimization Inc. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that the copyright notice and this permission notice and warranty + disclaimer appear in supporting documentation. + + The author and AMPL Optimization Inc disclaim all warranties with + regard to this software, including all implied warranties of + merchantability and fitness. In no event shall the author be liable + for any special, indirect or consequential damages or any damages + whatsoever resulting from loss of use, data or profits, whether in an + action of contract, negligence or other tortious action, arising out + of or in connection with the use or performance of this software. + + Authors: Victor Zverovich, Gleb Belov + */ + +#ifndef NLHEADER_H +#define NLHEADER_H + +#include +#include +#include +#include +#include + +#include "mp/nl-header-c.h" + +/// Namespace mp. +namespace mp { + + +/** Information about an optimization problem. + * Full documentation on the NL format: + * technical report "Writing .nl Files" + * (https://ampl.github.io/nlwrite.pdf.) + */ +struct NLProblemInfo : NLProblemInfo_C { + /// Construct default + NLProblemInfo() { + std::memset(this, 0, sizeof(*this)); + } + + /** Returns the number of integer variables (includes binary variables). */ + int num_integer_vars() const { + return num_linear_binary_vars + num_linear_integer_vars + + num_nl_integer_vars_in_both + num_nl_integer_vars_in_cons + + num_nl_integer_vars_in_objs; + } + + /** Returns the number of continuous variables. */ + int num_continuous_vars() const { return num_vars - num_integer_vars(); } + + /** Returns the total number of common expressions. */ + int num_common_exprs() const { + return num_common_exprs_in_both + num_common_exprs_in_cons + + num_common_exprs_in_objs + num_common_exprs_in_single_cons + + num_common_exprs_in_single_objs; + } +}; + + +enum { + /** Maximum number of options reserved for AMPL use in NL and SOL formats. */ + MAX_AMPL_OPTIONS = 9 +}; + +enum { + /// internal constant + VBTOL_OPTION_INDEX = 1, + /// internal constant + USE_VBTOL_FLAG = 3 +}; + +/// Namespace arith +namespace arith { + +/** Floating-point arithmetic kind. */ +enum Kind { + + /** Unknown floating-point arithmetic. */ + UNKNOWN = NL_ARITH_UNKNOWN, + + /** + \rst + Standard `IEEE-754 floating point + `_ - little endian. + \endrst + */ + IEEE_LITTLE_ENDIAN = NL_ARITH_IEEE_LITTLE_ENDIAN, + + /** Standard IEEE-754 floating point - big endian. */ + IEEE_BIG_ENDIAN = NL_ARITH_IEEE_BIG_ENDIAN, + + /** + \rst + `IBM floating point + `_. + \endrst + */ + IBM = NL_ARITH_IBM, + + /** VAX floating point (legacy). */ + VAX = NL_ARITH_VAX, + + /** Cray floating point. */ + CRAY = NL_ARITH_CRAY, + + /** Last floating point. */ + LAST = NL_ARITH_LAST +}; + +/// Returns floating-point arithmetic kind used on the current system. +Kind GetKind(); + +/// IsIEEE +inline bool IsIEEE(arith::Kind k) { + return k == IEEE_LITTLE_ENDIAN || k == IEEE_BIG_ENDIAN; +} +} // namespace arith + + +/// NL info +struct NLInfo : NLInfo_C { + + /** Input/output format */ + using Format = NLFormat; + + enum { + /** + Text format. The text format is fully portable meaning that an .nl file + can be written on a machine of one architecture and then read on a + machine of a different architecture. + */ + TEXT = NL_FORMAT_TEXT, + + /** + Binary format. The binary format is not generally portable and should + normally be used on a single machine. + */ + BINARY = NL_FORMAT_BINARY + }; + + /// Construct default + NLInfo() { + format = BINARY; + prob_name = "nl_instance"; + num_ampl_options = 3; + ampl_vbtol = 0.0; + arith_kind = NL_ARITH_IEEE_LITTLE_ENDIAN; + flags = WANT_OUTPUT_SUFFIXES; + std::fill(ampl_options, ampl_options + MAX_AMPL_OPTIONS, 0); + std::array opt_default {1, 1, 0}; + std::copy(opt_default.begin(), opt_default.end(), ampl_options); + } +}; + +/** + \rst + An NL `header `_ + which contains information about problem dimensions, such as the number of + variables and constraints, and the input format. + + Base class: `mp::ProblemInfo` + \endrst + */ +struct NLHeader : NLProblemInfo, NLInfo { +}; + +} // namespace mp + +#endif // NLHEADER_H diff --git a/src/amplmp/include/mp/nl-reader.h b/src/amplmp/include/mp/nl-reader.h index 74e719c1ab..d914de5e87 100644 --- a/src/amplmp/include/mp/nl-reader.h +++ b/src/amplmp/include/mp/nl-reader.h @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include namespace mp { @@ -110,133 +110,11 @@ class BinaryReadError : public Error { std::size_t offset() const { return offset_; } }; -enum { - /** Maximum number of options reserved for AMPL use in NL and SOL formats. */ - MAX_AMPL_OPTIONS = 9 -}; - -namespace arith { - -/** Floating-point arithmetic kind. */ -enum Kind { - - /** Unknown floating-point arithmetic. */ - UNKNOWN = 0, - - /** - \rst - Standard `IEEE-754 floating point - `_ - little endian. - \endrst - */ - IEEE_LITTLE_ENDIAN = 1, - - /** Standard IEEE-754 floating point - big endian. */ - IEEE_BIG_ENDIAN = 2, - - /** - \rst - `IBM floating point - `_. - \endrst - */ - IBM = 3, - - /** VAX floating point (legacy). */ - VAX = 4, - - /** Cray floating point. */ - CRAY = 5, - - /** Last floating point. */ - LAST = CRAY -}; - -// Returns floating-point arithmetic kind used on the current system. -Kind GetKind(); - -inline bool IsIEEE(arith::Kind k) { - return k == IEEE_LITTLE_ENDIAN || k == IEEE_BIG_ENDIAN; -} -} // namespace arith - -/** - \rst - An NL `header `_ - which contains information about problem dimensions, such as the number of - variables and constraints, and the input format. - - Base class: `mp::ProblemInfo` - \endrst - */ -struct NLHeader : ProblemInfo { - /** Input/output format */ - enum Format { - /** - Text format. The text format is fully portable meaning that an .nl file - can be written on a machine of one architecture and then read on a - machine of a different architecture. - */ - TEXT = 0, - - /** - Binary format. The binary format is not generally portable and should - normally be used on a single machine. - */ - BINARY = 1 - }; - - /** Input/output format. */ - Format format; - - /** The number of options reserved for AMPL use. */ - int num_ampl_options; - - /** - Values of options reserved for AMPL use. Leave the default values if not - using AMPL. - */ - int ampl_options[MAX_AMPL_OPTIONS]; - - /** - Extra info for writing a solution reserved for AMPL use. Leave the default - value if not using AMPL. - */ - double ampl_vbtol; - - /** - \rst - Floating-point arithmetic kind used with binary format to check - if an .nl file is written using a compatible representation of - floating-point numbers. It is not used with the text format and normally - set to `mp::arith::UNKNOWN` there. - \endrst - */ - arith::Kind arith_kind; - - /** Flags. */ - enum { - /** Flag that specifies whether to write output suffixes to a .sol file. */ - WANT_OUTPUT_SUFFIXES = 1 - }; - - /** - \rst - Flags. Can be either 0 or `mp::NLHeader::WANT_OUTPUT_SUFFIXES`. - \endrst - */ - int flags; - - NLHeader() - : ProblemInfo(), format(TEXT), num_ampl_options(0), ampl_vbtol(0), - arith_kind(arith::UNKNOWN), flags(0) { - std::fill(ampl_options, ampl_options + MAX_AMPL_OPTIONS, 0); - } -}; /** Writes NLHeader in the NL format. */ fmt::Writer &operator<<(fmt::Writer &w, const NLHeader &h); + /** \rst An NL handler. @@ -1064,8 +942,8 @@ class TextReader : public ReaderBase { }; CopyableLocale locale_; - // Reads an integer without a sign. - // Int: signed or unsigned integer type. + /// Reads an integer without a sign. + /// Int: signed or unsigned integer type. template bool ReadIntWithoutSign(Int& value) { char c = *ptr_; @@ -1104,8 +982,8 @@ class TextReader : public ReaderBase { return true; } - // Reads a nonnegative integer and checks that adding it to accumulator - // doesn't overflow. + /// Reads a nonnegative integer and checks that adding it to accumulator + /// doesn't overflow. int ReadUInt(int &accumulator) { int value = ReadUInt(); if (accumulator > std::numeric_limits::max() - value) @@ -1187,22 +1065,22 @@ class TextReader : public ReaderBase { fmt::StringRef ReadString(); - // Reads a function or suffix name. + /// Reads a function or suffix name. fmt::StringRef ReadName(); - // Reads an .nl file header. The header is always in text format, so this - // function doesn't have a counterpart in BinaryReader. + /// Reads an .nl file header. The header is always in text format, so this + /// function doesn't have a counterpart in BinaryReader. void ReadHeader(NLHeader &header); }; -// Converter that doesn't change the input. +/// Converter that doesn't change the input. class IdentityConverter { public: template T Convert(T value) { return value; } }; -// Converter that changes the input endianness. +/// Converter that changes the input endianness. class EndiannessConverter { private: void Convert(char *data, std::size_t size) { @@ -1221,7 +1099,7 @@ class BinaryReaderBase : public ReaderBase { protected: explicit BinaryReaderBase(const ReaderBase &base) : ReaderBase(base) {} - // Reads length chars. + /// Reads length chars. const char *Read(int length) { if (end_ - ptr_ < length) { token_ = end_; @@ -1241,7 +1119,7 @@ class BinaryReaderBase : public ReaderBase { } }; -// Binary reader. +/// Binary reader. template class BinaryReader : private InputConverter, public BinaryReaderBase { public: @@ -1274,12 +1152,12 @@ class BinaryReader : private InputConverter, public BinaryReaderBase { return fmt::StringRef(length != 0 ? Read(length) : 0, length); } - // Reads a function or suffix name. + /// Reads a function or suffix name. fmt::StringRef ReadName() { return ReadString(); } }; -// An NLHandler that forwards notification of variable bounds to another -// handler and ignores all other notifications. +/// An NLHandler that forwards notification of variable bounds to another +/// handler and ignores all other notifications. template class VarBoundHandler : public NullNLHandler { private: @@ -1293,14 +1171,15 @@ class VarBoundHandler : public NullNLHandler { } }; -// Linear expression handler that ignores input. +/// Linear expression handler that ignores input. struct NullLinearExprHandler { void AddTerm(int, double) {} }; -// An NL reader. -// Handler: a class implementing the NLHandler concept that receives -// notifications of NL constructs + +/// An NL reader. +/// Handler: a class implementing the NLHandler concept that receives +/// notifications of NL constructs template class NLReader { private: @@ -1318,8 +1197,8 @@ class NLReader { double ReadConstant(char code); double ReadConstant() { return ReadConstant(reader_.ReadChar()); } - // Reads a nonnegative integer and checks that it is less than ub. - // ub is unsigned so that it can hold value INT_MAX + 1u. + /// Reads a nonnegative integer and checks that it is less than ub. + /// ub is unsigned so that it can hold value INT_MAX + 1u. int ReadUInt(unsigned ub) { int value = reader_.ReadUInt(); unsigned unsigned_value = value; @@ -1328,7 +1207,7 @@ class NLReader { return value; } - // Reads a nonnegative integer and checks that it is in the range [lb, ub). + /// Reads a nonnegative integer and checks that it is in the range [lb, ub). int ReadUInt(unsigned lb, unsigned ub) { int value = reader_.ReadUInt(); unsigned unsigned_value = value; @@ -1337,8 +1216,8 @@ class NLReader { return value; } - // Minimum number of arguments for an iterated expression that has a - // binary counterpart. Examples: sum (+), forall (&&), exists (||). + /// Minimum number of arguments for an iterated expression that has a + /// binary counterpart. Examples: sum (+), forall (&&), exists (||). enum {MIN_ITER_ARGS = 3}; int ReadNumArgs(int min_args = MIN_ITER_ARGS) { @@ -1348,7 +1227,7 @@ class NLReader { return num_args; } - // Reads a variable or a common expression reference. + /// Reads a variable or a common expression reference. Reference DoReadReference() { int index = ReadUInt(num_vars_and_exprs_); reader_.ReadTillEndOfLine(); @@ -1357,7 +1236,7 @@ class NLReader { handler_.OnCommonExprRef(index - header_.num_vars); } - // Reads a variable or a common expression reference. + /// Reads a variable or a common expression reference. Reference ReadReference() { if (reader_.ReadChar() != 'v') reader_.ReportError("expected reference"); @@ -1392,9 +1271,9 @@ class NLReader { return handler_.EndCount(args); } - // Helper structs to provide a uniform interface to Read{Numeric,Logical}Expr - // since it is not possible to overload on expression type as NumericExpr - // and LogicalExpr can be the same type. + /// Helper structs to provide a uniform interface to Read{Numeric,Logical}Expr + /// since it is not possible to overload on expression type as NumericExpr + /// and LogicalExpr can be the same type. struct NumericExprReader { typedef NumericExpr Expr; Expr Read(NLReader &r) const { return r.ReadNumericExpr(); } @@ -1408,11 +1287,11 @@ class NLReader { Expr Read(NLReader &r) const { return r.ReadSymbolicExpr(); } }; - // A helper struct used to make sure that the arguments to a binary - // expression are read in the correct order and avoid errors of the form: - // MakeBinary(opcode, ReadNumericExpr(), ReadNumericExpr()) - // The above code is incorrect as the order of evaluation of arguments is - // unspecified. + /// A helper struct used to make sure that the arguments to a binary + /// expression are read in the correct order and avoid errors of the form: + /// MakeBinary(opcode, ReadNumericExpr(), ReadNumericExpr()) + /// The above code is incorrect as the order of evaluation of arguments is + /// unspecified. template struct BinaryArgReader { typename ExprReader::Expr lhs; @@ -1421,18 +1300,18 @@ class NLReader { : lhs(ExprReader().Read(r)), rhs(ExprReader().Read(r)) {} }; - // Reads a numeric or string expression. + /// Reads a numeric or string expression. Expr ReadSymbolicExpr(); - // Reads a numeric expression. - // ignore_zero: if true, zero constants are ignored + /// Reads a numeric expression. + /// ignore_zero: if true, zero constants are ignored NumericExpr ReadNumericExpr(bool ignore_zero = false) { return ReadNumericExpr(reader_.ReadChar(), ignore_zero); } NumericExpr ReadNumericExpr(char code, bool ignore_zero); NumericExpr ReadNumericExpr(int opcode); - // Reads a logical expression. + /// Reads a logical expression. LogicalExpr ReadLogicalExpr(); LogicalExpr ReadLogicalExpr(int opcode); @@ -1466,7 +1345,7 @@ class NLReader { int num_items() const { return this->reader_.header_.num_objs; } - // Returns true if objective expression should be skipped. + /// Returns true if objective expression should be skipped. bool SkipExpr(int obj_index) const { return !this->reader_.handler_.NeedObj(obj_index); } @@ -1489,23 +1368,23 @@ class NLReader { struct ProblemHandler : ItemHandler { explicit ProblemHandler(NLReader &r) : ItemHandler(r) {} - // An NL input always contains one problem. + /// An NL input always contains one problem. int num_items() const { return 1; } }; - // Reads the linear part of an objective or constraint expression. + /// Reads the linear part of an objective or constraint expression. template void ReadLinearExpr(); template void ReadLinearExpr(int num_terms, LinearHandler linear_expr); - // Reads column sizes, numbers of nonzeros in the first num_var − 1 - // columns of the Jacobian sparsity matrix. + /// Reads column sizes, numbers of nonzeros in the first num_var − 1 + /// columns of the Jacobian sparsity matrix. template void ReadColumnSizes(); - // Reads initial values for primal or dual variables. + /// Reads initial values for primal or dual variables. template void ReadInitialValues(); @@ -1535,13 +1414,13 @@ class NLReader { : reader_(reader), header_(header), handler_(handler), flags_(flags), num_vars_and_exprs_(0) {} - // Algebraic constraint handler. + /// Algebraic constraint handler. struct AlgebraicConHandler : ItemHandler { explicit AlgebraicConHandler(NLReader &r) : ItemHandler(r) {} int num_items() const { return this->reader_.header_.num_algebraic_cons; } - // Returns false because constraint expressions are always read. + /// Returns false because constraint expressions are always read. bool SkipExpr(int) const { return false; } typename Handler::LinearConHandler OnLinearExpr(int index, int num_terms) { @@ -1556,11 +1435,11 @@ class NLReader { } }; - // Reads variable or constraint bounds. + /// Reads variable or constraint bounds. template void ReadBounds(); - // bound_reader: a reader after variable bounds section input + /// bound_reader: a reader after variable bounds section input void Read(Reader *bound_reader); void Read(); @@ -1822,7 +1701,7 @@ void NLReader::ReadBounds() { double lb = 0, ub = 0; BoundHandler bh(*this); int num_bounds = bh.num_items(); - double infinity = std::numeric_limits::infinity(); + double infinity = INFINITY; for (int i = 0; i < num_bounds; ++i) { switch (reader_.ReadChar() - '0') { case RANGE: @@ -2086,7 +1965,7 @@ void NLReader::Read() { handler_.EndInput(); } -// An .nl file reader. +/// An .nl file reader. template class NLFileReader { private: @@ -2096,7 +1975,7 @@ class NLFileReader { void Open(fmt::CStringRef filename); - // Reads the file into an array. + /// Reads the file into an array. void Read(fmt::internal::MemoryBuffer &array); public: @@ -2104,7 +1983,7 @@ class NLFileReader { const File &file() { return file_; } - // Opens and reads the file. + /// Opens and reads the file. template void Read(fmt::CStringRef filename, Handler &handler, int flags) { Open(filename); @@ -2130,17 +2009,27 @@ void ReadBinary(TextReader<> &reader, const NLHeader &header, bin_reader, header, handler, flags).Read(); } + +/// Reads names from the string *data* sending the names to the *handler* +/// object by calling ``handler.OnName(name)``. The name argument to +/// ``OnName`` is a ``fmt::StringRef`` object and the string it refers to +/// is not null-terminated. +/// Each name in the input string/file should be on a separate line +/// ended with a newline character ('\n') or "\r\n" (on Windows). template void ReadNames(fmt::CStringRef filename, fmt::StringRef data, NameHandler &handler) { + bool in_win_newline = false; int line = 1; const char *start = data.data(); const char *end = start + data.size(); for (const char *ptr = start; ptr != end; ++ptr) { + if (*ptr == '\r') in_win_newline = true; if (*ptr == '\n') { - handler.OnName(fmt::StringRef(start, ptr - start)); + handler.OnName(fmt::StringRef(start, ptr - start - in_win_newline)); start = ptr + 1; ++line; + in_win_newline = false; } } if (start != end) { @@ -2149,28 +2038,31 @@ void ReadNames(fmt::CStringRef filename, fmt::StringRef data, } } -// A name file reader. + +/// A name file reader. class NameReader { private: MemoryMappedFile<> mapped_file_; public: - // Reads names from the file *filename* sending the names to the *handler* - // object by calling ``handler.OnName(name)``. The name argument to - // ``OnName`` is a ``fmt::StringRef`` object and the string it refers to - // is not null-terminated. - // Each name in the input file should be on a separate line ended with a - // newline character ('\n'). + /// Reads names from file *filename* + /// by passing its contents to ReadNames(). template void Read(fmt::CStringRef filename, NameHandler &handler) { - fmt::File file(filename, fmt::File::RDONLY); - mapped_file_.map(file, filename); + try { + fmt::File file(filename, fmt::File::RDONLY); + mapped_file_.map(file, filename); + } catch (...) { + return; // ignore if not provided + } fmt::StringRef data(mapped_file_.start(), mapped_file_.size()); ReadNames(filename, data, handler); } }; -// An NL handler that constructs an optimization problem using ProblemBuilder. + +/// An NL handler that constructs an optimization problem +/// using ProblemBuilder. template class NLProblemBuilder { public: @@ -2190,7 +2082,7 @@ class NLProblemBuilder { obj.set_nonlinear_expr(expr); } - // Sets bounds on a problem item (objective or constraint). + /// Sets bounds on a problem item (objective or constraint). template void SetBounds(const Item &item, double lb, double ub) { item.set_lb(lb); @@ -2205,21 +2097,24 @@ class NLProblemBuilder { } public: + /// Constructor explicit NLProblemBuilder(ProblemBuilder &builder): builder_(builder) {} + /// Destructor. We have virtual functions + virtual ~NLProblemBuilder() = default; + + /// Get builder ProblemBuilder &builder() { return builder_; } + /// OnHeader event void OnHeader(const NLHeader &h) { builder_.SetInfo(h); // As nl-benchmark shows, adding problem components at once and then // updating them is faster than adding them incrementally. The latter - // requires additional checks to make sure that prolbem components are + // requires additional checks to make sure that problem components are // in the correct order. - if (int n = h.num_continuous_vars()) - builder_.AddVars(n, var::CONTINUOUS); - if (int n = h.num_integer_vars()) - builder_.AddVars(n, var::INTEGER); + AddVariables(h); if (int n = h.num_common_exprs()) builder_.AddCommonExprs(n); int n_objs = resulting_nobj( h.num_objs ); @@ -2233,16 +2128,73 @@ class NLProblemBuilder { builder_.AddFunctions(h.num_funcs); } + /// Add variables + void AddVariables(const NLHeader& h) { + // Distinguish NL variable order + // See D.M.Gay, Hooking Your Solver to AMPL; and Writing .NL Files, + // and, e.g., + // github.com/jump-dev/MathOptInterface.jl/blob/master/src/FileFormats/NL/README.md + int k=0; // current block position + const int num_nl_vars = std::max(h.num_nl_vars_in_cons, + h.num_nl_vars_in_objs); + if (num_nl_vars) { + DoAddVars(h.num_nl_vars_in_both - h.num_nl_integer_vars_in_both, + var::CONTINUOUS, k); + DoAddVars(h.num_nl_integer_vars_in_both, + var::INTEGER, k); + DoAddVars(h.num_nl_vars_in_cons - + (h.num_nl_vars_in_both + h.num_nl_integer_vars_in_cons), + var::CONTINUOUS, k); + DoAddVars(h.num_nl_integer_vars_in_cons, + var::INTEGER, k); + int num_nl_vars_in_objs_only = + std::max(0, h.num_nl_vars_in_objs - h.num_nl_vars_in_cons); + if (num_nl_vars_in_objs_only) { + DoAddVars(num_nl_vars_in_objs_only - h.num_nl_integer_vars_in_objs, + var::CONTINUOUS, k); + DoAddVars(h.num_nl_integer_vars_in_objs, + var::INTEGER, k); + } + } + MP_ASSERT_ALWAYS(num_nl_vars == k, "NLProblemBuilder: num_nl_vars mismatch"); + DoAddVars(h.num_vars - + (num_nl_vars + + h.num_linear_integer_vars + h.num_linear_binary_vars), + var::CONTINUOUS, k); + DoAddVars(h.num_linear_integer_vars + h.num_linear_binary_vars, + var::INTEGER, k); + MP_ASSERT_ALWAYS(h.num_vars == k, "NLProblemBuilder: num_vars mismatch"); + } + + /// DoAddVars: update counter \a k + void DoAddVars(int n, var::Type t, int& k) { + builder_.AddVars(n, t); + k += n; + } + + /// objno(). virtual, so that SolverNLHandler can override virtual int objno() const { return 1; } + + /// multiobj(). virtual, so that SolverNLHandler can override virtual bool multiobj() const { return true; } + /// notify_obj_added(). virtual, so that SolverNLHandler can override + virtual void notify_obj_added() const { } + + /// Actual N objectives int resulting_nobj(int nobj_header) const { return multiobj() ? nobj_header : std::min( (objno()>0), (nobj_header>0) ); } + + /// Whether need this objective bool NeedObj(int obj_index) const { return multiobj() || objno()-1==obj_index; } + + /// Final obj index for a given original index. + /// For multiobj, it's the same. + /// Otherwise it's 0 (1st objective). int resulting_obj_index(int index) const { if (multiobj()) return index; @@ -2252,6 +2204,7 @@ class NLProblemBuilder { void OnObj(int index, obj::Type type, NumericExpr expr) { SetObj(builder_.obj(index), type, expr); + notify_obj_added(); } void OnAlgebraicCon(int index, NumericExpr expr) { @@ -2499,7 +2452,7 @@ struct NLAdapter { #pragma GCC diagnostic ignored "-Wctor-dtor-privacy" #endif -// Checks if T has a member type Builder. +/// Checks if T has a member type Builder. template class HasBuilder { private: @@ -2534,7 +2487,8 @@ void ReadNLString(NLStringRef str, Handler &handler, ReadBinary(reader, header, adapter, flags); break; } - if (!IsIEEE(arith_kind) || !IsIEEE(header.arith_kind)) + if (!IsIEEE(arith_kind) + || !IsIEEE((arith::Kind)header.arith_kind)) throw ReadError(name, 0, 0, "unsupported floating-point arithmetic"); ReadBinary(reader, header, adapter, flags); break; @@ -2546,6 +2500,41 @@ template inline void ReadNLFile(fmt::CStringRef filename, Handler &handler, int flags) { internal::NLFileReader<>().Read(filename, handler, flags); } + + +/// A variable or constraint name provider. +/// Caters for possible missing names. +class NameProvider { +public: + /// Construct and read + NameProvider(fmt::CStringRef filename, + fmt::CStringRef gen_name, + std::size_t num_items); + + /// Construct without reading (generic names can be provided) + NameProvider(fmt::CStringRef gen_name); + + /// Read names + void ReadNames(fmt::CStringRef filename, + std::size_t num_items); + + /// Number of names read from file + size_t number_read() const; + + /// Returns the name of the item at specified index. + fmt::StringRef name(std::size_t index); + + /// Return vector of names, length n. + /// If number_read() < n, generic names are filled. + std::vector get_names(size_t n); + +private: + std::vector names_; + std::string gen_name_; + internal::NameReader reader_; + fmt::MemoryWriter writer_; +}; + } // namespace mp #endif // MP_NL_READER_H_ diff --git a/src/amplmp/include/mp/nl.h b/src/amplmp/include/mp/nl.h index 985b888fe2..c5fa4c090b 100644 --- a/src/amplmp/include/mp/nl.h +++ b/src/amplmp/include/mp/nl.h @@ -65,7 +65,7 @@ class NLStringRef { std::size_t size() const { return size_; } }; -// Flags for ReadNLFile and ReadNLString. +/// Flags for ReadNLFile and ReadNLString. enum { /** Read variable bounds before anything else. */ READ_BOUNDS_FIRST = 1 @@ -75,18 +75,9 @@ enum { \rst Reads an optimization problem in the NL format from the string *str* and sends notifications of the problem components to the *handler* object. - The handler class can be one of the following - - * derived from `mp::NLHandler` or `mp::NullNLHandler`, - * `mp::Problem`, - * provide an interface compatible with one of the above. - + See `mp::ReadNLFile` for description of *handler* and *flags*. Both *str* and *name* can be C strings or ``std::string`` objects. The *name* argument is used as the name of the input when reporting errors. - *flags* can be either 0, which is the default, to read all constructs in - the order they appear in the input, or `mp::READ_BOUNDS_FIRST` to read - variable bounds after the NL header and before other constructs such as - nonlinear expressions. \endrst */ template @@ -127,6 +118,7 @@ void ReadNLString(NLStringRef str, Handler &handler, */ template void ReadNLFile(fmt::CStringRef filename, Handler &handler, int flags = 0); -} // namespace mp + +} // namespace mp #endif // MP_NL_ diff --git a/src/amplmp/include/mp/os.h b/src/amplmp/include/mp/os.h index 84e59ca87e..68787e1c7d 100644 --- a/src/amplmp/include/mp/os.h +++ b/src/amplmp/include/mp/os.h @@ -33,7 +33,6 @@ class path { private: std::string str_; - inline std::size_t FindLastSep() const { #ifdef _WIN32 const char *sep = "/\\"; @@ -56,7 +55,6 @@ class path { const std::string &string() const { return str_; } - path filename() const { size_t last_sep = FindLastSep(); return last_sep == std::string::npos ? diff --git a/src/amplmp/include/mp/posix.h b/src/amplmp/include/mp/posix.h index be1286c425..700f9358f9 100644 --- a/src/amplmp/include/mp/posix.h +++ b/src/amplmp/include/mp/posix.h @@ -205,8 +205,18 @@ class File { explicit File(int fd) : fd_(fd) {} public: - // Possible values for the oflag argument to the constructor. + /// Possible values for the oflag argument to the constructor. +#ifndef _O_BINARY // POSIX opens in binary mode always + #define _O_BINARY 0 +#endif +#ifndef O_BINARY + #define O_BINARY 0 +#endif + enum { + /// Windows opens in text mode by default + BINARY = FMT_POSIX(O_BINARY), // Open in binary mode. + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. diff --git a/src/amplmp/src/expr-writer.h b/src/amplmp/src/expr-writer.h index 0db9a34e92..695a138a87 100644 --- a/src/amplmp/src/expr-writer.h +++ b/src/amplmp/src/expr-writer.h @@ -23,6 +23,8 @@ #ifndef MP_EXPR_WRITER_H_ #define MP_EXPR_WRITER_H_ +#include + #include "mp/basic-expr-visitor.h" namespace mp { @@ -396,7 +398,7 @@ void WriteExpr(fmt::Writer &w, const LinearExpr &linear, template void Write(fmt::Writer &w, const Problem &p) { // Write variables. - double inf = std::numeric_limits::infinity(); + double inf = INFINITY; int num_vars = p.num_vars(); for (int i = 0; i < num_vars; ++i) { w << "var x" << (i + 1); diff --git a/src/amplmp/src/format.cpp b/src/amplmp/src/format.cpp index 6b645d38a3..572663bddf 100644 --- a/src/amplmp/src/format.cpp +++ b/src/amplmp/src/format.cpp @@ -875,7 +875,7 @@ FMT_FUNC void fmt::report_windows_error( FMT_FUNC void fmt::print(std::FILE *f, CStringRef format_str, ArgList args) { MemoryWriter w; w.write(format_str, args); - std::fwrite(w.data(), 1, w.size(), f); + std::fwrite(w.data(), w.size(), 1, f); } FMT_FUNC void fmt::print(CStringRef format_str, ArgList args) { @@ -894,7 +894,7 @@ FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) { MemoryWriter w; printf(w, format, args); std::size_t size = w.size(); - return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); + return !std::fwrite(w.data(), size, 1, f) ? -1 : static_cast(size); } #ifndef FMT_HEADER_ONLY diff --git a/src/amplmp/src/nl-reader.cpp b/src/amplmp/src/nl-reader.cpp index 5c4f4902a6..68203421c0 100644 --- a/src/amplmp/src/nl-reader.cpp +++ b/src/amplmp/src/nl-reader.cpp @@ -172,6 +172,7 @@ void mp::internal::TextReader::ReadHeader(NLHeader &header) { // Read the format (text or binary). switch (ReadChar()) { case 'g': + header.format = NLHeader::TEXT; break; case 'b': header.format = NLHeader::BINARY; @@ -186,7 +187,11 @@ void mp::internal::TextReader::ReadHeader(NLHeader &header) { if (header.num_ampl_options > MAX_AMPL_OPTIONS) ReportError("too many options"); for (int i = 0; i < header.num_ampl_options; ++i) { - if (!ReadOptionalInt(header.ampl_options[i])) + double tmp; + if (!ReadOptionalDouble(tmp)) + break; + header.ampl_options[i] = (long)tmp; + if (header.ampl_options[i] != tmp) break; } if (header.ampl_options[USE_VBTOL_OPTION] == READ_VBTOL) @@ -235,7 +240,7 @@ void mp::internal::TextReader::ReadHeader(NLHeader &header) { if (ReadOptionalUInt(arith_kind)) { if (arith_kind > arith::LAST) ReportError("unknown floating-point arithmetic kind"); - header.arith_kind = static_cast(arith_kind); + header.arith_kind = arith_kind; ReadOptionalUInt(header.flags); } ReadTillEndOfLine(); @@ -282,7 +287,7 @@ void mp::internal::BinaryReaderBase::ReportError( template void mp::internal::NLFileReader::Open(fmt::CStringRef filename) { - file_ = File(filename, fmt::File::RDONLY); + file_ = File(filename, fmt::File::RDONLY | fmt::File::BINARY); size_ = ConvertFileToMmapSize(file_.size(), filename); // Round size up to a multiple of page_size. The remainded of the last // partial page is zero-filled both on POSIX and Windows so the resulting @@ -302,5 +307,77 @@ void mp::internal::NLFileReader::Read( array[size_] = 0; } + +namespace internal { + +class NameHandler { + private: + std::vector &names_; + fmt::StringRef name_; + + public: + explicit NameHandler(std::vector &names) + : names_(names), name_("") {} + + void OnName(fmt::StringRef name) { + name_ = name; + names_.push_back(name.data()); + } + + fmt::StringRef name() const { return name_; } +}; + +} // namespace internal + + +mp::NameProvider::NameProvider( + fmt::CStringRef filename, fmt::CStringRef gen_name, std::size_t num_items) + : gen_name_(gen_name.c_str()) { + ReadNames(filename, num_items); +} + +mp::NameProvider::NameProvider(fmt::CStringRef gen_name) + : gen_name_(gen_name.c_str()) { } + +void mp::NameProvider::ReadNames( + fmt::CStringRef filename, std::size_t num_items) { + ::internal::NameHandler handler(names_); + names_.reserve(num_items + 1); + try { + reader_.Read(filename, handler); + } catch (const fmt::SystemError &) { + return; // System error, ignore the file and use generated names. + } + fmt::StringRef last_name = handler.name(); + names_.push_back(last_name.data() + last_name.size() + 1); +} + +fmt::StringRef mp::NameProvider::name(std::size_t index) { + if (index + 1 < names_.size()) { + const char *name = names_[index]; + const auto* pos1past = names_[index + 1] - 1; + assert( ('\n' == *pos1past) || ('\r' == *pos1past)); + if ('\r' == *(pos1past-1)) // Windows + --pos1past; + return fmt::StringRef(name, pos1past - name); + } + writer_.clear(); + writer_ << gen_name_ << '[' << (index + 1) << ']'; + return fmt::StringRef(writer_.c_str(), writer_.size()); +} + +size_t mp::NameProvider::number_read() const { + return (names_.size() ? names_.size()-1 : 0); +} + +std::vector mp::NameProvider::get_names(size_t n) { + std::vector result; + result.reserve(n); + for (size_t i=0; i; template class mp::internal::NLFileReader<>; diff --git a/src/amplmp/src/os.cpp b/src/amplmp/src/os.cpp index f8fd236d06..21fc97f04b 100644 --- a/src/amplmp/src/os.cpp +++ b/src/amplmp/src/os.cpp @@ -112,11 +112,14 @@ path mp::GetExecutablePath() { using namespace std; return path(getprogname()); } + # else + path mp::GetExecutablePath() { throw "GetExecutablePath() is not implemented for this system"; return path(""); } + # endif // POSIX implementation. diff --git a/src/scip/reader_nl.cpp b/src/scip/reader_nl.cpp index 0b9768e0b2..2fee15bfbe 100644 --- a/src/scip/reader_nl.cpp +++ b/src/scip/reader_nl.cpp @@ -996,6 +996,10 @@ class AMPLProblemHandler : public mp::NLHandler } break; + case mp::suf::Kind::CON_BIT: + SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown constraint bit suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); + break; + case mp::suf::Kind::VAR: { if( strncmp(name.data(), "initial", name.size()) == 0 ) @@ -1023,13 +1027,25 @@ class AMPLProblemHandler : public mp::NLHandler } break; + case mp::suf::Kind::VAR_BIT: + SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown variable bit suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); + break; + case mp::suf::Kind::OBJ: SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown objective suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); break; + case mp::suf::Kind::OBJ_BIT: + SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown objective bit suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); + break; + case mp::suf::Kind::PROBLEM: SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown problem suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); break; + + case mp::suf::Kind::PROB_BIT: + SCIPverbMessage(amplph.scip, SCIP_VERBLEVEL_HIGH, NULL, "Unknown problem bit suffix <%.*s>. Ignoring.\n", (int)name.size(), name.data()); + break; } } } @@ -1801,7 +1817,7 @@ SCIP_RETCODE SCIPincludeReaderNl( SCIP_CALL( SCIPsetReaderCopy(scip, reader, readerCopyNl) ); SCIP_CALL( SCIPsetReaderRead(scip, reader, readerReadNl) ); - SCIP_CALL( SCIPincludeExternalCodeInformation(scip, "AMPL/MP 4e2d45c4", "AMPL .nl file reader library (github.com/ampl/mp)") ); + SCIP_CALL( SCIPincludeExternalCodeInformation(scip, "AMPL/MP 690e9e7", "AMPL .nl file reader library (github.com/ampl/mp)") ); return SCIP_OKAY; } From 85322601f9b013b95a8be5d872b340ac41021512 Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Fri, 5 Jan 2024 16:11:19 +0700 Subject: [PATCH 2/3] adjust lp1 refsol to updated ampl::mp - seems that ampl options to be written to sol file have changed for this case --- tests/src/reader/lp1.refsol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/reader/lp1.refsol b/tests/src/reader/lp1.refsol index b98c189b88..32ce7bac7c 100644 --- a/tests/src/reader/lp1.refsol +++ b/tests/src/reader/lp1.refsol @@ -3,8 +3,8 @@ optimal solution found Options 3 0 -1 0 +1 2 2 3 From b7cba726fb18124c3863e559bcbfa852c38173fc Mon Sep 17 00:00:00 2001 From: Stefan Vigerske Date: Fri, 5 Jan 2024 15:57:19 +0700 Subject: [PATCH 3/3] use new sol::Status enum to decide on solution status codes --- CHANGELOG | 1 + src/scip/reader_nl.cpp | 72 ++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aa0167a3cf..876b30c030 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -138,6 +138,7 @@ Interface changes `option scip_options 'limits/time 10 display/verblevel 1';`. String values should not be quoted and spaces in string values are not supported. - sassy can be used now as preprocessor for nauty/traces +- The solution status codes written to AMPL solution files now match those from https://mp.ampl.com/details.html#_CPPv4N2mp3sol6StatusE. ### Changed parameters diff --git a/src/scip/reader_nl.cpp b/src/scip/reader_nl.cpp index 2fee15bfbe..bc1bb96a02 100644 --- a/src/scip/reader_nl.cpp +++ b/src/scip/reader_nl.cpp @@ -1898,65 +1898,77 @@ SCIP_RETCODE SCIPwriteSolutionNl( for( int i = 0; i < probdata->nvars; ++i ) SCIPinfoMessage(scip, solfile, "%.17g\n", SCIPgetSolVal(scip, SCIPgetBestSol(scip), probdata->vars[i])); - /* AMPL solve status codes are at http://www.ampl.com/NEW/statuses.html - * number string interpretation - * 0 - 99 solved optimal solution found - * 100 - 199 solved? optimal solution indicated, but error likely - * 200 - 299 infeasible constraints cannot be satisfied - * 300 - 399 unbounded objective can be improved without limit - * 400 - 499 limit stopped by a limit that you set (such as on iterations) - * 500 - 599 failure stopped by an error condition in the solver routines + /* AMPL solve status codes are at https://mp.ampl.com/details.html#_CPPv4N2mp3sol6StatusE + * (mp::sol::Status enum in amplmp/include/mp/common.h) */ - int solve_result_num; + int solve_result_num = mp::sol::FAILURE; switch( SCIPgetStatus(scip) ) { case SCIP_STATUS_UNKNOWN: - solve_result_num = 500; break; case SCIP_STATUS_USERINTERRUPT: - solve_result_num = 450; + case SCIP_STATUS_TERMINATE: + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_INTERRUPT; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS_INTERRUPT; break; case SCIP_STATUS_NODELIMIT: - solve_result_num = 400; - break; case SCIP_STATUS_TOTALNODELIMIT: - solve_result_num = 401; - break; case SCIP_STATUS_STALLNODELIMIT: - solve_result_num = 402; + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_NODES; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS_NODES; break; case SCIP_STATUS_TIMELIMIT: - solve_result_num = 403; + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_TIME; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS_TIME; break; case SCIP_STATUS_MEMLIMIT: - solve_result_num = 404; + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_SOFTMEM; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS_SOFTMEM; break; case SCIP_STATUS_GAPLIMIT: - solve_result_num = 405; + /* there is no enum value for gaplimit, so use "work limit" */ + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_WORK; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS_WORK; break; case SCIP_STATUS_SOLLIMIT: - solve_result_num = 406; + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS_NUMSOLS; + else /* reach solution limit without solution? */ + solve_result_num = mp::sol::LIMIT_NO_FEAS; break; case SCIP_STATUS_BESTSOLLIMIT: - solve_result_num = 407; + case SCIP_STATUS_RESTARTLIMIT: + /* rare SCIP specific limits that don't map to an AMPL status */ + if( haveprimal ) + solve_result_num = mp::sol::LIMIT_FEAS; + else + solve_result_num = mp::sol::LIMIT_NO_FEAS; break; case SCIP_STATUS_OPTIMAL: - solve_result_num = 0; + solve_result_num = mp::sol::SOLVED; break; case SCIP_STATUS_INFEASIBLE: - solve_result_num = 200; + solve_result_num = mp::sol::INFEASIBLE; break; case SCIP_STATUS_UNBOUNDED: - solve_result_num = 300; + if( haveprimal ) + solve_result_num = mp::sol::UNBOUNDED_FEAS; + else + solve_result_num = mp::sol::UNBOUNDED_NO_FEAS; break; case SCIP_STATUS_INFORUNBD: - solve_result_num = 299; + solve_result_num = mp::sol::LIMIT_INF_UNB; break; - default: - /* solve_result_num = 500; */ - SCIPerrorMessage("invalid status code <%d>\n", SCIPgetStatus(scip)); - (void) fclose(solfile); - return SCIP_INVALIDDATA; } SCIPinfoMessage(scip, solfile, "objno 0 %d\n", solve_result_num);