Skip to content

Commit

Permalink
Expressions #27: partial conversion
Browse files Browse the repository at this point in the history
Remaining: add result explicifiers for those expressions which need it
  • Loading branch information
glebbelov committed Aug 12, 2024
1 parent e2006b0 commit 1dafa25
Show file tree
Hide file tree
Showing 10 changed files with 438 additions and 34 deletions.
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -400,14 +400,14 @@ add_prefix(MP_FLAT_REDEF_MIP_HEADERS include/mp/flat/redef/MIP/
piecewise_linear.h
power_const.h sos2.h)

add_prefix(MP_FLAT_EXPR_HEADERS include/mp/flat/expr/
model_api_base.h)
add_prefix(MP_FLAT_NL_EXPR_HEADERS include/mp/flat/nl_expr/
model_api_base.h constr_nl.h)


set(MP_ALL_HEADERS ${MP_HEADERS} ${MP_FLAT_HEADERS}
${MP_FLAT_REDEF_HEADERS}
${MP_FLAT_REDEF_STD_HEADERS} ${MP_FLAT_REDEF_MIP_HEADERS}
${MP_FLAT_EXPR_HEADERS}
${MP_FLAT_NL_EXPR_HEADERS}
)


Expand Down
295 changes: 285 additions & 10 deletions include/mp/flat/constr_2_expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
#define CONSTR_2_EXPR_H

#include <functional>
#include <type_traits>

#include "mp/flat/constr_keeper.h"
#include "mp/flat/nl_expr/constr_nl.h"
#include "mp/valcvt-link.h"

