Show / Hide Table of Contents

    Working with Expressions

    An Expression consists of a set of Variables and constants that are linked via some operators. The variables are wrapped in Terms, which are a combination of a scalar factor and a variable. Constant values are represented by ConstantExpressions.
    The various types of operators that can be used to build expressions, and Do's and Don'ts of using them will be outlined in the following sections.

    Different Kinds of Expressions

    Internally, we distinguish between two "kinds" of expressions. On the one hand, there are operators that can be used to combine two (or more) (sub-)expressions. On the other hand, there are terminal symbols that make up the leaves of an expression tree.


    Operators

    • Plus
      • Represents the sum of two (or more) expressions
    • Times
      • Represents the product of two (or more) expressions

    Special Cases

    The following operators will always lead to non-linear expressions. However, they can be replaced with a set of equivalent linear expressions. To prevent the user from having to deal with all of the mind-boggling transformations, OPTANO.Modeling provides capabilities to automate this task. By default, this step is performed before a model is passed to the Solver. Thus, you can make use of more sophisticated expressions/constraints without having to ponder of how to keep your Model linear.
    Note that all of these operators are defined on a set of Expressions. This provides much more flexibility/versatility than proprietary solutions, such as the one of, e.g., Gurobi. Gurobi only allows GRBGenExpressions to be formulated over a list of GRBVars (compare GRBRefMan, pp. 144).
    Keep in mind that the transformation of these special operators into linear expressions requires the creation of several helper variables and -constraints in the background. Thus, your model size (and solution time) will probably increase.

    • Min
      • Takes the value of the smallest sub-expression
      • Defined over a set of one or more expressions
    • Max
      • Takes the value of the largest sub-expression
      • Defined over a set of one or more expressions
    • Floor
      • Takes the value of the largest integer that is lean than or equal (≤) to the contained expression
      • Defined over a single expression
    • Ceil
      • Takes the value of the smallest integer that is greater than or equal (≥) to the contained expression
      • Defined over a single expression
    • Abs
      • Takes the absolute value of the contained expression
      • Defined over a single expression

    Terminal Symbols

    • Term
      • A term consists of a variable and a factor
      • Lone variables are implicitly converted to terms with a factor of 1
    • ConstantExpression
      • Represents a constant (double) value

    Note that "terminal symbols", as well as "operators" (and the special cases) are valid "expressions" to plug in each of the above mentioned set of operators.

    Creating Expressions

    The C#-operators +, -, * are overloaded so that they accept arbitrary Expressions. In order to "create" an expression it suffices to simply add to variables or terms (or other expressions):

    var x = new Variable("x", 0, 100);
    var y = new Variable("y", 0, 250);
    
    var myFirstExpression = 2 * x + y;
    

    For the creation of expressions that are listed in "special cases", we provide static methods on the Expressions class. Alternatively, you can also create them by explicitly calling new YourDesiredOperator(...):

    var floorExp = Expression.Floor(myFirstExpression, 10000);
    

    When creating an operator expression from the "special cases", you'll notice optional argument double? bigM = null. The bigM is a large number that is only used if the bounds of the variables in the expressions are non-finite. It should be chosen large enough, so that the solution space is not cut. But choosing this value too large can cause numerical errors.
    In the example given above the variable bounds are already finite. Thus, explicitly passing a biM actually is not required. Additionally, the value 10000 is larger than need be: We know that myFirstExpression can never be larger than 450 (due to the bounds on x and y). OPTANO.Modeling always tries to derive the smallest feasible BigM for an expression when it is linearized. Only if that fails (i.e. not all Variable Bounds are finite), the user-provided bigM is used. If this is null, an InvalidOperationExceptionis thrown and the solution process is cancelled.

    Using Expression.Sum

    The afore-mentioned way of creating a sum over a set of variables will become very unhandy (and inefficient) for longer expressions. This is due to the fact that Expressions are immutable, meaning that with every "+" a "new" expression is created (and thrown away right when the next "+" is appended).
    Instead, Expression.Sum is a fast method to create a sum of elements, terms and variables.

    Have a look at the Getting Started Example.

    As an example, the following constraint might be used to limit the number of edges used:

    sum_of_all \{edges(y)\} <= 4
    

    The wrong way: DO NOT DO THIS

    // ...
    var limitUsage = Expression.EmptyExpression;
    
    // Add design decision of each edge
    foreach (var edge in this.Edges)
    {
        limitUsage += y[edge];
    }
    // ...
    

    The right way: USE EXPRESSION.SUM()

    Expression.Sum is much faster, creates better code and generates a nice, balanced expression tree. Just use it.

    var limitUsage = Expression.Sum(this.Edges.Select(edge => y[edge]));
    

    Using Expressions to create Constraints

    Constraints are created just as you'd expect them to be. Simply take an Expression and restrict it with a lower or upper bound:

    var usageConstraint = limitUsage <= 4;
    this.Model.AddConstraint(usageConstraint, "Limit design decision");
    

    Range Constraints

    If you wish to create a ranged constraint (i.e. a constraint with lower and upper bound), you can either manipulate an existing constraint:

    usageConstraint.LowerBound = 1;
    

    Or you can to this by explicitly calling the Constraint's constructor:

    var otherConstraint = new Constraint(limitUsage, "Range design decision", 1, 4);
    

    Advanced: Working with OperatorConstraints

    Similar to the advanced operators, OPTANO.Modeling provides some logical OperatorConstraints that enable you to model relationships between constraints.

    OperatorConstraint Types

    • Or-Operator
      • Defines alternative constraints, or alternative constraint groups
      • At least one of the two groups (left or right) need to be fulfilled completely
        • Operator is similar to a logical or (and not an xor).
    • Not-Operator
      • Negates a single constraint
      • E.g. Not(3 <= x <= 5) will be converted into two alternative constraints:
        • x < 3 and x > 5, which will be wrapped in an Or-constraint.
        • I.e. only one of the two constraints needs to be fulfilled in order for the solution to be feasible.
    • Implication-Operator
      • Defines a logical implication for two constraints a and b.
      • a.Implies(b) is equivalent to !a | b.

    For example, you can use the Or-operator to quickly generate two alternative constraint groups:

    // Get an IEnumerable (or a single Constraint) as group 1
    IEnumerable<Constraint> leftConstraints = CreateConstraintsWithoutAddingThemToTheModel(); 
    // Get an IEnumerable (or a single Constraint) as group 2
    IEnumerable<Constraint> rightConstraints = CreateMoreConstraintsWithoutAddingThemToTheModel(); 
    
    // all 'left' or all 'right' constraints (or both groups) should be fulfilled.
    var alternativeGroups = new Or(leftConstraints, rightConstraints);
    // use override of AddConstraint that accepts OperatorConstraints:
    model.AddConstraint(alternativeGroups);
    // proceed ...
    

    To quickly negate a constraint, you can make use of the overloaded !-operator:

    var myConstraint = new Constraint(2 * x + y, "Range design decision", 1, 4);
    var negatedConstraint = !myConstraint;
    

    The same works for creating two alternative constraints, using the |-operator:

    var alternativeConstraint = (2 * x + y) >= 4 + ModelScope.Current.EPSILON 
                                | (2 * x + y) <= 1 - ModelScope.Current.EPSILON;
    
    Back to top Copyright © OPTANO GmbH generated with DocFX
    Privacy Policy | Impressum – Legal Notice