Source code for CADETProcess.parameter_space.constraints
"""Linear constraints for ParameterSpace.
Both classes represent affine constraints over a subset of parameters.
They hold coefficients and references to the parameters they constrain;
the space assembles the full matrix form for consumption by optimizers.
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import Union
import numpy as np
from CADETProcess.parameter_space.parameters import ParameterBase
__all__ = [
"LinearConstraint",
"LinearEqualityConstraint",
]
def _normalize_lhs(
parameters: list[ParameterBase],
lhs: Union[float, list[float]],
label: str,
) -> list[float]:
"""Expand a scalar lhs to a per-parameter list and validate length."""
if np.isscalar(lhs):
return [float(lhs)] * len(parameters)
lhs = list(lhs)
if len(lhs) != len(parameters):
raise ValueError(
f"{label}: lhs has {len(lhs)} coefficients but "
f"{len(parameters)} parameters were given."
)
return [float(c) for c in lhs]
[docs]
class LinearConstraint:
"""Linear inequality constraint: ``lhs · x <= b``.
Parameters
----------
parameters : ParameterBase or sequence of ParameterBase
Parameters involved in the constraint. Validated as numeric
(``RangedParameter``) when registered with ``ParameterSpace``.
lhs : float or list[float]
Coefficients. A scalar is broadcast to all parameters.
b : float
Right-hand side.
"""
def __init__(
self,
parameters: Union[ParameterBase, list[ParameterBase]],
lhs: Union[float, list[float]] = 1.0,
b: float = 0.0,
) -> None:
if isinstance(parameters, ParameterBase):
parameters = [parameters]
elif not isinstance(parameters, Sequence):
raise TypeError(
f"parameters must be a ParameterBase or a sequence thereof, "
f"got {type(parameters).__name__}."
)
else:
parameters = list(parameters)
if len({p.name for p in parameters}) != len(parameters):
raise ValueError("Duplicate parameters in constraint.")
self.parameters = parameters
self.lhs = _normalize_lhs(parameters, lhs, "LinearConstraint")
self.b = float(b)
def __repr__(self) -> str:
"""Return a readable representation."""
names = [p.name for p in self.parameters]
return f"LinearConstraint(parameters={names}, lhs={self.lhs}, b={self.b})"
[docs]
class LinearEqualityConstraint:
"""Linear equality constraint: ``lhs · x = b``.
Parameters
----------
parameters : ParameterBase or sequence of ParameterBase
Parameters involved in the constraint. Validated as numeric
(``RangedParameter``) when registered with ``ParameterSpace``.
lhs : float or list[float]
Coefficients. A scalar is broadcast to all parameters.
b : float
Right-hand side.
"""
def __init__(
self,
parameters: Union[ParameterBase, list[ParameterBase]],
lhs: Union[float, list[float]] = 1.0,
b: float = 0.0,
) -> None:
if isinstance(parameters, ParameterBase):
parameters = [parameters]
elif not isinstance(parameters, Sequence):
raise TypeError(
f"parameters must be a ParameterBase or a sequence thereof, "
f"got {type(parameters).__name__}."
)
else:
parameters = list(parameters)
if len({p.name for p in parameters}) != len(parameters):
raise ValueError("Duplicate parameters in constraint.")
self.parameters = parameters
self.lhs = _normalize_lhs(parameters, lhs, "LinearEqualityConstraint")
self.b = float(b)
def __repr__(self) -> str:
"""Return a readable representation."""
names = [p.name for p in self.parameters]
return (
f"LinearEqualityConstraint(parameters={names}, lhs={self.lhs}, "
f"b={self.b})"
)