Multipliers
Multiplier objects represent the dual variables \( \vlambda \) and \( \vmu \) in the constrained problem. They are required by certain formulations, such as Lagrangian and AugmentedLagrangian.
In a generic formulation \( \Lag \), the dual variables correspond to the inner maximization variables:
In Cooper, multipliers are implemented as torch.nn.Modules, ensuring compatibility with PyTorch’s autograd capabilities. They are evaluated via a forward() call.
The cooper.multipliers module provides three types of multipliers:
DenseMultiplier: Represents each multiplier individually, with each entry in the multiplier vector corresponding to a separate constraint.IndexedMultiplier: Similar toDenseMultiplier, but supports efficient indexing. This is useful for scenarios where constraints are sampled, allowing for sparse multiplier accessing and updates (see Constraint Sampling).ImplicitMultiplier: Instead of storing multipliers explicitly,ImplicitMultipliers compute their values through aforwardcall on an arbitrarytorch.nn.Module. This is particularly useful when the number of constraints is too large to maintain individual multipliers.
Linking constraints and multipliers
Constraint objects require an associated Multiplier when the problem formulation demands it. You can check this using the expects_multiplier attribute of a Formulation sub-class. To ensure compliance, pass a Multiplier object to the Constraint constructor.
multiplier = ...
constraint = cooper.Constraint(
multiplier=multiplier,
constraint_type=cooper.ConstraintType.INEQUALITY,
formulation_type=cooper.formulations.Lagrangian,
)
Note
The helper methods CMP.multipliers and CMP.named_multipliers allow iteration over the multipliers associated with constraints registered in a CMP.
For more details, see Registering constraints in a CMP.
Explicit (Non-Parametric) Multipliers
Consider the following Lagrangian formulation of a constrained optimization problem:
where \(\vlambda = [\lambda_i]_{i=1}^m\) and \(\vmu = [\mu_i]_{i=1}^n\) are the Lagrange multipliers associated with the equality and inequality constraints, respectively.
ExplicitMultiplier objects represent the vectors \(\vlambda\) and \(\vmu\) directly, by storing one decision variable per constraint.
Cooper provides two types of explicit multipliers: DenseMultipliers and IndexedMultipliers.
Initialization
To create an ExplicitMultiplier, you must specify either (i) the number of associated constraints or (ii) an initial value for each multiplier entry.
The example below illustrates how to construct multiplier objects. Note that the syntax is consistent between DenseMultiplier and IndexedMultiplier.
# When specifying the number of constraints, all multipliers are initialized to zero
multiplier = cooper.multipliers.DenseMultiplier(
num_constraints=3,
device=torch.device("cpu"),
dtype=torch.float32,
)
# When `init` is provided, `num_constraints` is inferred from its shape
multiplier = cooper.multipliers.IndexedMultiplier(
init=torch.ones(7),
device=torch.device("cuda"),
dtype=torch.float16,
)
Evaluating an ExplicitMultiplier
Cooper stores multiplier values in the weight attribute, which can be accessed for inspecting their behavior.
However, to leverage PyTorch’s autograd functionality, we recommend evaluating multipliers via their forward() method. For example:
# `DenseMultiplier`s do not require arguments during evaluation
multiplier_value = multiplier()
# `IndexedMultiplier`s require indices for evaluation
indices = torch.tensor([0, 2, 4, 6])
multiplier_value = multiplier(indices)
- class cooper.multipliers.ExplicitMultiplier(num_constraints=None, init=None, device=None, dtype=torch.float32)[source]
An ExplicitMultiplier holds a
torch.nn.parameter.Parameter(weight) which explicitly contains the value of the Lagrange multipliers associated with aConstraintin aConstrainedMinimizationProblem.- Parameters:
num_constraints (
Optional[int]) – Number of constraints associated with the multiplier.init (
Optional[Tensor]) – Tensor used to initialize the multiplier values. If bothinitandnum_constraintsare provided,initmust have shape(num_constraints,).device (
Optional[device]) – Device for the multiplier. IfNone, the device is inferred from theinittensor or the default device.dtype (
dtype) – Data type for the multiplier. Default istorch.float32.
- static initialize_weight(num_constraints, init, device=None, dtype=torch.float32)[source]
Initialize the weight of the multiplier. If both
initandnum_constraintsare provided (and the shapes are consistent),inittakes precedence. Otherwise, the weight is initialized totorch.zeros()of shape(num_constraints,).- Raises:
ValueError – If both
num_constraintsandinitareNone.ValueError – If both
num_constraintsandinitare provided but their shapes are inconsistent.ValueError – If the provided
initis not a 1D tensor.
- Return type:
- sanity_check()[source]
Ensures multipliers for inequality constraints are non-negative.
- Raises:
ValueError – If the multiplier is associated with an inequality constraint and any of its entries is negative.
- Return type:
Dense Multipliers
DenseMultiplier objects are ideal for problems with a small to medium number of constraints, where all constraints are measured or observed at each iteration.
We refer to this type of multiplier as dense because all multipliers are utilized at every optimization step (e.g., during the computation of the Lagrangian), as opposed to only a subset being used.
A DenseMultiplier is essentially a wrapper around a torch.Tensor to provide an interface consistent with other types of multipliers.
It implements the forward() method, which returns all multipliers as a single tensor. This method takes no arguments.
For large-scale Constraint objects (e.g., one constraint per training example) or problems where constraints are sampled and not all constraints are observed simultaneously, consider using an IndexedMultiplier or ImplicitMultiplier instead.
Indexed Multipliers
Like DenseMultipliers, IndexedMultipliers represent the multiplier tensors directly, but allow efficiently accessing and updating specific entries by index.
IndexedMultiplier objects are designed for situations where only a subset of constraints are observed at each iteration, rather than all constraints. This approach is particularly useful when the number of constraints is large, such as in tasks where a constraint is imposed for each data. In such cases, measuring all constraints simultaneously can be computationally prohibitive.
IndexedMultiplier objects model \(\vlambda\) and \(\vmu\) explicitly, just like DenseMultipliers, but allow fetching and updating them by index. Given indices idx, the forward() method of an IndexedMultiplier object returns the multipliers corresponding to the indices in idx. IndexedMultipliers enable time-efficient retrieval of multipliers for only the sampled constraints, while also supporting memory-efficient sparse gradients (on GPU).
To use an IndexedMultiplier, after computing the constraints you must provide the observed constraint indices to the constraint_features argument of the ConstraintState(). Cooper will then know which multipliers to fetch and update during optimization. For example, if you measured the constraints at indices 0, 11, and 17, you would set the constraint_features attribute as follows:
observed_violation_tensor = torch.tensor([3, 1, 4])
observed_constraint_indices = torch.tensor([0, 11, 17])
constraint_state = cooper.ConstraintState(
violation=observed_violation_tensor,
constraint_features=observed_constraint_indices
)
Warning
The forward() call of an IndexedMultiplier expects a list of indices corresponding to the constraints whose multipliers you want to fetch. If you want to fetch all multipliers, you must provide a list of all constraint indices.
- class cooper.multipliers.IndexedMultiplier(num_constraints=None, init=None, device=None, dtype=torch.float32)[source]
ExplicitMultiplierfor indexed constraints which are evaluated only for a subset of constraints on every optimization step.- forward(indices)[source]
Return the current value of the multiplier at the provided indices.
- Parameters:
indices (
Tensor) – Indices of the multipliers to return. The shape ofindicesmust be(num_indices,).- Raises:
ValueError – If
indicesdtype is nottorch.long.- Return type:
Implicit (Parametric) Multipliers
Rather than maintaining a separate learnable parameter for each multiplier, it can be more practical to model the multipliers as functions of the constraints. This approach is particularly beneficial when the number of constraints is large, as explicitly maintaining a Lagrange multiplier for each constraint can be computationally expensive or infeasible.
We can represent the multipliers functionally as follows: \(\vlambda_{\phi}: \mathbb{R}^d \to \mathbb{R}^m\) and \(\vmu_{\psi}: \mathbb{R}^d \to \mathbb{R}^n\). In this representation, \(d\) refers to the dimensionality of the constraint feature space, while \(\phi\) and \(\psi\) are the parameters of the multiplier functions. These functions calculate the multiplier values based on the input constraint features.
To support this functional approach, Cooper provides the ImplicitMultiplier class. To use it, you must implement its forward() method, which takes a tensor of constraint features as input and returns the corresponding multiplier values. For example, to define a linear model for the multipliers, you can write:
import torch
import torch.nn as nn
class ImplicitMultiplier(nn.Module):
def __init__(self, input_dim, output_dim):
super().__init__()
self.linear = nn.Linear(input_dim, output_dim)
def forward(self, constraint_features):
return self.linear(constraint_features)
This approach can be extended to more complex models, such as neural networks.
Much like the IndexedMultiplier, you need to provide the constraint features associated with the observed constraints to the constraint_features attribute of the ConstraintState. Cooper will then perform forward and backward passes through the multiplier model and update its parameters accordingly.
Note
Due to their functional nature, implicit multipliers can (approximately) represent infinitely many constraints. This capability is inspired by the “Lagrange multiplier model” proposed by [NCZ+20].
Warning
Because of the high flexibility of implicit multipliers, the post_step_ method is not implemented in the base class. For applications involving inequality constraints and implicit multipliers, you must implement the logic to ensure the non-negativity of the multipliers associated with inequality constraints.
- class cooper.multipliers.ImplicitMultiplier(*args, **kwargs)[source]
An implicit multiplier is a
torch.nn.Modulethat computes the value of a Lagrange multiplier associated with aConstraintbased on the “features” for each constraint. The multiplier is implicitly represented by its parameters.
Base Class
- class cooper.multipliers.Multiplier(*args, **kwargs)[source]
Checkpointing
To save the current multipliers 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).