Transformations

Transformations allow changing the solved problem without updating its specification. Common use-cases include:

  • Relaxing a constraint to investigate infeasibilities

  • Pinning a variable to estimate the impact of an override

  • Omitting a constraint to see its impact on the objective value

Base transformations

This section lists available transformation building blocks. These can be used individually or combined (see Transformation patterns).

Pinning variables

class opvious.transformations.PinVariables(labels: list[str] = <factory>)

Bases: ProblemTransformation

A transformation which pins one or more variables to input values

Each pinned variable will have an associated derived parameter labeled $label_pin with the same domain and image as the original variable. For example a non-negative production variable would have a non-negative parameter labeled production_pin.

A constraint (labeled $label_isPinned) is also automatically added which enforces equality between the variable and any inputs passed in to the parameter. Values for keys which do not have a pin remain free, so it is possible to do partial pinning. For example, assuming a monthly variable labeled production, the following code would only pin production in January (to 100):

transformations = [opvious.transformations.PinVariables("production")]
parameters = {
    "production_pin": {"january": 100},
    # ...
}

Finally, note that this constraint can also be transformed similar to any other constraint. For example it can sometimes be useful to relax it via RelaxConstraints.

labels: list[str]

The labels of the variables to pin

If empty, all variables will be pinned.

Relaxing constraints

class opvious.transformations.RelaxConstraints(labels: list[str] = <factory>, penalty: ~typing.Literal['TOTAL_DEVIATION', 'MAX_DEVIATION', 'DEVIATION_CARDINALITY'] = 'TOTAL_DEVIATION', is_capped: bool = False)

Bases: ProblemTransformation

A transformation which relaxes one or more constraints

Each relaxed constraint will be omitted from the formulation and replaced by a slack variable and an objective minimizing this slack (two of each for equality constraints). The derived variables have the same domain as the relaxed constraint, are always non-negative, and are labeled as follows:

  • $label_deficit for deficit slack (applicable for greater than and equality constraints).

  • $label_surplus for surplus slack (applicable for less than and equality constraints).

For example an equality constraint labeled isBalanced would be transformed into two variables labeled isBalanced_deficit and isBalanced_surplus. A greater than constraint labeled demandIsMet would be relaxed into a single variable labeled demandIsMet_deficit.

Each deficit (resp. surplus) variable has a corresponding objective labeled $label_minimizeDeficit (resp. $label_minimizeSurplus). Refer to the penalty parameter for details on how slack is penalized.

Finally, since relaxed formulations will almost always have multiple objectives, you may also need to specific a opvious.SolveStrategy. A common pattern is to omit any existing objectives and minimize the aggregate slack violation (see Detecting infeasibilities).

is_capped: bool = False

Whether slack is capped

Setting this to true will create an additional parameter for each slack variable labeled $label_deficitCap (resp. $label_surplusCap) for deficit (resp. surplus). This parameter has the same domain as the variable and is non-negative.

This option is required when using the DEVIATION_CARDINALITY penalty.

labels: list[str]

The labels of the constraints to relax

If empty, all constraints will be relaxed.

penalty: Literal['TOTAL_DEVIATION', 'MAX_DEVIATION', 'DEVIATION_CARDINALITY'] = 'TOTAL_DEVIATION'

Slack penalization mode

  • TOTAL_DEVIATION: Cost proportional to the total sum of the (absolute value of) slack for the constraint.

  • MAX_DEVIATION: Cost proportional to the maximum deviation for the constraint.

  • DEVIATION_CARDINALITY: Cost proportional to the number of rows with non-zero deviation. This penalty requires the relaxation to be capped.

The default is TOTAL_DEVIATION.

Densifying variables

class opvious.transformations.DensifyVariables(labels: list[str] = <factory>)

Bases: ProblemTransformation

A transformation which updates one or more variables to be continuous

labels: list[str]

The labels of the variables to densify

If empty, all integral variables will be densified.

Omitting constraints and objectives

Warning

Transformations in this subsection are still WIP and will be available soon.

class opvious.transformations.OmitConstraints(labels: list[str] = <factory>)

Bases: ProblemTransformation

A transformation which drops one or more constraints

Any parameters or variables which are not referenced in any remaining constraint or objective will automatically be dropped.

labels: list[str]

The labels of the constraints to drop

If empty, all constraints will be dropped.

class opvious.transformations.OmitObjectives(labels: list[str] = <factory>)

Bases: ProblemTransformation

A transformation which drops one or more objectives

Similar to OmitConstraints, any parameters or variables which are not referenced in any remaining constraint or objective will automatically be dropped.

labels: list[str]

The labels of the objectives to drop

If empty, all objectives will be dropped.

Enforcing a minimum objective level

Warning

Transformations in this subsection are still WIP and will be available soon.

class opvious.transformations.ConstrainObjective(target: Target, min_value: float = -inf, max_value: float = inf)

Bases: ProblemTransformation

A transformation which bounds the value of a objective

This can be used for example to guarantee a minimum objective levels when multiple objectives are involved or to implement custom multi-objective strategies (see Weighted distance multi-objective optimization).

max_value: float = inf

The objective’s maximum allowed value

min_value: float = -inf

The objective’s minimum allowed value

target: Target

The label of the objective to constrain

Transformation patterns

This section highlights a few common patterns built from the above base transformations.

Detecting infeasibilities

await client.run_solve(
    # ...
    transformations=[
        opvious.transformations.OmitObjectives(), # Drop existing objectives
        opvious.transformations.RelaxConstraints(), # Relax all constraints
    ],
    strategy=opvious.SolveStrategy.equally_weighted_sum("MINIMIZE"),
)

Solution smoothing

await client.run_solve(
    # ...
    transformations=[
        opvious.transformations.PinVariables(["production"]),
        opvious.transformations.RelaxConstraints(["production_isPinned"]),
    ],
    strategy=opvious.SolveStrategy({
        "production_isPinned_minimizeDeficit": 100, # Smoothing factor
        # ... Other objectives
    }),
)

Weighted distance multi-objective optimization

await client.run_solve(
    # ...
    transformations=[
        # Set the target values
        opvious.transformations.ConstrainObjective("foo", min_value=5),
        opvious.transformations.ConstrainObjective("bar", max_value=10),
        # Replace the original objectives
        opvious.transformations.OmitObjectives(["foo", "bar"]),
        opvious.transformations.RelaxConstraints([
            "foo_isAtLeastMinimum",
            "bar_isAtMostMaximum",
        ]),
    ],
    strategy=opvious.SolveStrategy.equally_weighted_sum(),
)