Skip to content

Commit

Permalink
Expression interface: objectives, context #237
Browse files Browse the repository at this point in the history
plus conversion order (static -> func cons)
  • Loading branch information
glebbelov committed Aug 15, 2024
1 parent 3b48f30 commit c490dec
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 26 deletions.
105 changes: 92 additions & 13 deletions include/mp/flat/constr_2_expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ class Constraints2Expr {
/// will be handled in a special way.
void Convert2NL() {
MPD( MarkExpressions() );
/// Objectives before constraints,
/// because if we produce new expressions and their result variables
/// need to be explicit, this is done in constraints conversion.
MPD( ConvertObjectivesWithExpressions() );
stage_cvt2expr_ = 1; // static cons
// -> new static cons + func cons
MPD( GetModel() ).ConvertAllWithExpressions(*(Impl*)this);
stage_cvt2expr_ = 2; // func cons -> explicifiers
MPD( GetModel() ).ConvertAllWithExpressions(*(Impl*)this);
}

Expand Down Expand Up @@ -80,12 +88,14 @@ class Constraints2Expr {
const AlgebraicConstraint<Body, RhsOrRange>& con,
int i,
ConstraintAcceptanceLevel cal) {
assert(stage_cvt2expr_>0);
/// Replace \a con by a NLConstraint,
/// if either the ModelAPI does not accept it,
/// or the linear/quadratic terms have expressions
/// (and then they are non-flat.)
if (ConstraintAcceptanceLevel::Recommended != cal
|| HasExpressionArgs(con.GetBody())) {
if (1==stage_cvt2expr_
&& (ConstraintAcceptanceLevel::Recommended != cal
|| HasExpressionArgs(con.GetBody()))) {
ConvertToNLCon(con, i);
return true; // to remove the original \a con
}
Expand All @@ -101,11 +111,15 @@ class Constraints2Expr {
const ConditionalConstraint< AlgebraicConstraint<Body, RhsOrRange> >& con,
int i,
ConstraintAcceptanceLevel ) {
ConsiderExplicifyingExpression(con, i); // this is a func con too
if (!con.GetConstraint().GetBody().is_variable()) { // already a variable
ConvertConditionalConLHS(con, i);
return true;
assert(stage_cvt2expr_>0 && stage_cvt2expr_<=2);
if (1==stage_cvt2expr_) {
if (!con.GetConstraint().GetBody().is_variable()) { // already a variable
ConvertConditionalConLHS(con, i);
return true;
}
}
else // if (2==stage_cvt2expr_)
ConsiderExplicifyingExpression(con, i); // this is a func con too
return false;
}

Expand All @@ -117,7 +131,8 @@ class Constraints2Expr {
std::is_base_of_v<FunctionalConstraint, FuncCon>, bool > = true >
bool ConvertWithExpressions(
const FuncCon& con, int i, ConstraintAcceptanceLevel ) {
ConsiderExplicifyingExpression(con, i);
if (2==stage_cvt2expr_)
ConsiderExplicifyingExpression(con, i);
return false; // leave it active
}

Expand All @@ -129,7 +144,8 @@ class Constraints2Expr {
const ComplementarityConstraint<Expr>& con,
int i,
ConstraintAcceptanceLevel ) {
if (!con.GetExpression().is_variable()) { // already a variable
if (1==stage_cvt2expr_
&& !con.GetExpression().is_variable()) { // already a variable
ConvertComplementarityExpr(con, i);
return true;
}
Expand All @@ -148,6 +164,7 @@ class Constraints2Expr {
/// But check that they are flat?
bool ConvertWithExpressions(
const SOS1Constraint& con, int , ConstraintAcceptanceLevel ) {
if (2==stage_cvt2expr_)
for (int v: con.GetArguments()) {
assert(MPCD( IsProperVar(v) ));
}
Expand All @@ -158,6 +175,7 @@ class Constraints2Expr {
/// But check that they are flat?
bool ConvertWithExpressions(
const SOS2Constraint& con, int , ConstraintAcceptanceLevel ) {
if (2==stage_cvt2expr_)
for (int v: con.GetArguments()) {
assert(MPCD( IsProperVar(v) ));
}
Expand Down Expand Up @@ -190,6 +208,14 @@ class Constraints2Expr {
return false;
}

/// Convert objectives
void ConvertObjectivesWithExpressions() {
auto& objs = MPD( get_objectives() );
for (size_t iobj=0; iobj<objs.size(); ++iobj) {
Convert1ObjWithExpressions(iobj, objs[iobj]);
}
}


protected:
/// Algebraic cons: no marking (when NLConstraint accepted?)
Expand Down Expand Up @@ -246,13 +272,19 @@ class Constraints2Expr {
/// exprTerm will be a LinearFunctionalConstraint or a Quadratic...
auto exprTerm = ExtractLinAndExprArgs(con.GetBody(), lt);
assert(0.0 == exprTerm.GetArguments().constant_term());
AlgConRange rng { con.GetRhsOrRange().lb(), con.GetRhsOrRange().ub() };
/// Store full LFC only if it is not 1.0*var
int exprResVar = -1;
if (exprTerm.GetArguments().is_variable()) {
exprResVar = exprTerm.GetArguments().get_representing_variable();
assert( !MPCD( IsProperVar(exprResVar) ) ); // is an expr
} else {
assert( !exprTerm.GetArguments().empty() ); // has more terms, or coef != 1.0
exprTerm.SetContext( // Context is compulsory
rng.lb() > MPCD( PracticallyMinusInf() ) // Should not need to propagate
? (rng.ub() < MPCD( PracticallyInf() )
? Context::CTX_MIX : Context::CTX_POS)
: Context::CTX_NEG);
exprResVar = MPD( AssignResultVar2Args(std::move(exprTerm)) );
if (!MPCD(VarHasMarking(exprResVar))) // mark as expr if new
MPD( MarkAsExpression(exprResVar) );
Expand All @@ -263,12 +295,52 @@ class Constraints2Expr {
lt.sort_terms();
}
} // else, stays as -1
NLConstraint nlc{lt, exprResVar,
{ con.GetRhsOrRange().lb(), con.GetRhsOrRange().ub() },
false}; // no sorting
NLConstraint nlc{lt, exprResVar, rng, false}; // no sorting
MPD( AddConstraint( std::move(nlc) ) );
}

/// Convert (if needed) an objective to NLObjective.
/// Logic similar to NLConstraint.
void Convert1ObjWithExpressions(int iobj, QuadraticObjective& qobj) {
LinTerms lt_varsonly;
LinTerms lt_in_expr = SplitLinTerms(qobj.GetLinTerms(), lt_varsonly);
// Have expression(s) or QP terms? Need to hide them into the expression part
if (lt_in_expr.size() || qobj.GetQPTerms().size()) {
int exprResVar = -1;
if (lt_in_expr.is_variable() && qobj.GetQPTerms().empty()) {
exprResVar = lt_in_expr.get_representing_variable();
assert( !MPCD( IsProperVar(exprResVar) ) ); // is an expr
} else { // We need a new expression
// Set up AutoLink
auto obj_src = // source value node for this obj
MPD( GetValuePresolver() ).GetSourceNodes().GetObjValues()().Select(iobj);
pre::AutoLinkScope<Impl> auto_link_scope{ *(Impl*)this, obj_src };
if (qobj.GetQPTerms().empty())
exprResVar = MPD( AssignResultVar2Args(
LinearFunctionalConstraint{ {lt_in_expr, 1.0} } ) );
else // Move QP terms into the expr
exprResVar = MPD( AssignResultVar2Args(
QuadraticFunctionalConstraint
{ {{lt_in_expr, std::move(qobj.GetQPTerms())}, 1.0} } ) );
MPD( SetInitExprContext(exprResVar, // Context is compulsory
obj::MAX==qobj.obj_sense_true() // no need to propagate
? Context::CTX_POS : Context::CTX_NEG) );
if ( !MPCD(VarHasMarking(exprResVar) )) // mark as expr if new
MPD( MarkAsExpression(exprResVar) );
if ( !MPCD( GetModelAPI() ).AcceptsNLObj() ) // But as var if NLObj not accepted
MPD( MarkAsResultVar(exprResVar) );
if ( MPCD( IsProperVar(exprResVar) ) ) { // Not an expression after all
lt_varsonly.add_term(1.0, exprResVar);
exprResVar = -1; // no expression
lt_varsonly.sort_terms();
}
}
qobj.GetLinTerms() = lt_varsonly;
if (exprResVar>=0)
qobj.SetExprIndex(exprResVar);
}
}

/// Extract linear and expression args
/// from Quad+Linear terms
/// @param ltout: pure linear part (coefs*vars)
Expand Down Expand Up @@ -331,6 +403,7 @@ class Constraints2Expr {
ConditionalConstraint< AlgebraicConstraint<LinTerms, RhsOrRange> >
ccnew { { std::move(lt), con.GetConstraint().GetRhsOrRange() } };
MPD( RedefineVariable(con.GetResultVar(), std::move(ccnew)) ); // Use new CondCon
MPD( PropagateResultOfInitExpr(con.GetResultVar(), con.GetContext()) ); // context
}

/// Convert the expression part of complementarity.
Expand All @@ -342,6 +415,7 @@ class Constraints2Expr {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
/// Create a functional constraint from the LHS
auto fc = MakeFunctionalConstraint(con.GetExpression());
fc.SetContext(Context::CTX_MIX); // need context
auto resvar = MPD( AssignResultVar2Args(std::move(fc)) );
/// resvar can be a proper variable - ModelAPI should flexibly handle this
LinTerms lt { {1.0}, {resvar} };
Expand All @@ -360,7 +434,8 @@ class Constraints2Expr {
/// Add expr = var assignment for algebraic expression
template <class FuncCon,
std::enable_if_t<
std::is_base_of_v<NumericFunctionalConstraintTraits, FuncCon>, bool > = true >
std::is_base_of_v<
NumericFunctionalConstraintTraits, FuncCon>, bool > = true >
void DoExplicify(const FuncCon& con, int i) {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
assert(!con.GetContext().IsNone());
Expand All @@ -379,7 +454,8 @@ class Constraints2Expr {
/// add root explicifier instead (NLLogical).
template <class FuncCon,
std::enable_if_t<
std::is_base_of_v<LogicalFunctionalConstraintTraits, FuncCon>, bool > = true >
std::is_base_of_v<
LogicalFunctionalConstraintTraits, FuncCon>, bool > = true >
void DoExplicify(const FuncCon& con, int i) {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
auto resvar = con.GetResultVar();
Expand All @@ -404,6 +480,9 @@ class Constraints2Expr {
std::function<void( int )> MarkVar_ = [this](int v){
MPD( MarkAsResultVar(v) ); // no recursion
};

/// NL conversion stage
int stage_cvt2expr_ {-1};
};

} // namespace mp
Expand Down
4 changes: 2 additions & 2 deletions include/mp/flat/constr_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ class BasicConstraint {
/// Get context, if meaningful
Context GetContext() const { return Context::CTX_NONE; }
/// Set context, if meaningful
void SetContext(Context ) const { }
void SetContext(Context ) const { MP_RAISE("Setting context for static constraint"); }
/// Add (merge) context, if meaningful
void AddContext(Context ) const { }
void AddContext(Context ) const { MP_RAISE("Setting context for static constraint"); }
/// Has result var (is functional)?
bool HasResultVar() const { return false; }
/// For functional constraints, result variable index
Expand Down
6 changes: 5 additions & 1 deletion include/mp/flat/constr_general.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,15 @@ class IndicatorConstraint: public BasicConstraint {
b_(b), bv_(bv), con_(std::move(con)) { assert(check()); }
bool check() const { return (b_>=0) && (bv_==0 || bv_==1); }

/// Getters
/// Get the binary var
int get_binary_var() const { return b_; }
/// Get the value for b==value
int get_binary_value() const { return bv_; }
/// Check if value==1
bool is_binary_value_1() const
{ return 1==get_binary_value(); }

/// Get the implied constraint, const
const Con& get_constraint() const { return con_; }

/// Compute violation
Expand Down
4 changes: 4 additions & 0 deletions include/mp/flat/constr_keeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ class ConstraintKeeper final
Context GetContext(int i) const override
{ assert(check_index(i)); return cons_[i].GetCon().GetContext(); }

/// Set context of contraint \a i
void SetContext(int i, Context ctx) override
{ assert(check_index(i)); cons_[i].GetCon().SetContext(ctx); }

/// Propagate expression result of constraint \a i top-down
void PropagateResult(BasicFlatConverter& cvt,
int i,
Expand Down
15 changes: 12 additions & 3 deletions include/mp/flat/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,16 @@ class FlatConverter :

/// Propagate objective contexts
void PropagateObjContexts() {
const auto& objs = MPD( get_objectives() );
auto& objs = MPD( get_objectives() );
const auto objwgt = MPD( GetMOWeights() );
if (GetEnv().multiobj()) // only in obj:multi mode
assert(objs.size() == objwgt.size());
for (size_t i=0; i<objs.size(); ++i) {
auto isMax = obj::MAX==objs[i].obj_sense();
if (GetEnv().multiobj() && objwgt[i]<0.0) // only in obj:multi
if (GetEnv().multiobj() && objwgt[i]<0.0) { // only in obj:multi
isMax = !isMax;
objs[i].set_sense_true(isMax ? obj::MAX : obj::MIN);
}
auto ctx = isMax ? Context::CTX_POS : Context::CTX_NEG;
MPD( PropagateResult2LinTerms(objs[i].GetLinTerms(),
MPD( MinusInfty() ), MPD( Infty() ), ctx) );
Expand Down Expand Up @@ -880,7 +882,14 @@ class FlatConverter :
/// Does not check if that's a func con,
/// user check for != CTX_NONE
Context GetInitExprContext(int var) const {
return GetInitExpression(var).GetConstraint().GetContext();
auto ie = GetInitExpression(var);
return ie.GetCK()->GetContext(ie.GetIndex());
}

/// Set func expr context
void SetInitExprContext(int var, Context ctx) {
auto ie = GetInitExpression(var);
ie.GetCK()->SetContext(ie.GetIndex(), ctx);
}

/// Get the init expression pointer.
Expand Down
1 change: 1 addition & 0 deletions include/mp/flat/converter_multiobj.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class MOManager {
const auto& i0_vec = pr_level.second;
obj_new_.push_back(obj_orig.at(i0_vec.front()));
obj_new_.back().set_sense(obj_orig.front().obj_sense()); // "Legacy" obj:multi:weight
obj_new_.back().set_sense_true(obj_orig.front().obj_sense());
obj_new_.back().GetLinTerms() *= objwgt.at(i0_vec.front()); // Use weight
obj_new_.back().GetQPTerms() *= objwgt.at(i0_vec.front());
obj_new_tola_.push_back(objtola.at(i0_vec.front()));
Expand Down
3 changes: 3 additions & 0 deletions include/mp/flat/item_keeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class BasicConstraintKeeper {
/// Get context of contraint \a i
virtual Context GetContext(int i) const = 0;

/// Set context of contraint \a i
virtual void SetContext(int i, Context ctx) = 0;

/// Propagate expression result of constraint \a i top-down
virtual void PropagateResult(BasicFlatConverter& cvt,
int i,
Expand Down
3 changes: 3 additions & 0 deletions include/mp/flat/model_api_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ class BasicFlatModelAPI {
/// 0 - no, 1 - convex, 2 - nonconvex
static int AcceptsQuadObj() { return 0; }

/// Whether accepts NLObjective (relevant in BasicExprModelAPI)
static int AcceptsNLObj() { return 0; }

/// Placeholder for SetQuadraticObjective()
void SetQuadraticObjective(int , const QuadraticObjective& ) {
MP_UNSUPPORTED("FlatModelAPI::SetQuadraticObjective()");
Expand Down
2 changes: 1 addition & 1 deletion include/mp/flat/nl_expr/constr_nl.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class NLConstraint
bool HasExpr() const { return expr_>=0; }

/// Expression index.
/// @note ModelAPI should call self.HasExpression
/// @note ModelAPI should call self.HasExpression()
/// and self.GetExpression()
/// to obtain the expression term.
int ExprIndex() const { assert(HasExpr()); return expr_; }
Expand Down
4 changes: 4 additions & 0 deletions include/mp/flat/nl_expr/model_api_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class BasicExprModelAPI
static constexpr ExpressionAcceptanceLevel \
ExpressionInterfaceAcceptanceLevel() { return ExpressionAcceptanceLevel::val; }

/// Whether accepts NLObjective.
/// SCIP does not.
static int AcceptsNLObj() { return 1; }

/// Reuse inherited names
USE_BASE_CONSTRAINT_HANDLERS(BasicFlatModelAPI)

Expand Down
Loading

0 comments on commit c490dec

Please sign in to comment.