src/compose.py

changeset 12
a0d61ac3719e
parent 3
c3a4f4bb87f7
equal deleted inserted replaced
11:c4f923017349 12:a0d61ac3719e
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

mercurial