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-negativeproduction
variable would have a non-negative parameter labeledproduction_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 labeledproduction
, 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 labeledisBalanced_deficit
andisBalanced_surplus
. A greater than constraint labeleddemandIsMet
would be relaxed into a single variable labeleddemandIsMet_deficit
.Each deficit (resp. surplus) variable has a corresponding objective labeled
$label_minimizeDeficit
(resp.$label_minimizeSurplus
). Refer to thepenalty
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(),
)