--- a/src/compose.py Mon Jun 29 00:20:42 2026 +0300 +++ b/src/compose.py Sun Oct 04 13:02:26 2026 -0500 @@ -43,6 +43,8 @@ def __init__(self, f, op): self.f = f self.op = op + self.override_lipschitz = None + self.override_lipschitz_pair = None def apply(self, *args): return self.f.apply(self.op.apply(*args)) @@ -93,7 +95,9 @@ on ∇F. The function `diff_chain_lipschitz_factor` should return the Lipschitz factor divided by $M$: we obtain $M$ through the `diff_bound` on $F$. """ - if hasattr(self.op, "opnorm"): + if self.override_lipschitz is not None: + return self.override_lipschitz + elif hasattr(self.op, "opnorm"): return self.f.diff_lipschitz_factor() * self.op.opnorm() ** 2 else: mdf = self.f.diff_bound() @@ -113,6 +117,9 @@ there is no special handling of linear opeartors. """ + if self.override_lipschitz_pair is not None: + return self.override_lipschitz_pair + mdf = self.f.diff_bound() lda1, lda2 = self.op.diff_chain_lipschitz_factor_pair() @@ -151,3 +158,31 @@ # raise Exception("Linear operators have unbounded range") # else: # return xbound + + +class ScaleAuxStepHack: + """ + This hack only scale the differential, to get different step length parameters wrt. k and c, + without rescaling the variables themselves. Essentially it amounts to computing the gradient wrt. + different inner products. + """ + + def __init__(self, scale): + self.scale = scale + + def apply(self, x): + # μ, (k, (c1, c2)) = x + # sck, scc = self.scale + # return μ, (k * sck, (c1 * scc, c2 * scc)) + return x + + def diff_adjdir(self, j, _x, apply_result=None): + dμ, (dk, (dc1, dc2)) = j + sck, scc = self.scale + return dμ, (dk * sck, (dc1 * scc, dc2 * scc)) + + def opnorm(self, *args): + return 1 + + def diff_bound(self): + return 1