agd.Metrics.base
1# Copyright 2020 Jean-Marie Mirebeau, University Paris-Sud, CNRS, University Paris-Saclay 2# Distributed WITHOUT ANY WARRANTY. Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 3 4import numpy as np 5from .. import AutomaticDifferentiation as ad 6from ..AutomaticDifferentiation import cupy_support as cps 7from .. import LinearParallel as lp 8from .. import FiniteDifferences as fd 9from .. import Interpolation 10 11class Base: 12 """ 13 Base class for a metric, in other words a family of norms. 14 """ 15 16 def __repr__(self): 17 return type(self).__name__ + repr(tuple(self)) 18 19 def __str__(self): 20 return type(self).__name__ + str(tuple(self)) 21 22 def norm(self,v): 23 r""" 24 Norm or quasi-norm defined by the class, often denoted $N$ in mathematical 25 formulas. Unless incorrect data is provided, this member function obeys, 26 for all vectors $u,v\in R^d$ and all $\alpha \geq 0$ 27 - $N(u+v) \leq N(u)+N(v)$ 28 - $N(\alpha u) = \alpha N(u)$ 29 - $N(u)\geq 0$ with equality iff $u=0$. 30 31 Broadcasting will occur depending on the shape of $v$ and of the class data. 32 """ 33 raise NotImplementedError("""Error : norm must be specialized in subclass""") 34 35 def gradient(self,v): 36 """ 37 Gradient of the `norm` defined by the class. 38 """ 39 if ad.is_ad(v) or ad.is_ad(self,iterables=(type(self),)): 40 v_dis = ad.disassociate(v,shape_bound=v.shape[1:]) 41 grad_dis = self.disassociate().gradient(v_dis) 42 return ad.associate(grad_dis) 43 44 v_ad = ad.Dense.identity(constant=v,shape_free=(len(v),)) 45 return np.moveaxis(self.norm(v_ad).coef,-1,0) 46 47 def dual(self): 48 r""" 49 Dual `norm`, mathematically defined by 50 $N^*(x) = max\\{ < x, y> ; N(y)\leq 1 \\}$ 51 """ 52 raise NotImplementedError("dual is not implemented for this norm") 53 54 @property 55 def vdim(self): 56 """ 57 The ambient vector space dimension, often denoted $d$ in mathematical formulas. 58 """ 59 raise NotImplementedError("vdim is not implemented for this norm") 60 61 @property 62 def shape(self): 63 """ 64 Dimensions of the underlying domain. 65 Expected to be the empty tuple, or a tuple of length `vdim`. 66 """ 67 raise NotImplementedError("Shape not implemented for this norm") 68 69 def disassociate(self): 70 """ 71 Hide the automatic differentiation (AD) information of the member fields. 72 See AutomaticDifferentiation.disassociate 73 """ 74 def dis(x): 75 if ad.isndarray(x) and x.shape[-self.vdim:]==self.shape: 76 return ad.disassociate(x,shape_bound=self.shape) 77 return x 78 return self.from_generator(dis(x) for x in self) 79 80# ---- Well posedness related methods ----- 81 def is_definite(self): 82 """ 83 Attempts to check wether the data defines a mathematically valid `norm`. 84 """ 85 raise NotImplementedError("is_definite is not implemented for this norm") 86 87 def anisotropy(self): 88 r""" 89 Anisotropy ratio of the `norm` denoted $N$. 90 Defined as 91 $$ 92 \max_{|u| = |v| = 1} \frac {N(u)}{N(v)}. 93 $$ 94 """ 95 raise NotImplementedError("anisotropy is not implemented for this norm") 96 97 def anisotropy_bound(self): 98 """ 99 An upper bound on the `anisotropy` of the norm. 100 """ 101 return self.anisotropy() 102 def cost_bound(self): 103 """ 104 Upper bound on $N(u)$, for any unit vector $u$, where $N$ is the `norm` 105 defined by the class. 106 """ 107 raise NotImplementedError("cost_bound is not implemented for this norm") 108 109# ---- Causality and acuteness related methods ---- 110 111 def cos_asym(self,u,v): 112 r""" 113 Generalized asymmetric cosine defined by the norm $N$, of the angle between two vectors $u,v$. 114 Defined as 115 $\cos^a_N(u,v) := < \nabla N(u), v> / N(v)$. 116 """ 117 u,v=(ad.asarray(e) for e in (u,v)) 118 return lp.dot_VV(self.gradient(u),v)/self.norm(v) 119 120 def cos(self,u,v): 121 r""" 122 Generalized cosine defined by the norm $N$. Expression 123 $\cos_N(u,v) := \min( \cos^a_N (u,v), \cos^a_N(v,u))$, 124 where $\cos^a_N=$`cos_asym`. 125 """ 126 u,v=(ad.asarray(e) for e in (u,v)) 127 gu,gv=self.gradient(u),self.gradient(v) 128 guu,guv = lp.dot_VV(gu,u),lp.dot_VV(gu,v) 129 gvu,gvv = lp.dot_VV(gv,u),lp.dot_VV(gv,v) 130 return np.minimum(guv/gvv,gvu/guu) 131 132 def angle(self,u,v): 133 r""" 134 Generalized unoriented angle defined by the norm, 135 defined as $\measuredangle_N(u,v) := \arccos(\cos_N(u,v))$. 136 See `cos` member functions. 137 """ 138 c = ad.asarray(self.cos(u,v)) 139 mask=c < -1. 140 c[mask]=0. 141 result = ad.asarray(np.arccos(c)) 142 result[mask]=np.inf 143 return result 144 145# ---- Geometric transformations ---- 146 147 def inv_transform(self,a): 148 """ 149 Affine transformation of the norm. 150 The new unit ball is the inverse image of the previous one. 151 """ 152 raise NotImplementedError("Affine transformation not implemented for this norm") 153 154 def transform(self,a): 155 """ 156 Affine transformation of the norm. 157 The new unit ball is the direct image of the previous one. 158 """ 159 return self.inv_transform(lp.inverse(a)) 160 161 def rotate(self,r): 162 """ 163 Rotation of the norm, by a given rotation matrix. 164 The new unit ball is the direct image of the previous one. 165 """ 166 # For orthogonal matrices, inversion is transposition 167 return self.inv_transform(lp.transpose(r)) 168 169 def rotate_by(self,*args,**kwargs): 170 """ 171 Rotation of the norm, based on rotation parameters : angle (and axis in 3D). 172 """ 173 return self.rotate(lp.rotation(*args,**kwargs)) 174 175 def with_costs(self,costs): 176 """ 177 Produces a norm $N'$ defined by 178 $$ 179 N'(x) = N(costs * x) 180 $$ 181 where the multiplication is elementwise. 182 """ 183 a = np.zeros_like(costs,shape=(len(costs),)+costs.shape) 184 for i,cost in enumerate(costs): a[i,i] = cost 185 return self.inv_transform(a) 186 187 def with_speeds(self,speeds): 188 """ 189 Produces a norm $N'$ obeying 190 $$ 191 N'(x) = N(x/speeds) 192 $$ 193 where the division is elementwise. 194 """ 195 return self.with_costs(1./speeds) 196 197 def with_cost(self,cost): 198 """ 199 Produces a norm $N'$ obeying $N'(x) = N(cost*x)$. 200 """ 201 cost = ad.asarray(cost) 202 costs = np.broadcast_to(cost,(self.vdim,)+cost.shape) 203 return self.with_costs(costs) 204 205 206 def with_speed(self,speed): 207 """ 208 Produces a norm $N'$ obeying $N'(x) = N(x/speed)$. 209 """ 210 return self.with_cost(1/speed) 211 212# ---- Import and export ---- 213 214 def flatten(self): 215 """ 216 Flattens and concatenate the member fields into a single array. 217 """ 218 raise NotImplementedError("Flattening not implemented for this norm") 219 220 @classmethod 221 def expand(cls,arr): 222 """ 223 Inverse of the flatten member function. 224 Turns a suitable array into a metric. 225 """ 226 raise NotImplementedError("Expansion not implemented for this norm") 227 228 def to_HFM(self): 229 """ 230 Formats a metric for the HFM library. 231 This may include flattening some symmetric matrices, 232 concatenating with vector fields, and moving the first axis last. 233 """ 234 return np.moveaxis(self.flatten(),0,-1) 235 236 def model_HFM(self): 237 """ 238 The name of the 'model' for parameter, as input to the HFM library. 239 """ 240 raise NotImplementedError("HFM name is not specified for this norm") 241 242 @classmethod 243 def from_HFM(cls,arr): 244 """ 245 Inverse of the to_HFM member function. 246 Turns a suitable array into a metric. 247 """ 248 return cls.expand(np.moveaxis(arr,-1,0)) 249 250 def __iter__(self): 251 """ 252 Iterate over the member fields. 253 """ 254 raise NotImplementedError("__iter__ not implemented for this norm") 255 256 @classmethod 257 def from_generator(cls,gen): 258 """ 259 Produce a metric from a suitable generator expression. 260 """ 261 return cls(*gen) 262 263 @classmethod 264 def from_cast(cls,metric): 265 """ 266 Produces a metric by casting another metric of a compatible type. 267 """ 268 raise NotImplementedError("from_cast not implemented for this norm") 269 270 @property 271 def array_float_caster(self): 272 """ 273 Returns a caster function, which can be used to turn lists, etc, into 274 arrays with the suitable floating point type, from the suitable library 275 (numpy or cupy), depending on the member fields. 276 """ 277 return ad.cupy_generic.array_float_caster(self,iterables=type(self)) 278 279# ---- Related with Lagrandian and Hamiltonian interpretation ---- 280 281 def norm2(self,v): 282 r""" 283 Half square of the `norm`, defined by $F(v) := \frac 1 2 N(v)^2$. 284 """ 285 n = self.norm(v) 286 return 0.5*n**2 287 288 def gradient2(self,v): 289 """ 290 Gradient of `norm2`the half squared norm. 291 """ 292 g = self.gradient(v) 293 return lp.dot_VV(g,v)*g 294 295 def set_interpolation(self,grid,**kwargs): 296 """ 297 Sets interpolation_data, required to specialize the norm 298 at a given position. 299 300 Inputs: 301 - grid (optional). Coordinate system (required on first call). 302 - kwargs. Passed to UniformGridInterpolation (includes order) 303 """ 304 vdim = len(grid) 305 try: assert self.vdim == vdim 306 except ValueError: pass # Constant isotropic metrics have no dimension 307 308 def make_interp(value): 309 if hasattr(value,'shape') and value.shape[-vdim:]==grid.shape[1:]: 310 return Interpolation.UniformGridInterpolation(grid,value,**kwargs) 311 return value 312 313 self.interpolation_data = tuple(make_interp(value) for value in self) 314 315 def at(self,x): 316 """ 317 Interpolates the metric to a given position, on a grid given beforehand. 318 319 Inputs : 320 - x. Place where interpolation is needed. 321 """ 322 return self.from_generator( 323 field(x) if callable(field) else field 324 for field in self.interpolation_data) 325 # isinstance(field,Interpolation.UniformGridInterpolation) 326 327 328# ---- Proximal methods ---- 329 def make_proj_dual(self,**kwargs): 330 """ 331 Returns the orthogonal projection operator onto the unit ball of the dual metric. 332 """ 333 raise NotImplementedError("Sorry, make_proj_dual is not implemented for this metric") 334 335 def make_proj(self,**kwargs): 336 """ 337 Returns the orthogonal projection operator onto the unit ball. 338 - **kwargs : passed to self.make_proj_dual 339 """ 340 return self.dual().make_proj_dual(**kwargs) 341 342 def make_prox(self,**kwargs): 343 """ 344 Returns the proximal operator associated with the metric. 345 argmin_x (1/2) |x-y|^2 + τ F(y) 346 - **kwargs : passed to self.make_proj_dual 347 """ 348 # Implementation is based on the Moreau formula x = prox_{τ f}(x) + τ prox_{f^*/τ} (x/τ) 349 # noting that f^* is the characteristic function of the dual unit ball, hence 350 # prox_{f^*/τ} is the orthogonal projection onto the dual unit ball. 351 proj = self.make_proj_dual(**kwargs) 352 return lambda x,τ=1.: x - τ*proj(x/τ) 353
12class Base: 13 """ 14 Base class for a metric, in other words a family of norms. 15 """ 16 17 def __repr__(self): 18 return type(self).__name__ + repr(tuple(self)) 19 20 def __str__(self): 21 return type(self).__name__ + str(tuple(self)) 22 23 def norm(self,v): 24 r""" 25 Norm or quasi-norm defined by the class, often denoted $N$ in mathematical 26 formulas. Unless incorrect data is provided, this member function obeys, 27 for all vectors $u,v\in R^d$ and all $\alpha \geq 0$ 28 - $N(u+v) \leq N(u)+N(v)$ 29 - $N(\alpha u) = \alpha N(u)$ 30 - $N(u)\geq 0$ with equality iff $u=0$. 31 32 Broadcasting will occur depending on the shape of $v$ and of the class data. 33 """ 34 raise NotImplementedError("""Error : norm must be specialized in subclass""") 35 36 def gradient(self,v): 37 """ 38 Gradient of the `norm` defined by the class. 39 """ 40 if ad.is_ad(v) or ad.is_ad(self,iterables=(type(self),)): 41 v_dis = ad.disassociate(v,shape_bound=v.shape[1:]) 42 grad_dis = self.disassociate().gradient(v_dis) 43 return ad.associate(grad_dis) 44 45 v_ad = ad.Dense.identity(constant=v,shape_free=(len(v),)) 46 return np.moveaxis(self.norm(v_ad).coef,-1,0) 47 48 def dual(self): 49 r""" 50 Dual `norm`, mathematically defined by 51 $N^*(x) = max\\{ < x, y> ; N(y)\leq 1 \\}$ 52 """ 53 raise NotImplementedError("dual is not implemented for this norm") 54 55 @property 56 def vdim(self): 57 """ 58 The ambient vector space dimension, often denoted $d$ in mathematical formulas. 59 """ 60 raise NotImplementedError("vdim is not implemented for this norm") 61 62 @property 63 def shape(self): 64 """ 65 Dimensions of the underlying domain. 66 Expected to be the empty tuple, or a tuple of length `vdim`. 67 """ 68 raise NotImplementedError("Shape not implemented for this norm") 69 70 def disassociate(self): 71 """ 72 Hide the automatic differentiation (AD) information of the member fields. 73 See AutomaticDifferentiation.disassociate 74 """ 75 def dis(x): 76 if ad.isndarray(x) and x.shape[-self.vdim:]==self.shape: 77 return ad.disassociate(x,shape_bound=self.shape) 78 return x 79 return self.from_generator(dis(x) for x in self) 80 81# ---- Well posedness related methods ----- 82 def is_definite(self): 83 """ 84 Attempts to check wether the data defines a mathematically valid `norm`. 85 """ 86 raise NotImplementedError("is_definite is not implemented for this norm") 87 88 def anisotropy(self): 89 r""" 90 Anisotropy ratio of the `norm` denoted $N$. 91 Defined as 92 $$ 93 \max_{|u| = |v| = 1} \frac {N(u)}{N(v)}. 94 $$ 95 """ 96 raise NotImplementedError("anisotropy is not implemented for this norm") 97 98 def anisotropy_bound(self): 99 """ 100 An upper bound on the `anisotropy` of the norm. 101 """ 102 return self.anisotropy() 103 def cost_bound(self): 104 """ 105 Upper bound on $N(u)$, for any unit vector $u$, where $N$ is the `norm` 106 defined by the class. 107 """ 108 raise NotImplementedError("cost_bound is not implemented for this norm") 109 110# ---- Causality and acuteness related methods ---- 111 112 def cos_asym(self,u,v): 113 r""" 114 Generalized asymmetric cosine defined by the norm $N$, of the angle between two vectors $u,v$. 115 Defined as 116 $\cos^a_N(u,v) := < \nabla N(u), v> / N(v)$. 117 """ 118 u,v=(ad.asarray(e) for e in (u,v)) 119 return lp.dot_VV(self.gradient(u),v)/self.norm(v) 120 121 def cos(self,u,v): 122 r""" 123 Generalized cosine defined by the norm $N$. Expression 124 $\cos_N(u,v) := \min( \cos^a_N (u,v), \cos^a_N(v,u))$, 125 where $\cos^a_N=$`cos_asym`. 126 """ 127 u,v=(ad.asarray(e) for e in (u,v)) 128 gu,gv=self.gradient(u),self.gradient(v) 129 guu,guv = lp.dot_VV(gu,u),lp.dot_VV(gu,v) 130 gvu,gvv = lp.dot_VV(gv,u),lp.dot_VV(gv,v) 131 return np.minimum(guv/gvv,gvu/guu) 132 133 def angle(self,u,v): 134 r""" 135 Generalized unoriented angle defined by the norm, 136 defined as $\measuredangle_N(u,v) := \arccos(\cos_N(u,v))$. 137 See `cos` member functions. 138 """ 139 c = ad.asarray(self.cos(u,v)) 140 mask=c < -1. 141 c[mask]=0. 142 result = ad.asarray(np.arccos(c)) 143 result[mask]=np.inf 144 return result 145 146# ---- Geometric transformations ---- 147 148 def inv_transform(self,a): 149 """ 150 Affine transformation of the norm. 151 The new unit ball is the inverse image of the previous one. 152 """ 153 raise NotImplementedError("Affine transformation not implemented for this norm") 154 155 def transform(self,a): 156 """ 157 Affine transformation of the norm. 158 The new unit ball is the direct image of the previous one. 159 """ 160 return self.inv_transform(lp.inverse(a)) 161 162 def rotate(self,r): 163 """ 164 Rotation of the norm, by a given rotation matrix. 165 The new unit ball is the direct image of the previous one. 166 """ 167 # For orthogonal matrices, inversion is transposition 168 return self.inv_transform(lp.transpose(r)) 169 170 def rotate_by(self,*args,**kwargs): 171 """ 172 Rotation of the norm, based on rotation parameters : angle (and axis in 3D). 173 """ 174 return self.rotate(lp.rotation(*args,**kwargs)) 175 176 def with_costs(self,costs): 177 """ 178 Produces a norm $N'$ defined by 179 $$ 180 N'(x) = N(costs * x) 181 $$ 182 where the multiplication is elementwise. 183 """ 184 a = np.zeros_like(costs,shape=(len(costs),)+costs.shape) 185 for i,cost in enumerate(costs): a[i,i] = cost 186 return self.inv_transform(a) 187 188 def with_speeds(self,speeds): 189 """ 190 Produces a norm $N'$ obeying 191 $$ 192 N'(x) = N(x/speeds) 193 $$ 194 where the division is elementwise. 195 """ 196 return self.with_costs(1./speeds) 197 198 def with_cost(self,cost): 199 """ 200 Produces a norm $N'$ obeying $N'(x) = N(cost*x)$. 201 """ 202 cost = ad.asarray(cost) 203 costs = np.broadcast_to(cost,(self.vdim,)+cost.shape) 204 return self.with_costs(costs) 205 206 207 def with_speed(self,speed): 208 """ 209 Produces a norm $N'$ obeying $N'(x) = N(x/speed)$. 210 """ 211 return self.with_cost(1/speed) 212 213# ---- Import and export ---- 214 215 def flatten(self): 216 """ 217 Flattens and concatenate the member fields into a single array. 218 """ 219 raise NotImplementedError("Flattening not implemented for this norm") 220 221 @classmethod 222 def expand(cls,arr): 223 """ 224 Inverse of the flatten member function. 225 Turns a suitable array into a metric. 226 """ 227 raise NotImplementedError("Expansion not implemented for this norm") 228 229 def to_HFM(self): 230 """ 231 Formats a metric for the HFM library. 232 This may include flattening some symmetric matrices, 233 concatenating with vector fields, and moving the first axis last. 234 """ 235 return np.moveaxis(self.flatten(),0,-1) 236 237 def model_HFM(self): 238 """ 239 The name of the 'model' for parameter, as input to the HFM library. 240 """ 241 raise NotImplementedError("HFM name is not specified for this norm") 242 243 @classmethod 244 def from_HFM(cls,arr): 245 """ 246 Inverse of the to_HFM member function. 247 Turns a suitable array into a metric. 248 """ 249 return cls.expand(np.moveaxis(arr,-1,0)) 250 251 def __iter__(self): 252 """ 253 Iterate over the member fields. 254 """ 255 raise NotImplementedError("__iter__ not implemented for this norm") 256 257 @classmethod 258 def from_generator(cls,gen): 259 """ 260 Produce a metric from a suitable generator expression. 261 """ 262 return cls(*gen) 263 264 @classmethod 265 def from_cast(cls,metric): 266 """ 267 Produces a metric by casting another metric of a compatible type. 268 """ 269 raise NotImplementedError("from_cast not implemented for this norm") 270 271 @property 272 def array_float_caster(self): 273 """ 274 Returns a caster function, which can be used to turn lists, etc, into 275 arrays with the suitable floating point type, from the suitable library 276 (numpy or cupy), depending on the member fields. 277 """ 278 return ad.cupy_generic.array_float_caster(self,iterables=type(self)) 279 280# ---- Related with Lagrandian and Hamiltonian interpretation ---- 281 282 def norm2(self,v): 283 r""" 284 Half square of the `norm`, defined by $F(v) := \frac 1 2 N(v)^2$. 285 """ 286 n = self.norm(v) 287 return 0.5*n**2 288 289 def gradient2(self,v): 290 """ 291 Gradient of `norm2`the half squared norm. 292 """ 293 g = self.gradient(v) 294 return lp.dot_VV(g,v)*g 295 296 def set_interpolation(self,grid,**kwargs): 297 """ 298 Sets interpolation_data, required to specialize the norm 299 at a given position. 300 301 Inputs: 302 - grid (optional). Coordinate system (required on first call). 303 - kwargs. Passed to UniformGridInterpolation (includes order) 304 """ 305 vdim = len(grid) 306 try: assert self.vdim == vdim 307 except ValueError: pass # Constant isotropic metrics have no dimension 308 309 def make_interp(value): 310 if hasattr(value,'shape') and value.shape[-vdim:]==grid.shape[1:]: 311 return Interpolation.UniformGridInterpolation(grid,value,**kwargs) 312 return value 313 314 self.interpolation_data = tuple(make_interp(value) for value in self) 315 316 def at(self,x): 317 """ 318 Interpolates the metric to a given position, on a grid given beforehand. 319 320 Inputs : 321 - x. Place where interpolation is needed. 322 """ 323 return self.from_generator( 324 field(x) if callable(field) else field 325 for field in self.interpolation_data) 326 # isinstance(field,Interpolation.UniformGridInterpolation) 327 328 329# ---- Proximal methods ---- 330 def make_proj_dual(self,**kwargs): 331 """ 332 Returns the orthogonal projection operator onto the unit ball of the dual metric. 333 """ 334 raise NotImplementedError("Sorry, make_proj_dual is not implemented for this metric") 335 336 def make_proj(self,**kwargs): 337 """ 338 Returns the orthogonal projection operator onto the unit ball. 339 - **kwargs : passed to self.make_proj_dual 340 """ 341 return self.dual().make_proj_dual(**kwargs) 342 343 def make_prox(self,**kwargs): 344 """ 345 Returns the proximal operator associated with the metric. 346 argmin_x (1/2) |x-y|^2 + τ F(y) 347 - **kwargs : passed to self.make_proj_dual 348 """ 349 # Implementation is based on the Moreau formula x = prox_{τ f}(x) + τ prox_{f^*/τ} (x/τ) 350 # noting that f^* is the characteristic function of the dual unit ball, hence 351 # prox_{f^*/τ} is the orthogonal projection onto the dual unit ball. 352 proj = self.make_proj_dual(**kwargs) 353 return lambda x,τ=1.: x - τ*proj(x/τ)
Base class for a metric, in other words a family of norms.
23 def norm(self,v): 24 r""" 25 Norm or quasi-norm defined by the class, often denoted $N$ in mathematical 26 formulas. Unless incorrect data is provided, this member function obeys, 27 for all vectors $u,v\in R^d$ and all $\alpha \geq 0$ 28 - $N(u+v) \leq N(u)+N(v)$ 29 - $N(\alpha u) = \alpha N(u)$ 30 - $N(u)\geq 0$ with equality iff $u=0$. 31 32 Broadcasting will occur depending on the shape of $v$ and of the class data. 33 """ 34 raise NotImplementedError("""Error : norm must be specialized in subclass""")
Norm or quasi-norm defined by the class, often denoted $N$ in mathematical formulas. Unless incorrect data is provided, this member function obeys, for all vectors $u,v\in R^d$ and all $\alpha \geq 0$
- $N(u+v) \leq N(u)+N(v)$
- $N(\alpha u) = \alpha N(u)$
- $N(u)\geq 0$ with equality iff $u=0$.
Broadcasting will occur depending on the shape of $v$ and of the class data.
36 def gradient(self,v): 37 """ 38 Gradient of the `norm` defined by the class. 39 """ 40 if ad.is_ad(v) or ad.is_ad(self,iterables=(type(self),)): 41 v_dis = ad.disassociate(v,shape_bound=v.shape[1:]) 42 grad_dis = self.disassociate().gradient(v_dis) 43 return ad.associate(grad_dis) 44 45 v_ad = ad.Dense.identity(constant=v,shape_free=(len(v),)) 46 return np.moveaxis(self.norm(v_ad).coef,-1,0)
Gradient of the norm
defined by the class.
48 def dual(self): 49 r""" 50 Dual `norm`, mathematically defined by 51 $N^*(x) = max\\{ < x, y> ; N(y)\leq 1 \\}$ 52 """ 53 raise NotImplementedError("dual is not implemented for this norm")
Dual norm
, mathematically defined by
$N^*(x) = max\{ < x, y> ; N(y)\leq 1 \}$
55 @property 56 def vdim(self): 57 """ 58 The ambient vector space dimension, often denoted $d$ in mathematical formulas. 59 """ 60 raise NotImplementedError("vdim is not implemented for this norm")
The ambient vector space dimension, often denoted $d$ in mathematical formulas.
62 @property 63 def shape(self): 64 """ 65 Dimensions of the underlying domain. 66 Expected to be the empty tuple, or a tuple of length `vdim`. 67 """ 68 raise NotImplementedError("Shape not implemented for this norm")
Dimensions of the underlying domain.
Expected to be the empty tuple, or a tuple of length vdim
.
70 def disassociate(self): 71 """ 72 Hide the automatic differentiation (AD) information of the member fields. 73 See AutomaticDifferentiation.disassociate 74 """ 75 def dis(x): 76 if ad.isndarray(x) and x.shape[-self.vdim:]==self.shape: 77 return ad.disassociate(x,shape_bound=self.shape) 78 return x 79 return self.from_generator(dis(x) for x in self)
Hide the automatic differentiation (AD) information of the member fields. See AutomaticDifferentiation.disassociate
82 def is_definite(self): 83 """ 84 Attempts to check wether the data defines a mathematically valid `norm`. 85 """ 86 raise NotImplementedError("is_definite is not implemented for this norm")
Attempts to check wether the data defines a mathematically valid norm
.
88 def anisotropy(self): 89 r""" 90 Anisotropy ratio of the `norm` denoted $N$. 91 Defined as 92 $$ 93 \max_{|u| = |v| = 1} \frac {N(u)}{N(v)}. 94 $$ 95 """ 96 raise NotImplementedError("anisotropy is not implemented for this norm")
Anisotropy ratio of the norm
denoted $N$.
Defined as
$$
\max_{|u| = |v| = 1} \frac {N(u)}{N(v)}.
$$
98 def anisotropy_bound(self): 99 """ 100 An upper bound on the `anisotropy` of the norm. 101 """ 102 return self.anisotropy()
An upper bound on the anisotropy
of the norm.
103 def cost_bound(self): 104 """ 105 Upper bound on $N(u)$, for any unit vector $u$, where $N$ is the `norm` 106 defined by the class. 107 """ 108 raise NotImplementedError("cost_bound is not implemented for this norm")
Upper bound on $N(u)$, for any unit vector $u$, where $N$ is the norm
defined by the class.
112 def cos_asym(self,u,v): 113 r""" 114 Generalized asymmetric cosine defined by the norm $N$, of the angle between two vectors $u,v$. 115 Defined as 116 $\cos^a_N(u,v) := < \nabla N(u), v> / N(v)$. 117 """ 118 u,v=(ad.asarray(e) for e in (u,v)) 119 return lp.dot_VV(self.gradient(u),v)/self.norm(v)
Generalized asymmetric cosine defined by the norm $N$, of the angle between two vectors $u,v$. Defined as $\cos^a_N(u,v) := < \nabla N(u), v> / N(v)$.
121 def cos(self,u,v): 122 r""" 123 Generalized cosine defined by the norm $N$. Expression 124 $\cos_N(u,v) := \min( \cos^a_N (u,v), \cos^a_N(v,u))$, 125 where $\cos^a_N=$`cos_asym`. 126 """ 127 u,v=(ad.asarray(e) for e in (u,v)) 128 gu,gv=self.gradient(u),self.gradient(v) 129 guu,guv = lp.dot_VV(gu,u),lp.dot_VV(gu,v) 130 gvu,gvv = lp.dot_VV(gv,u),lp.dot_VV(gv,v) 131 return np.minimum(guv/gvv,gvu/guu)
Generalized cosine defined by the norm $N$. Expression
$\cos_N(u,v) := \min( \cos^a_N (u,v), \cos^a_N(v,u))$,
where $\cos^a_N=$cos_asym
.
133 def angle(self,u,v): 134 r""" 135 Generalized unoriented angle defined by the norm, 136 defined as $\measuredangle_N(u,v) := \arccos(\cos_N(u,v))$. 137 See `cos` member functions. 138 """ 139 c = ad.asarray(self.cos(u,v)) 140 mask=c < -1. 141 c[mask]=0. 142 result = ad.asarray(np.arccos(c)) 143 result[mask]=np.inf 144 return result
Generalized unoriented angle defined by the norm,
defined as $\measuredangle_N(u,v) := \arccos(\cos_N(u,v))$.
See cos
member functions.
148 def inv_transform(self,a): 149 """ 150 Affine transformation of the norm. 151 The new unit ball is the inverse image of the previous one. 152 """ 153 raise NotImplementedError("Affine transformation not implemented for this norm")
Affine transformation of the norm. The new unit ball is the inverse image of the previous one.
155 def transform(self,a): 156 """ 157 Affine transformation of the norm. 158 The new unit ball is the direct image of the previous one. 159 """ 160 return self.inv_transform(lp.inverse(a))
Affine transformation of the norm. The new unit ball is the direct image of the previous one.
162 def rotate(self,r): 163 """ 164 Rotation of the norm, by a given rotation matrix. 165 The new unit ball is the direct image of the previous one. 166 """ 167 # For orthogonal matrices, inversion is transposition 168 return self.inv_transform(lp.transpose(r))
Rotation of the norm, by a given rotation matrix. The new unit ball is the direct image of the previous one.
170 def rotate_by(self,*args,**kwargs): 171 """ 172 Rotation of the norm, based on rotation parameters : angle (and axis in 3D). 173 """ 174 return self.rotate(lp.rotation(*args,**kwargs))
Rotation of the norm, based on rotation parameters : angle (and axis in 3D).
176 def with_costs(self,costs): 177 """ 178 Produces a norm $N'$ defined by 179 $$ 180 N'(x) = N(costs * x) 181 $$ 182 where the multiplication is elementwise. 183 """ 184 a = np.zeros_like(costs,shape=(len(costs),)+costs.shape) 185 for i,cost in enumerate(costs): a[i,i] = cost 186 return self.inv_transform(a)
Produces a norm $N'$ defined by $$ N'(x) = N(costs * x) $$ where the multiplication is elementwise.
188 def with_speeds(self,speeds): 189 """ 190 Produces a norm $N'$ obeying 191 $$ 192 N'(x) = N(x/speeds) 193 $$ 194 where the division is elementwise. 195 """ 196 return self.with_costs(1./speeds)
Produces a norm $N'$ obeying $$ N'(x) = N(x/speeds) $$ where the division is elementwise.
198 def with_cost(self,cost): 199 """ 200 Produces a norm $N'$ obeying $N'(x) = N(cost*x)$. 201 """ 202 cost = ad.asarray(cost) 203 costs = np.broadcast_to(cost,(self.vdim,)+cost.shape) 204 return self.with_costs(costs)
Produces a norm $N'$ obeying $N'(x) = N(cost*x)$.
207 def with_speed(self,speed): 208 """ 209 Produces a norm $N'$ obeying $N'(x) = N(x/speed)$. 210 """ 211 return self.with_cost(1/speed)
Produces a norm $N'$ obeying $N'(x) = N(x/speed)$.
215 def flatten(self): 216 """ 217 Flattens and concatenate the member fields into a single array. 218 """ 219 raise NotImplementedError("Flattening not implemented for this norm")
Flattens and concatenate the member fields into a single array.
221 @classmethod 222 def expand(cls,arr): 223 """ 224 Inverse of the flatten member function. 225 Turns a suitable array into a metric. 226 """ 227 raise NotImplementedError("Expansion not implemented for this norm")
Inverse of the flatten member function. Turns a suitable array into a metric.
229 def to_HFM(self): 230 """ 231 Formats a metric for the HFM library. 232 This may include flattening some symmetric matrices, 233 concatenating with vector fields, and moving the first axis last. 234 """ 235 return np.moveaxis(self.flatten(),0,-1)
Formats a metric for the HFM library. This may include flattening some symmetric matrices, concatenating with vector fields, and moving the first axis last.
237 def model_HFM(self): 238 """ 239 The name of the 'model' for parameter, as input to the HFM library. 240 """ 241 raise NotImplementedError("HFM name is not specified for this norm")
The name of the 'model' for parameter, as input to the HFM library.
243 @classmethod 244 def from_HFM(cls,arr): 245 """ 246 Inverse of the to_HFM member function. 247 Turns a suitable array into a metric. 248 """ 249 return cls.expand(np.moveaxis(arr,-1,0))
Inverse of the to_HFM member function. Turns a suitable array into a metric.
257 @classmethod 258 def from_generator(cls,gen): 259 """ 260 Produce a metric from a suitable generator expression. 261 """ 262 return cls(*gen)
Produce a metric from a suitable generator expression.
264 @classmethod 265 def from_cast(cls,metric): 266 """ 267 Produces a metric by casting another metric of a compatible type. 268 """ 269 raise NotImplementedError("from_cast not implemented for this norm")
Produces a metric by casting another metric of a compatible type.
271 @property 272 def array_float_caster(self): 273 """ 274 Returns a caster function, which can be used to turn lists, etc, into 275 arrays with the suitable floating point type, from the suitable library 276 (numpy or cupy), depending on the member fields. 277 """ 278 return ad.cupy_generic.array_float_caster(self,iterables=type(self))
Returns a caster function, which can be used to turn lists, etc, into arrays with the suitable floating point type, from the suitable library (numpy or cupy), depending on the member fields.
282 def norm2(self,v): 283 r""" 284 Half square of the `norm`, defined by $F(v) := \frac 1 2 N(v)^2$. 285 """ 286 n = self.norm(v) 287 return 0.5*n**2
Half square of the norm
, defined by $F(v) := \frac 1 2 N(v)^2$.
289 def gradient2(self,v): 290 """ 291 Gradient of `norm2`the half squared norm. 292 """ 293 g = self.gradient(v) 294 return lp.dot_VV(g,v)*g
Gradient of norm2
the half squared norm.
296 def set_interpolation(self,grid,**kwargs): 297 """ 298 Sets interpolation_data, required to specialize the norm 299 at a given position. 300 301 Inputs: 302 - grid (optional). Coordinate system (required on first call). 303 - kwargs. Passed to UniformGridInterpolation (includes order) 304 """ 305 vdim = len(grid) 306 try: assert self.vdim == vdim 307 except ValueError: pass # Constant isotropic metrics have no dimension 308 309 def make_interp(value): 310 if hasattr(value,'shape') and value.shape[-vdim:]==grid.shape[1:]: 311 return Interpolation.UniformGridInterpolation(grid,value,**kwargs) 312 return value 313 314 self.interpolation_data = tuple(make_interp(value) for value in self)
Sets interpolation_data, required to specialize the norm at a given position.
Inputs:
- grid (optional). Coordinate system (required on first call).
- kwargs. Passed to UniformGridInterpolation (includes order)
316 def at(self,x): 317 """ 318 Interpolates the metric to a given position, on a grid given beforehand. 319 320 Inputs : 321 - x. Place where interpolation is needed. 322 """ 323 return self.from_generator( 324 field(x) if callable(field) else field 325 for field in self.interpolation_data) 326 # isinstance(field,Interpolation.UniformGridInterpolation)
Interpolates the metric to a given position, on a grid given beforehand.
Inputs :
- x. Place where interpolation is needed.
330 def make_proj_dual(self,**kwargs): 331 """ 332 Returns the orthogonal projection operator onto the unit ball of the dual metric. 333 """ 334 raise NotImplementedError("Sorry, make_proj_dual is not implemented for this metric")
Returns the orthogonal projection operator onto the unit ball of the dual metric.
336 def make_proj(self,**kwargs): 337 """ 338 Returns the orthogonal projection operator onto the unit ball. 339 - **kwargs : passed to self.make_proj_dual 340 """ 341 return self.dual().make_proj_dual(**kwargs)
Returns the orthogonal projection operator onto the unit ball.
- **kwargs : passed to self.make_proj_dual
343 def make_prox(self,**kwargs): 344 """ 345 Returns the proximal operator associated with the metric. 346 argmin_x (1/2) |x-y|^2 + τ F(y) 347 - **kwargs : passed to self.make_proj_dual 348 """ 349 # Implementation is based on the Moreau formula x = prox_{τ f}(x) + τ prox_{f^*/τ} (x/τ) 350 # noting that f^* is the characteristic function of the dual unit ball, hence 351 # prox_{f^*/τ} is the orthogonal projection onto the dual unit ball. 352 proj = self.make_proj_dual(**kwargs) 353 return lambda x,τ=1.: x - τ*proj(x/τ)
Returns the proximal operator associated with the metric. argmin_x (1/2) |x-y|^2 + τ F(y)
- **kwargs : passed to self.make_proj_dual