base
Abstract base for autotuning strategies.
:class:AutotuningBase is the contract every penalty-weight update rule
implements: meth:
~AutotuningBase.update_weights is a pure functional
update on an :class:~openscvx.algorithms.state.AlgorithmState pytree, so the
SCP body composes with jax.jit / jax.vmap / jax.grad. The base also
houses :class:LamCostRelaxHyper, the lam_cost relaxation knobs every
built-in autotuner shares, and :meth:AutotuningBase._relaxed_lam_cost, the
single implementation of that rule.
This module mirrors the package's base.py-is-the-ABC idiom inside the
autotuner subpackage; the concrete autotuners live alongside it.
AutotuningBase
¶
Bases: ABC
Base class for autotuning strategies in SCP algorithms.
Subclasses implement meth:
update_weights as a pure functional
update on an :class:AlgorithmState pytree. Concretely, an autotuner
must:
- Not mutate
stateorcandidate. - Not return strings, raise on data-dependent conditions, or append to Python lists at trace time.
- Express all branching on iterate values via
jax.lax.cond/jnp.where.
The autotuner's per-iteration outcome is encoded as an
:class:AdaptiveStateCode int32 on the returned state; the SCP loop
converts that back to a human-readable label only on the Python-loop
printing path.
The returned state IS the next iterate. The SCP loop discards
everything except what update_weights returns, so accepting the
subproblem's candidate means carrying its trajectory onto the returned
state — x / u / x_prop / x_prop_plus from candidate —
while rejecting means keeping the previous fields and adjusting only the
weights. An update that never copies the candidate produces a solver that
runs to k_max without the iterate ever moving, silently.
Declaring tunable hyperparameters. A numeric knob a user might sweep
(a penalty ramp, a relaxation iteration) must not be read off self
inside update_weights — a Python attribute is baked into the trace
and invisible to every override channel. Instead, declare it on a
:class:HyperParams subclass, assign an instance to self.hyper, and
read it from state.hyper::
class MyHyper(HyperParams):
ramp: float = 2.0
class MyAutotuner(AutotuningBase):
def __init__(self, ramp: float = 2.0):
self.hyper = MyHyper(ramp=ramp)
def update_weights(self, state, candidate, nodal_constraints, settings, params):
return state.replace(
# accept the candidate: it becomes the next iterate
x=candidate.x,
u=candidate.u,
x_prop=candidate.x_prop,
x_prop_plus=candidate.x_prop_plus,
lam_prox=state.lam_prox * state.hyper.ramp,
adaptive_state_code=...,
)
The declaration is the registration: the field becomes a per-solve
override (solve_jax(algorithm={"ramp": ...})), a batchable sweep
target (solve_batched(algorithm={"ramp": jnp.linspace(...)})), and a
runtime input of the exported batched artifact — with zero core edits.
Every numeric knob of the built-in autotuners is declared this way, so the
only configuration left outside hyper is structural choices that select
code paths (class attributes like COMPUTES_ACCEPTANCE_METRICS); they
are part of the traced program, not data. For ergonomics, declared knobs
are still readable and writable as bare attributes
(autotuner.ramp = 3.0): the proxy below routes the access into
hyper so a constructor — or a test — may keep assigning knobs by name.
Assign self.hyper first in __init__; a knob assigned before
hyper exists lands as a plain instance attribute the proxy cannot see.
Class Attributes
COLUMNS: List of Column specs for autotuner-specific metrics to display.
Subclasses override this to add their own columns.
COMPUTES_ACCEPTANCE_METRICS: Whether update_weights computes a
predicted/actual reduction and the resulting acceptance ratio. The
SCP loop records and prints those diagnostics only when this is
True (the default); tuners that never reject an iterate — e.g.
:class:~openscvx.algorithms.autotuner.constant_proximal_weight.ConstantProximalWeight
and :class:~openscvx.algorithms.autotuner.ramp_proximal_weight.RampProximalWeight
— set it False.
hyper: The autotuner's declared hyperparameters — a
:class:HyperParams instance carrying plain-Python defaults
(the empty base when it declares none). Snapshotted onto
AlgorithmState.hyper with array leaves (see
:meth:AlgorithmState.from_settings; int fields get k's
integer dtype, float fields the float dtype), which is what
update_weights reads at trace time.
Source code in openscvx/algorithms/autotuner/base.py
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 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | |
citation() -> List[str]
¶
Return BibTeX citations for the published update rule this autotuner implements.
Autotuners implementing a published scheme override this so that
:meth:openscvx.Problem.citation can credit it alongside the
algorithm, solver, and discretization references. Unpublished
heuristics keep the default.
Returns:
| Type | Description |
|---|---|
List[str]
|
List of BibTeX citation strings. Empty for uncited autotuners. |
Source code in openscvx/algorithms/autotuner/base.py
update_weights(state: AlgorithmState, candidate: CandidateIterate, nodal_constraints: LoweredJaxConstraints, settings: Config, params: dict) -> AlgorithmState
abstractmethod
¶
Return the next-iterate :class:AlgorithmState.
Must be JAX-traceable. See the class docstring for the contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
AlgorithmState
|
Current-iterate pytree. Initial weights live on
|
required |
candidate
|
CandidateIterate
|
Subproblem result (read-only here). On acceptance its trajectory fields must be carried onto the returned state — see the class docstring. |
required |
nodal_constraints
|
LoweredJaxConstraints
|
Lowered JAX constraints. |
required |
settings
|
Config
|
Configuration object. |
required |
params
|
dict
|
Problem parameter dictionary. |
required |
Returns:
| Type | Description |
|---|---|
AlgorithmState
|
The next-iterate :class: |
AlgorithmState
|
nothing else, so an accepting update carries |
AlgorithmState
|
|
AlgorithmState
|
rejecting update keeps the previous fields and adjusts only the |
AlgorithmState
|
weights. Its |
AlgorithmState
|
the autotuner's decision; the SCP loop records that into history |
AlgorithmState
|
and maps it to a printable label. |
Source code in openscvx/algorithms/autotuner/base.py
LamCostRelaxHyper
¶
Bases: HyperParams
lam_cost relaxation knobs shared by every built-in autotuner.
Every autotuner relaxes lam_cost by the same rule (see
:meth:AutotuningBase._relaxed_lam_cost), so the two knobs that steer it
live here once instead of being re-declared per class. lam_cost_drop is
the iteration after which relaxation applies (state.k > lam_cost_drop):
-1 relaxes from the first iteration, and the default
lam_cost_relax=1.0 makes that a no-op.