Penalty Coefficients

PenaltyCoefficient objects are used to represent the penalty coefficients \(\vc_{\vg}\) and \(\vc_{\vh}\), required by formulations such as the QuadraticPenalty and AugmentedLagrangian formulations.

In Cooper, penalty coefficients are wrappers around a torch.Tensor. Notably, they do not require gradients, as they are not optimized.

The cooper.penalty_coefficients module provides the following types of penalty coefficients:

Linking constraints and penalty coefficients

Constraint objects require an associated PenaltyCoefficient object to be passed to the constructor when the problem formulation demands it. You can check this requirement using the expects_penalty_coefficient attribute of a Formulation subclass.

penalty_coefficient = ...
constraint = cooper.Constraint(
    penalty_coefficient=penalty_coefficient,
    constraint_type=cooper.ConstraintType.INEQUALITY,
    formulation_type=cooper.formulations.QuadraticPenalty,
)

Note

The helper methods CMP.penalty_coefficients and CMP.named_penalty_coefficients allow iteration over the penalty coefficients associated with constraints registered in a CMP. For more details, see Registering constraints in a CMP.

Consider the following Quadratic Penalty formulation of a constrained optimization problem:

\[ \Lag^{\text{QP}}_{\vc_g, \vc_h}(\vx) = f(\vx) + \frac{1}{2} \vc_{\vg}^\top \, \texttt{relu}(\vg(\vx))^2 + \frac{1}{2} \vc_{\vh}^\top \, \vh(\vx)^2, \]

where \(\vc_{\vg}\) and \(\vc_{\vh}\) are the penalty coefficients associated with the inequality and equality constraints, respectively.

In Cooper, PenaltyCoefficient objects represent the vectors \(\vc_{\vg}\) and \(\vc_{\vh}\), with one coefficient for each constraint. Alternatively, Cooper also supports scalar-valued penalty coefficients, which apply a shared coefficient across all constraints (i.e., \(\vc_{\vg} = c_g \mathbf{1}\) and \(\vc_{\vh} = c_h \mathbf{1}\)).

Since it is often desirable to increase the penalty coefficient over the optimization process, Cooper provides a scheduler mechanism to do so. For more information, see Penalty Coefficient Updaters.

Initialization

To initialize a PenaltyCoefficient, you can pass either a torch.Tensor of shape (num_constraints, ) or a scalar tensor to the init argument.

penalty_coefficient = cooper.penalty_coefficients.DensePenaltyCoefficient(
    init=torch.ones(10, device="cuda", dtype=torch.float32)
)

penalty_coefficient = cooper.penalty_coefficients.IndexedPenaltyCoefficient(
    init=torch.tensor(1.0, device="cuda", dtype=torch.float32)
)

Evaluating a PenaltyCoefficient

Similar to multipliers, penalty coefficients can be evaluated using __call__(). For example:

# `DensePenaltyCoefficient`s do not require arguments during evaluation
penalty_coefficient_value = penalty_coefficient()

# `IndexedPenaltyCoefficient`s require indices for evaluation
indices = torch.tensor([1, 2, 4, 6])
penalty_coefficient_value = penalty_coefficient(indices)
class cooper.penalty_coefficients.PenaltyCoefficient(init)[source]

Abstract class for constant (non-trainable) penalty coefficients.

Parameters:

init (Tensor) – Value of the penalty coefficient.

Raises:

ValueError – If init has two or more dimensions.

property value: Tensor

Return the current value of the penalty coefficient.

to(*args, **kwargs)[source]

Move the penalty coefficient to a new device and/or change its dtype.

Return type:

Self

state_dict()[source]

Return the current state of the penalty coefficient.

Return type:

dict

load_state_dict(state_dict)[source]

Load the state of the penalty coefficient.

Parameters:

state_dict (dict) – Dictionary containing the state of the penalty coefficient.

Return type:

None

sanity_check()[source]

Check that the penalty coefficient is well-formed.

Raises:

ValueError – If the penalty coefficient contains negative entries.

Return type:

None

abstract __call__(*args, **kwargs)[source]

Return the current value of the penalty coefficient.

Return type:

Tensor

Dense Penalty Coefficients

The DensePenaltyCoefficient class wraps a tensor of penalty coefficients, ensuring that all coefficients are accessed during each evaluation.

class cooper.penalty_coefficients.DensePenaltyCoefficient(init)[source]

Constant (non-trainable) coefficient class used for Augmented Lagrangian formulation.

__call__()[source]

Return the current value of the penalty coefficient.

Return type:

Tensor

Indexed Penalty Coefficients

Similar to IndexedMultipliers, IndexedPenaltyCoefficients allow fetching and updating the penalty coefficients by index. Given indices idx, the __call__() method of an IndexedPenaltyCoefficient object returns the penalty coefficients corresponding to the indices in idx.

class cooper.penalty_coefficients.IndexedPenaltyCoefficient(init)[source]

Constant (non-trainable) penalty coefficients. When called, indexed penalty coefficients accept a tensor of indices and return the value of the penalty for a subset of constraints.

__call__(indices)[source]

Return the current value of the penalty coefficient at the provided indices.

Parameters:

indices (Tensor) – Tensor of indices for which to return the penalty coefficient.

Raises:

ValueError – If indices is not of type torch.long.

Return type:

Tensor

Checkpointing

To save the current penalty coefficients of a CMP, use the state_dict() method to create a state checkpoint. Later, you can restore this state using load_state_dict(). This process captures the multiplier and penalty coefficient values (see CMP Checkpointing for details).

Penalty Coefficient Updaters

Penalty coefficient updaters are used to adjust penalty coefficients during optimization based on measurements of constraint violations. Cooper provides two feasibility-driven updaters: MultiplicativePenaltyCoefficientUpdater and AdditivePenaltyCoefficientUpdater, both of which increase the penalty coefficients when constraint violations exceed a specified tolerance.

class cooper.penalty_coefficients.PenaltyCoefficientUpdater[source]

Abstract class for updating the penalty coefficient of a constraint.

step(observed_constraints)[source]

Trigger updates on the penalty coefficients for each of the observed_constraints.

For each constraint in observed_constraints, this method determines whether its penalty coefficient should be updated. The decision depends on properties like whether the constraint contributes to primal/dual updates and the availability of strict violation measurements.

Primal vs Dual Contributions

  • For formulations expecting multipliers (e.g., AugmentedLagrangian), updates occur if:

    • The constraint contributes to the dual update, OR

    • It contributes to the primal update and has a strict violation measurement.

  • For primal-only formulations (e.g., QuadraticPenalty), updates occur only if the constraint contributes to the primal update.

Parameters:

observed_constraints (dict[Constraint, ConstraintState]) – Dictionary with Constraint instances as keys and ConstraintState instances as values (containing tensors \(\vg(\vx_t)\) and \(\vh(\vx_t)\)).

Return type:

None

Multiplicative Penalty Coefficient Updater

The MultiplicativePenaltyCoefficientUpdater multiplies the penalty coefficient by a growth factor when the corresponding entries in violation exceed a prescribed tolerance.

class cooper.penalty_coefficients.MultiplicativePenaltyCoefficientUpdater(growth_factor=1.01, violation_tolerance=0.0001, has_restart=True)[source]

Multiplicative updater for PenaltyCoefficients.

The penalty coefficient is updated by multiplying it by growth_factor when the constraint violation is larger than violation_tolerance.

Based on Algorithm 17.4 in Nocedal and Wright [NW06].

Parameters:
  • growth_factor (float) – The factor by which the penalty coefficient is multiplied when the constraint is violated beyond violation_tolerance.

  • violation_tolerance (float) – The tolerance for the constraint violation. If the violation is smaller than this tolerance, the penalty coefficient is not updated. The comparison is done at the constraint-level (i.e., each entry of the violation tensor). For equality constraints, the absolute violation is compared to the tolerance. All constraint types use the strict violation (when available) for the comparison.

  • has_restart (bool) – Whether to restart the penalty coefficient to its initial value when the inequality constraint is satisfied. This is only applicable to inequality constraints.

Raises:

ValueError – If the violation tolerance is negative.

Additive Penalty Coefficient Updater

The AdditivePenaltyCoefficientUpdater increases the penalty coefficient by a fixed amount when the corresponding entries in violation exceed a prescribed tolerance.

class cooper.penalty_coefficients.AdditivePenaltyCoefficientUpdater(increment=1.0, violation_tolerance=0.0001, has_restart=True)[source]

Additive updater for PenaltyCoefficients.

The penalty coefficient is updated by adding increment when the constraint violation is larger than violation_tolerance.

Parameters:
  • increment (float) – The constant value by which the penalty coefficient is added when the constraint is violated beyond violation_tolerance.

  • violation_tolerance (float) – The tolerance for the constraint violation. If the violation is smaller than this tolerance, the penalty coefficient is not updated. The comparison is done at the constraint-level (i.e., each entry of the violation tensor). For equality constraints, the absolute violation is compared to the tolerance. All constraint types use the strict violation (when available) for the comparison.

  • has_restart (bool) – Whether to restart the penalty coefficient to its initial value when the inequality constraint is satisfied. This is only applicable to inequality constraints.

Raises:

ValueError – If the violation tolerance is negative.

Using Penalty Coefficient Updaters

To use a PenaltyCoefficientUpdater, follow these steps:

  1. Instantiate the updater with the desired parameters.

  2. Call penalty_updater.step(), providing the observed constraints.

Example:

penalty_updater = cooper.penalty_coefficients.MultiplicativePenaltyCoefficientUpdater(
    growth_factor=2.0,
    violation_tolerance=1e-3,
    has_restart=True
)

roll_out = cooper_optimizer.roll(...)
penalty_updater.step(roll_out.cmp_state.observed_constraints)