namespace mp {

Expand All @@ -13,16 +16,21 @@ namespace mp {
template <class Impl>
class Constraints2Expr {
public:
/// Convert some functional constraints to expressions
/// Mark some functional constraints as expressions.
/// Then convert some objectives and constraints
/// to "NL...." items using the expressions.
/// The auxiliary result variables of the functional
/// constraints which were marked as expressions,
/// will be handled in a special way.
void Convert2NL() {
MPD( MarkExpressions() );
MPD( GetModel() ).ConvertWithExpressions(*(Impl*)this);
MPD( GetModel() ).ConvertAllWithExpressions(*(Impl*)this);
}

/// Mark which functional constraints to be used as expressions,
/// vs assigning their result to a variable
void MarkExpressions() {
MPD( MarkAllResultVarsAsVars() );
MPD( MarkAllResultVarsAsVars() ); // tentatively
MPD( GetModel() ).MarkExprResultVars(*(Impl*)this);
MPD( GetModel() ).MarkArguments(*(Impl*)this);
}
Expand All @@ -32,20 +40,21 @@ class Constraints2Expr {
template <class Con>
void ConsiderMarkingResultVar(
const Con& con, int , ExpressionAcceptanceLevel eal) {
assert(ExpressionAcceptanceLevel::NotAccepted!=eal);
if (con.HasResultVar()) { // A functional constraint
// Check that it will be expression, but possibly with a dedicated result variable
if (ExpressionAcceptanceLevel::NotAccepted!=eal) {
assert( // Check: the result var has \a con as the init expr
MPD( template GetInitExpressionOfType<Con>(con.GetResultVar()) )
== &con);
MPD( MarkAsExpression(con.GetResultVar()) ); // can be changed later
}
assert( // Check: the result var has \a con as the init expr
MPD( template GetInitExpressionOfType<Con>(con.GetResultVar()) )
== &con);
MPD( MarkAsExpression(con.GetResultVar()) ); // can be changed later
}
}

/// Consider marking the argument variables as
/// "explicit variables" (not expressions.)
/// Generic request.
/// For func cons, once not accepted as expressions, consider their args
/// as variables.
/// For static cons, always.
template <class Con>
void ConsiderMarkingArguments(
const Con& con, int i, ExpressionAcceptanceLevel eal) {
Expand All @@ -59,6 +68,109 @@ class Constraints2Expr {
}


/// Convert algebraic constraint for use with expressions
/// @param i: constraint index
/// @param cal: (user chosen) acceptance level for \a con.
/// While it is recommended that flat algebraic constraints
/// are still accepted along with NLConstraint,
/// user might set acc:linle=0 etc.
/// @return true if this constraint has been eliminated/replaced.
template <class Body, class RhsOrRange>
bool ConvertWithExpressions(
const AlgebraicConstraint<Body, RhsOrRange>& con,
int i,
ConstraintAcceptanceLevel cal) {
/// 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())) {
ConvertToNLCon(con, i);
return true; // to remove the original \a con
}
return false;
}

/// Convert conditional algebraic constraint for use with expressions.
/// Basically we need the LHS to be an expression.
/// This can be simplified if we switch to do it earlier in flat conversions.
/// @return true if this constraint has been eliminated/replaced.
template <class Body, class RhsOrRange>
bool ConvertWithExpressions(
const ConditionalConstraint< AlgebraicConstraint<Body, RhsOrRange> >& con,
int i,
ConstraintAcceptanceLevel ) {
ConsiderExplicifyingExpression(con); // this is a func con too
if (!con.GetConstraint().GetBody().is_variable()) { // already a variable
ConvertConditionalConLHS(con, i);
return true;
}
return false;
}

/// Convert other functional constraints for use with expressions.
/// Basically, see if the result variable is explicit.
/// @return true if this constraint has been eliminated/replaced.
template <class FuncCon,
std::enable_if_t<
std::is_base_of_v<FunctionalConstraint, FuncCon>, bool > = true >
bool ConvertWithExpressions(
const FuncCon& con, int , ConstraintAcceptanceLevel ) {
ConsiderExplicifyingExpression(con);
return false; // leave it active
}

/// Convert complementarity constraint for use with expressions.
/// Similarly to Conditional, we need the expression part to be an NL expression.
/// @return true if this constraint has been eliminated/replaced.
template <class Expr>
bool ConvertWithExpressions(
const ComplementarityConstraint<Expr>& con,
int i,
ConstraintAcceptanceLevel ) {
if (!con.GetExpression().is_variable()) { // already a variable
ConvertComplementarityExpr(con, i);
return true;
}
return false;
}

/// Template for Indicators: do nothing.
/// But check that they are flat?
template <class SubCon>
bool ConvertWithExpressions(
const IndicatorConstraint<SubCon>& , int , ConstraintAcceptanceLevel ) {
return false;
}

/// SOS1: do nothing.
/// But check that they are flat?
bool ConvertWithExpressions(
const SOS1Constraint& con, int , ConstraintAcceptanceLevel ) {
for (int v: con.GetArguments()) {
assert(MPCD( IsProperVar(v) ));
}
return false;
}

/// SOS1: do nothing.
/// But check that they are flat?
bool ConvertWithExpressions(
const SOS2Constraint& con, int , ConstraintAcceptanceLevel ) {
for (int v: con.GetArguments()) {
assert(MPCD( IsProperVar(v) ));
}
return false;
}

/// NLConstraint: just produced.
bool ConvertWithExpressions(
const NLConstraint& , int , ConstraintAcceptanceLevel ) {
return false;
}


protected:
/// Algebraic cons: no marking (when NLConstraint accepted?)
/// @todo Do we need to consider NLConstraint / NLObjective at this step?
Expand All @@ -82,6 +194,169 @@ class Constraints2Expr {
}


/// Check if the algebraic body has expressions
bool HasExpressionArgs(const QuadAndLinTerms& qlt) const {
return HasExpressionArgs(qlt.GetLinTerms())
|| HasExpressionArgs(qlt.GetQPTerms());
}

bool HasExpressionArgs(const LinTerms& lt) const {
for (auto v: lt.vars())
if (!MPCD( IsProperVar(v) ))
return true;
return false;
}

bool HasExpressionArgs(const QuadTerms& qt) const {
for (auto v: qt.vars1())
if (!MPCD( IsProperVar(v) ))
return true;
for (auto v: qt.vars2())
if (!MPCD( IsProperVar(v) ))
return true;
return false;
}

/// Convert algebraic con to a NLConstraint
template <class Body, class RhsOrRange>
void ConvertToNLCon(
const AlgebraicConstraint<Body, RhsOrRange>& con, int i) {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
LinTerms lt;
/// exprTerm will be a LinearFunctionalConstraint or a Quadratic...
auto exprTerm = ExtractLinAndExprArgs(con.GetBody(), lt);
assert(0.0 == exprTerm.GetArguments().constant_term());
/// 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
exprResVar = MPD( AssignResultVar2Args(std::move(exprTerm)) );
if (!MPCD(VarHasMarking(exprResVar))) // mark as expr if new
MPD( MarkAsExpression(exprResVar) );
/// Exists and marked a variable
else if (MPCD( IsProperVar(exprResVar) )) { // Not an expression after all
lt.add_term(1.0, exprResVar);
exprResVar = -1; // no expression
lt.sort_terms();
}
} // else, stays as -1
NLConstraint nlc{lt, exprResVar,
{ con.GetRhsOrRange().lb(), con.GetRhsOrRange().ub() },
false}; // no sorting
MPD( AddConstraint( std::move(nlc) ) );
}

/// Extract linear and expression args
/// from Quad+Linear terms
/// @param ltout: pure linear part (coefs*vars)
/// for NLConstraint
/// @return expression part (QFC)
QuadraticFunctionalConstraint ExtractLinAndExprArgs(
const QuadAndLinTerms& qltin,
LinTerms& ltout) {
/// Create full QFC because we have QP terms
LinTerms lt_in_expr = SplitLinTerms(qltin.GetLinTerms(), ltout);
assert(qltin.GetQPTerms().size());
return
{{{std::move(lt_in_expr), qltin.GetQPTerms()}, 0.0}};
}

/// Extract linear and expression args
/// from Linear terms.
/// @param ltout: pure linear part (coefs*vars)
/// for NLConstraint
/// @return expression part (LFC)
LinearFunctionalConstraint ExtractLinAndExprArgs(
const LinTerms& ltin,
LinTerms& ltout) {
LinTerms lt_in_expr = SplitLinTerms(ltin.GetLinTerms(), ltout);
return {{std::move(lt_in_expr), 0.0}};
}

/// Split linterms into pure-var and expressions
/// @return the expressions part
LinTerms SplitLinTerms(const LinTerms& ltin, LinTerms lt_out_vars) {
LinTerms result;
int nvars=0;
for (int v: ltin.vars())
if (MPCD( IsProperVar(v) ))
++nvars;
lt_out_vars.reserve(nvars);
result.reserve(ltin.size() - nvars);
int v=0;
for (size_t i=0; i<ltin.size(); ++i) {
if (MPCD( IsProperVar(v = ltin.var(i)) ))
lt_out_vars.add_term(ltin.coef(i), v);
else
result.add_term(ltin.coef(i), v);
}
return result;
}

/// Convert the LHS of a conditional con
template <class Body, class RhsOrRange>
void ConvertConditionalConLHS(
const ConditionalConstraint< AlgebraicConstraint<Body, RhsOrRange> >& con,
int i) {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
/// Create a functional constraint from the LHS
auto fc = MakeFunctionalConstraint(
AlgebraicExpression{con.GetConstraint().GetArguments(), 0.0});
auto resvar = MPD( AssignResultVar2Args(std::move(fc)) );
/// resvar can be a proper variable - ModelAPI should flexibly handle this
LinTerms lt { {1.0}, {resvar} };
ConditionalConstraint< AlgebraicConstraint<LinTerms, RhsOrRange> >
ccnew { { std::move(lt), con.GetConstraint().GetRhsOrRange() } };
MPD( RedefineVariable(con.GetResultVar(), std::move(ccnew)) ); // Use new CondCon
}

/// Convert the expression part of complementarity.
/// Similar to the argument of a conditional con.
template <class Expr>
void ConvertComplementarityExpr(
const ComplementarityConstraint<Expr>& con,
int i) {
auto alscope = MPD( MakeAutoLinker( con, i ) ); // link from \a con
/// Create a functional constraint from the LHS
auto fc = MakeFunctionalConstraint(con.GetExpression());
auto resvar = MPD( AssignResultVar2Args(std::move(fc)) );
/// resvar can be a proper variable - ModelAPI should flexibly handle this
LinTerms lt { {1.0}, {resvar} };
ComplementarityConstraint< AlgebraicExpression<LinTerms> >
ccnew { AffineExpr{ std::move(lt), 0.0 }, con.GetVariable() };
MPD( AddConstraint(std::move(ccnew)) ); // Use new CondCon
}

/// Consider explicifying an expression
template <class FuncCon>
void ConsiderExplicifyingExpression(const FuncCon& con) {
if (MPCD( IsProperVar(con.GetResultVar()) ))
DoExplicify(con);
}

/// Add expr = var assignment for algebraic expression
template <class FuncCon,
std::enable_if_t<
std::is_base_of_v<NumericFunctionalConstraintTraits, FuncCon>, bool > = true >
void DoExplicify(const FuncCon& con) {

}

/// Add expr = var assignment for logical expression (NLEquivalence, NLImpl, NLRImpl).
/// If this constraint is root-level (is true),
/// add root explicifier instead (NLLogical).
template <class FuncCon,
std::enable_if_t<
std::is_base_of_v<LogicalFunctionalConstraintTraits, FuncCon>, bool > = true >
void DoExplicify(const FuncCon& con) {
// If root...
// else...
}


private:
/// (Argument) variable visitor
std::function<void( int )> MarkVar_ = [this](int v){
Expand Down
12 changes: 12 additions & 0 deletions include/mp/flat/constr_functional.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ class QuadraticFunctionalConstraint :
/// Typedef LinExpression
using QuadExpression = ExprWrapper<QuadraticFunctionalConstraint>;

/// Shortcut to make an LFC from linear body and constant
inline LinearFunctionalConstraint
MakeFunctionalConstraint(AffineExpr ae) {
return {std::move(ae)};
}

/// Shortcut to make a QFC from quadr body and constant
inline QuadraticFunctionalConstraint
MakeFunctionalConstraint(QuadraticExpr qe) {
return {std::move(qe)};
}

/// Write LFC without name.
template <class Writer>
inline void WriteModelItem(Writer& wrt,
Expand Down
Loading

1 comment on commit 1dafa25

@glebbelov
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be #237

Please sign in to comment.