| 41 |
41 |
| 42 class ComposeFnWithOperator: |
42 class ComposeFnWithOperator: |
| 43 def __init__(self, f, op): |
43 def __init__(self, f, op): |
| 44 self.f = f |
44 self.f = f |
| 45 self.op = op |
45 self.op = op |
| |
46 self.override_lipschitz = None |
| |
47 self.override_lipschitz_pair = None |
| 46 |
48 |
| 47 def apply(self, *args): |
49 def apply(self, *args): |
| 48 return self.f.apply(self.op.apply(*args)) |
50 return self.f.apply(self.op.apply(*args)) |
| 49 |
51 |
| 50 def diff(self, *args): |
52 def diff(self, *args): |
| 91 Calculate the Lipschitz factor of the differential of this composed function. |
93 Calculate the Lipschitz factor of the differential of this composed function. |
| 92 We assume that either the operator is linear and implementes `opnorm`, or it is nonlinear, and impliements `diff_chain_lipschitz_factor` to directly calculate a Lipschitz factor of $x ↦ ∇A(x)^*∇F(A(x))$, given a bound $M$ |
94 We assume that either the operator is linear and implementes `opnorm`, or it is nonlinear, and impliements `diff_chain_lipschitz_factor` to directly calculate a Lipschitz factor of $x ↦ ∇A(x)^*∇F(A(x))$, given a bound $M$ |
| 93 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$. |
95 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$. |
| 94 """ |
96 """ |
| 95 |
97 |
| 96 if hasattr(self.op, "opnorm"): |
98 if self.override_lipschitz is not None: |
| |
99 return self.override_lipschitz |
| |
100 elif hasattr(self.op, "opnorm"): |
| 97 return self.f.diff_lipschitz_factor() * self.op.opnorm() ** 2 |
101 return self.f.diff_lipschitz_factor() * self.op.opnorm() ** 2 |
| 98 else: |
102 else: |
| 99 mdf = self.f.diff_bound() |
103 mdf = self.f.diff_bound() |
| 100 lda = self.op.diff_chain_lipschitz_factor() |
104 lda = self.op.diff_chain_lipschitz_factor() |
| 101 # print( |
105 # print( |
| 110 factor for arguments pairs. |
114 factor for arguments pairs. |
| 111 |
115 |
| 112 This requires the operator to implement `diff_chain_lipschitz_factor`; |
116 This requires the operator to implement `diff_chain_lipschitz_factor`; |
| 113 there is no special handling of linear opeartors. |
117 there is no special handling of linear opeartors. |
| 114 """ |
118 """ |
| |
119 |
| |
120 if self.override_lipschitz_pair is not None: |
| |
121 return self.override_lipschitz_pair |
| 115 |
122 |
| 116 mdf = self.f.diff_bound() |
123 mdf = self.f.diff_bound() |
| 117 |
124 |
| 118 lda1, lda2 = self.op.diff_chain_lipschitz_factor_pair() |
125 lda1, lda2 = self.op.diff_chain_lipschitz_factor_pair() |
| 119 |
126 |
| 149 # def codomain_bound(self, xbound=None): |
156 # def codomain_bound(self, xbound=None): |
| 150 # if xbound is None: |
157 # if xbound is None: |
| 151 # raise Exception("Linear operators have unbounded range") |
158 # raise Exception("Linear operators have unbounded range") |
| 152 # else: |
159 # else: |
| 153 # return xbound |
160 # return xbound |
| |
161 |
| |
162 |
| |
163 class ScaleAuxStepHack: |
| |
164 """ |
| |
165 This hack only scale the differential, to get different step length parameters wrt. k and c, |
| |
166 without rescaling the variables themselves. Essentially it amounts to computing the gradient wrt. |
| |
167 different inner products. |
| |
168 """ |
| |
169 |
| |
170 def __init__(self, scale): |
| |
171 self.scale = scale |
| |
172 |
| |
173 def apply(self, x): |
| |
174 # μ, (k, (c1, c2)) = x |
| |
175 # sck, scc = self.scale |
| |
176 # return μ, (k * sck, (c1 * scc, c2 * scc)) |
| |
177 return x |
| |
178 |
| |
179 def diff_adjdir(self, j, _x, apply_result=None): |
| |
180 dμ, (dk, (dc1, dc2)) = j |
| |
181 sck, scc = self.scale |
| |
182 return dμ, (dk * sck, (dc1 * scc, dc2 * scc)) |
| |
183 |
| |
184 def opnorm(self, *args): |
| |
185 return 1 |
| |
186 |
| |
187 def diff_bound(self): |
| |
188 return 1 |