Step operators¶
Step operators are the building blocks of a first-order method, covering affine steps, ReLU, saturated linear, and soft thresholding.
Step
¶
A base class for thresholding and activation steps. This class defines a common interface for step-like operators that transform inputs element-wise.
A step represents the relation: output = f(arg), where f is the step function (e.g., ReLU, soft threshold, saturated linear).
Attributes:
| Name | Type | Description |
|---|---|---|
arg |
LinearExpression
|
Right-hand side (input) linear expression. |
output |
LinearExpression
|
Left-hand side (output) linear expression. Bounds on the output
are stored in |
relax_binary_vars |
bool
|
If True, binary decision variables associated with the operator are relaxed to take continuous values. |
skip_obbt |
bool
|
If True, OBBT bound tightening is skipped for this step type. Override in subclasses that compute bounds analytically. |
obbt_output |
bool
|
If true, OBBT is also run on the step's output leaves once the step's
relaxation is in the OBBT LP. Enabled for |
Notes
Input vs output bounds:
- step.lb, step.ub: bounds on the INPUT (arg)
- step.output.lb, step.output.ub: bounds on the OUTPUT (output)
When update_bounds(lb, ub) is called with tightened input bounds,
subclasses should update both self.lb/self.ub (input) and
self.output.lb/self.output.ub (output, by applying the step function).
Source code in fomtools/steps/step.py
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
output
cached
property
¶
output: LinearExpression
Left-hand side linear expression in the operator definition.
output_leaves
property
¶
output_leaves: list[LinearExpression]
Leaves this step introduces and that must be registered with the canonicalizer/step decomposer.
Default: [self.output] (the output is itself a leaf). Steps that expose
a non-leaf composite output (currently only partial-projection
Relu) override this to return the smaller fresh leaf actually
introduced.
Affine
¶
Bases: Step
An affine operator of the following form:
where \(M \in \mathbf{R}^{ d \times d }\) is an invertible matrix and \(B \in \mathbf{R}^{d \times (p+d)}\).
It can also be constructed via weight and optional bias to represent
the explicit transform \(w = W \cdot \text{arg} + b\), where arg is the
input expression. In this form output_mat is set to identity internally.
Attributes:
| Name | Type | Description |
|---|---|---|
output |
LinearExpression
|
Left-hand side linear expression in the operator definition. |
arg |
LinearExpression
|
Right-hand side linear expression in the operator definition. |
lb |
ndarray
|
Lower bounds on the INPUT (arg), before the affine transform. |
ub |
ndarray
|
Upper bounds on the INPUT (arg), before the affine transform. |
relax_binary_vars |
bool
|
If True, binary decision variables associated with the operator are relaxed to take continuous values. |
output_mat |
matrix
|
Coefficient matrix on the left-hand side of the operator equation. |
obbt_output |
bool
|
True when |
Source code in fomtools/steps/affine.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
output_mat_inverse
cached
property
¶
output_mat_inverse: LinearExpression
Return a LinearExpression formed by applying \(A^{-1}\), where \(A\) is
self.output_mat, to each right-hand-side component \(b_k\)—i.e.,
solving \(A x_k = b_k\) for all \(k\).
Supports both dense and sparse output_mat.
sync_output_bounds
¶
sync_output_bounds() -> None
Tighten output bounds from the current input bounds.
Interval-propagates output_mat_inverse (built by linear solves, never an
explicit inverse). When output_mat is the identity, output == arg, so
the (possibly OBBT-tightened) input box self.lb/self.ub also bounds the
output directly. output.update_bounds intersects with the existing output
bounds, so tightening applied earlier is never discarded. For general
output_mat the tightened input box is exploited by OBBT on the output leaf
(see StepDecomposer._process_primitive).
Source code in fomtools/steps/affine.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
update_bounds
¶
update_bounds(lb: float | ndarray, ub: float | ndarray)
Update INPUT bounds (on arg) and propagate analytically to OUTPUT bounds on output.
Source code in fomtools/steps/affine.py
77 78 79 80 81 82 | |
Relu
¶
Bases: Step
Rectified Linear Unit (ReLU) step operator, defined as:
It sets all negative values to zero while keeping the positive ones unchanged.
Attributes:
| Name | Type | Description |
|---|---|---|
arg |
LinearExpression
|
Right-hand side (input) linear expression. |
lb |
ndarray
|
Lower bounds on the INPUT (arg), before ReLU is applied. |
ub |
ndarray
|
Upper bounds on the INPUT (arg), before ReLU is applied. |
relax_binary_vars |
bool
|
If True, binary decision variables associated with the operator are relaxed to take continuous values. |
idx_with_binary_vars |
set[int]
|
Indices corresponding to binary decision variables. |
proj_indices |
set[int]
|
Indices of output coordinates subjected to ReLU projection. |
Notes
Partial projection (proj_ranges covers only a subset of the input
coordinates) is encoded with a composite output: only
len(proj_indices) fresh leaf variables are introduced (stored on
self._output_proj_leaf), and the non-projected output coordinates
share decomposition with arg directly. This keeps the SDP variable
count, MIP variable count, and simulator leaf count at the minimum.
For the full-projection default, output remains a single leaf of size
arg.ndim (current behavior).
Source code in fomtools/steps/relu.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
output_leaves
property
¶
output_leaves: list[LinearExpression]
For partial projection, only _output_proj_leaf is fresh; non-proj
rows of the composite output reuse the arg's leaves. For full projection,
the output is itself a single fresh leaf.
relu
¶
relu(x: ndarray) -> ndarray
Apply ReLU activation selectively to specified indices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
ndarray
|
A 1-D array of values on which to apply the activation. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
A new array where elements at positions specified in
|
Source code in fomtools/steps/relu.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
update_bounds
¶
update_bounds(lb: float | ndarray, ub: float | ndarray)
Update the bounds of the left-hand side after applying the ReLU activation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lb
|
float | ndarray
|
The new lower bound(s) before applying ReLU. |
required |
ub
|
float | ndarray
|
The new upper bound(s) before applying ReLU. |
required |
Source code in fomtools/steps/relu.py
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
SaturatedLinear
¶
Bases: Step
Saturated Linear Unit step operator, defined as:
where \(x, c \in \mathbb{R}\).
Attributes:
| Name | Type | Description |
|---|---|---|
arg |
LinearExpression
|
Right-hand side (input) linear expression. |
output |
LinearExpression
|
Left-hand side (output) linear expression. |
lb |
ndarray
|
Lower bounds on the INPUT (arg), before saturation is applied. |
ub |
ndarray
|
Upper bounds on the INPUT (arg), before saturation is applied. |
relax_binary_vars |
bool
|
If True, binary decision variables associated with the operator are relaxed to take continuous values. |
l |
ndarray[float] | LinearExpression
|
The lower saturation bound of the saturated linear operator. Can be either a numeric value or a LinearExpression object whose lower bound acts as the lower saturation bound. |
u |
ndarray[float] | LinearExpression
|
The upper saturation bound of the saturated linear operator. Can be either a numeric value or a LinearExpression object whose upper bound acts as the upper saturation bound. |
Source code in fomtools/steps/saturated_linear.py
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
satlin_prop
¶
satlin_prop(x: ndarray, bound: str) -> ndarray
Propagate a bound through the saturated linear operator.
Since clip(x, l, u) is monotonically non-decreasing in all three arguments, the correct output bounds are: lower: clip(x_lb, l_lb, u_lb) upper: clip(x_ub, l_ub, u_ub)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
ndarray
|
A 1-D array of input bound values to propagate. |
required |
bound
|
str
|
Either "lb" or "ub", indicating which bound is being propagated. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The propagated bound on the output of the saturated linear operator. |
Source code in fomtools/steps/saturated_linear.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
update_bounds
¶
update_bounds(lb: float | ndarray, ub: float | ndarray)
Update the bounds of the left-hand side after applying the Saturated Linear Unit activation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lb
|
float | ndarray
|
The new lower bound(s) before applying the Saturated Linear Unit. |
required |
ub
|
float | ndarray
|
The new upper bound(s) before applying the Saturated Linear Unit. |
required |
Source code in fomtools/steps/saturated_linear.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
SoftThreshold
¶
Bases: Step
A soft-thresholding operator for shrinkage, defined as:
where \(\mathcal{T}_{\lambda}\) is the soft-thresholding operator with constant \(\lambda>0\), and \(a\in\mathbf{R}^d\).
Attributes:
| Name | Type | Description |
|---|---|---|
arg |
LinearExpression
|
Right-hand side (input) linear expression. |
output |
LinearExpression
|
Left-hand side (output) linear expression. |
lb |
ndarray
|
Lower bounds on the INPUT (arg), before soft-thresholding is applied. |
ub |
ndarray
|
Upper bounds on the INPUT (arg), before soft-thresholding is applied. |
relax_binary_vars |
bool
|
If True, binary decision variables associated with the operator are relaxed to take continuous values. |
lambd |
float
|
The threshold (\(\lambda\)) controlling shrinkage. Must be non-negative. |
idx_with_right_binary_vars |
set[int]
|
Indices tied to "right-branch" indicators in a MILP reformulation (e.g., \(t>\lambda\)). |
idx_with_left_binary_vars |
set[int]
|
Indices tied to "left-branch" indicators (e.g., \(t<-\lambda\)). |
Source code in fomtools/steps/soft_threshold.py
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
soft_threshold
¶
soft_threshold(x: ndarray) -> ndarray
Apply the soft-thresholding operator to the input array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
ndarray
|
A 1-D array of values on which to apply the activation. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
A new array obtained by applying the soft-thresholding
operator element-wise. Values with magnitude less than |
Source code in fomtools/steps/soft_threshold.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
update_bounds
¶
update_bounds(lb: float | ndarray, ub: float | ndarray)
Update the bounds of the left-hand side after applying the soft-thresholding operator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lb
|
float | ndarray
|
The new lower bound(s) before applying soft-thresholding. |
required |
ub
|
float | ndarray
|
The new upper bound(s) before applying soft-thresholding. |
required |
Source code in fomtools/steps/soft_threshold.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | |
QuadraticProgram
¶
Bases: Step
Step representing the solution of an arbitrary convex quadratic program
where \(P \in \mathbf{R}^{n\times n}\) is a fixed symmetric PSD matrix
(dense numpy or scipy.sparse), and q, b, d may be numeric
data or LinearExpression parameters (carrying dependence on prior
iterates). The verification-side lowering (decompose_quadraticprogram in
the step decomposer) posts the KKT system
with \(\nu\) a free equality multiplier and \(\lambda \ge 0\), using
the complementarity primitive. The MIP big-M encoding of the complementarity
needs a finite upper bound on \(\lambda\); supply it via dual_ub
whenever inequalities are present (OBBT tightens it further when enabled).
Invoked via Verifier.quadratic_program rather than the single-arg
run_step path: an arbitrary QP has up to three parametric inputs
(q, b, d), and each LinearExpression already references the
prior iterates it was built from, so no input is forced into an arg slot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
np.ndarray or scipy.sparse matrix
|
The |
required |
q
|
None, float, np.ndarray, or LinearExpression
|
Linear cost of dimension |
None
|
A
|
ndarray or sparse
|
Equality matrix of shape |
None
|
b
|
float, np.ndarray, or LinearExpression
|
Equality right-hand side of dimension |
None
|
C
|
ndarray or sparse
|
Inequality matrix of shape |
None
|
d
|
float, np.ndarray, or LinearExpression
|
Inequality right-hand side of dimension |
None
|
dual_ub
|
None, float, or np.ndarray
|
Finite upper bound(s) on the inequality dual |
None
|
primal_bounds
|
tuple[ndarray, ndarray]
|
|
None
|
nu_bounds
|
tuple[ndarray, ndarray]
|
|
None
|
arg
|
LinearExpression
|
The projection point used when |
None
|
relax_binary_vars
|
bool
|
Forwarded to the Step interface (relax big-M binaries when set). |
False
|
Source code in fomtools/steps/quad_prog.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
output
cached
property
¶
output: LinearExpression
Output is the QP primal solution x of dimension n (!= arg.ndim).
solve
¶
solve(q=None, b=None, d=None) -> ndarray
Numerically solve the QP with cvxpy (reference forward-evaluation).
Numeric overrides for q/b/d may be supplied for the case where
the stored values are LinearExpression parameters; otherwise the stored
numeric data is used. P is densified for cvxpy.
Returns:
| Type | Description |
|---|---|
ndarray
|
The optimal primal |
Source code in fomtools/steps/quad_prog.py
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
update_bounds
¶
update_bounds(lb, ub)
Store primal bounds (interface compliance; QP is a composite step that
bypasses _process_primitive, so this is rarely invoked).
Source code in fomtools/steps/quad_prog.py
169 170 171 172 173 | |
LinearProgram
¶
Bases: QuadraticProgram
Step representing the solution of a linear program
A QuadraticProgram specialization with P = 0 (stored as a sparse
zero matrix). The KKT system drops the quadratic term: stationarity becomes
the dual-only equality \(q + A^\top \nu + C^\top \lambda = 0\), so the
primal x is pinned by primal feasibility + complementarity rather than by
stationarity. Because stationarity no longer bounds x, finite primal
bounds must come from primal_bounds or OBBT over {Ax = b, Cx <= d}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
q
|
ndarray or LinearExpression
|
Linear cost; its dimension defines |
required |
A
|
See |
None
|
|
b
|
See |
None
|
|
C
|
See |
None
|
|
d
|
See |
None
|
|
dual_ub
|
See |
None
|
|
primal_bounds
|
See |
None
|
|
nu_bounds
|
See |
None
|
|
arg
|
See |
None
|
|
relax_binary_vars
|
See |
None
|
Source code in fomtools/steps/lin_prog.py
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
StandardFormLinearProgram
¶
Bases: LinearProgram
Step representing the solution of a standard-form linear program
A LinearProgram with q = c, equality (A, b), and the non-negativity
constraint x >= 0 written as the inequality -I x <= 0 (C = -I, d = 0).
No new lowering logic — it reuses the entire LinearProgram / _qp_kkt machinery
(the step decomposer resolves it to the LinearProgram decomposer via the class MRO).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
c
|
ndarray or LinearExpression
|
Linear cost; its dimension defines |
required |
A
|
optional
|
Equality matrix |
None
|
b
|
optional
|
Equality matrix |
None
|
dual_ub
|
See |
None
|
|
primal_bounds
|
See |
None
|
|
nu_bounds
|
See |
None
|
|
arg
|
See |
None
|
|
relax_binary_vars
|
See |
None
|
Source code in fomtools/steps/lin_prog.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |