agd.Eikonal.run_detail
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 .LibraryCall import RunDispatch,GetBinaryDir 6from .DictIn_detail import factoringPointChoice 7from .. import Metrics 8from .. import AutomaticDifferentiation as ad 9 10def GetGeodesics(output,suffix=''): 11 if suffix != '' and not suffix.startswith('_'): suffix='_'+suffix 12 geodesics = np.vsplit(output['geodesicPoints'+suffix], 13 output['geodesicLengths'+suffix].cumsum()[:-1].astype(int)) 14 return [geo.T for geo in geodesics] 15 16class Cache(object): 17 def __init__(self,needsflow=False): 18 self.contents = dict() 19 self.verbosity = None 20 self.requested = None 21 self.needsflow = needsflow 22 self.dummy = False 23 24 def empty(self): 25 """Wether the cache lacks data needed to bypass computation""" 26 return not self.contents 27 28# def full(self): 29# return self.contents and ('geodesicFlow' in self.contents or not needsflow) 30 31 def PreProcess(self,hfmIn_raw): 32 if self.dummy: return 33 self.verbosity = hfmIn_raw.get('verbosity',1) 34 self.requested = [] 35 if hfmIn_raw.get('exportValues',False): self.requested.append('values') 36 if hfmIn_raw.get('exportActiveNeighs',False): self.requested.append('activeNeighs') 37 if hfmIn_raw.get('exportGeodesicFlow',False): self.requested.append('geodesicFlow') 38 if self.empty(): 39 if self.verbosity>=1: print("Requesting cacheable data") 40 hfmIn_raw['exportValues']=True 41 hfmIn_raw['exportActiveNeighs']=True 42 if self.needsflow: hfmIn_raw['exportGeodesicFlow']=True 43 else: 44 if self.verbosity>=1: print("Providing cached data") 45 for key in ('values','activeNeighs'): 46 setkey_safe(hfmIn_raw,key,self.contents[key]) 47 # Note : all points set as accepted in HFM algorithm 48 hfmIn_raw['exportValues']=False 49 hfmIn_raw['exportActiveNeighs']=False 50 hfmIn_raw['exportGeodesicFlow']=self.needsflow and ('geodesicFlow' not in self.contents) 51 52 def PostProcess(self,hfmOut_raw): 53 if self.dummy: return 54 if self.empty(): 55 if self.verbosity>=1 : print("Filling cache data") 56 for key in ('values','activeNeighs'): 57 self.contents[key] = hfmOut_raw[key] 58 if self.needsflow: 59 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow'] 60 else: 61 for key in self.requested: 62 if key not in hfmOut_raw: 63 setkey_safe(hfmOut_raw,key,self.contents[key]) 64 if self.needsflow and 'geodesicFlow' not in self.contents: 65 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow'] 66 67 def geodesicFlow(self,hfmIn=None): 68 if 'geodesicFlow' in self.contents: 69 return self.contents['geodesicFlow'] 70 elif hfmIn is None: 71 raise ValueError("geodesicFlow not cached and lacking hfm input data") 72 else: 73 self.dummy = False 74 self.needsflow = True 75 76 hfmIn_ = {key:ad.remove_ad(value,iterables=(Metrics.Base,)) for key,value in hfmIn.items() 77 if key not in [ # Keys useless for evaluating the geodesic flow 78 'tips','tips_Unoriented', 79 'costVariation','seedValueVariation','inspectSensitivity', 80 'exportActiveNeighs','exportActiveOffsets']} 81 82 hfmOut_raw = RunSmart(hfmIn_,cache=self,returns='out_raw') 83 if self.verbosity: 84 print("--- HFM call triggered above to compute geodesic flow ---") 85 self.PostProcess(hfmOut_raw) 86 return self.contents['geodesicFlow'] 87 88 89def RunRaw(hfmIn): 90 """Raw call to the HFM library""" 91 rawIn = hfmIn.copy() 92 mode = rawIn.pop('mode',None); assert mode is None or mode=='cpu_raw' 93 float_t = rawIn.pop('float_t',None); assert float_t is None or float_t==np.float64 94 return RunDispatch(rawIn,GetBinaryDir("FileHFM","HFMpy")) 95 96 97def RunSmart(hfmIn,co_output=None,cache=None,returns="out"): 98 """ 99 Calls the HFM library, a CPU eikonal solver written in C++, 100 with pre-processing and post-processing of data. 101 102 Main input : 103 - hfmIn, a dictionary like structure, containing the eikonal solver data. 104 105 The C++ library embeds some help information, which can be accessed using the 106 following key:value pairs in hfmIn. 107 * 'verbosity' : set to 1 or 2 to display information on run, including the defaulted keys. 108 set to 0 to silence the run. 109 * 'keyHelp' : set as a string of keys separated by spaces, to print help on these. 110 example 'keyHelp':"seeds tips" 111 112 Optional input: 113 - co_output (optional) : used for reverse automatic differentiation. 114 - cache (optional) : store some intermediate results to bypass computations at a later stage. 115 - returns (optional) : early aborts the run (debug purposes). ('in_raw','out_raw','out') 116 """ 117 assert returns in ('in_raw','out_raw','out') 118 119 if factoringPointChoice(hfmIn) == 'Both': 120 hfmIn = hfmIn.copy() 121 hfmIn.SetFactor() 122 123 hfmIn_raw = {} 124 125 if cache is None: 126 cache = Cache() 127 cache.dummy = True 128 129 # Pre-process usual arguments 130 for key,value in hfmIn.items(): 131 PreProcess(key,value,hfmIn,hfmIn_raw,cache) 132 133 # Reverse automatic differentiation 134 if co_output is not None: 135 assert ad.misc.reverse_mode(co_output)=="Reverse" 136 [co_hfmOut,co_value],_ = co_output 137 assert hfmIn.get('extractValues',False) and co_hfmOut is None 138 indices = np.nonzero(co_value) 139 positions = hfmIn.PointFromIndex(np.asarray(indices).T) 140 weights = co_value[indices] 141 setkey_safe(hfmIn_raw,'inspectSensitivity',positions) 142 setkey_safe(hfmIn_raw,'inspectSensitivityWeights',weights) 143 setkey_safe(hfmIn_raw,'inspectSensitivityLengths',[len(weights)]) 144 if 'metric' in hfmIn or 'dualMetric' in hfmIn: 145 cache.needsflow = True 146 cache.dummy = False 147 148 # Dealing with cached data 149 cache.PreProcess(hfmIn_raw) 150 151 # Call to the HFM library 152 if returns=='in_raw': return hfmIn_raw 153 hfmOut_raw = RunDispatch(hfmIn_raw,GetBinaryDir("FileHFM","HFMpy")) 154 if returns=='out_raw': return hfmOut_raw 155 156 # Dealing with cached data 157 cache.PostProcess(hfmOut_raw) 158 159 # Post process 160 hfmOut = {} 161 for key,val in hfmOut_raw.items(): 162 PostProcess(key,val,hfmOut_raw,hfmOut) 163 164 # Reverse automatic differentiation 165 if co_output is not None: 166 result=[] 167 for key,value in hfmIn.items(): 168 if key in ('cost','speed'): 169 if isinstance(value,Metrics.Isotropic): 170 value = value.to_HFM() 171 result.append((value, 172 hfmOut['costSensitivity_0'] if key=='cost' else (hfmOut['costSensitivity_0']/value**2))) 173 if key in ('metric','dualMetric'): 174 shape_bound = value.shape 175 considered = co_output.second 176 value_ad = ad.Dense.register(value,iterables=(Metrics.Base,),shape_bound=shape_bound,considered=considered) 177 metric_ad = value_ad if key=='metric' else value_ad.dual() 178 179 costSensitivity = np.moveaxis(flow_variation(cache.geodesicFlow(hfmIn),metric_ad),-1,0)*hfmOut['costSensitivity_0'] 180 shift = 0 181 size_bound = np.prod(shape_bound,dtype=int) 182 for x in value: 183 if any(x is val for val in considered): 184 xsize_free = x.size//size_bound 185 result.append((x,costSensitivity[shift:(shift+xsize_free)].reshape(x.shape)) ) 186 shift+=xsize_free 187 elif key=='seedValues': 188 sens = hfmOut['seedSensitivity_0'] 189 # Match the seeds with their approx given in sensitivity 190 corresp = np.argmin(ad.Optimization.norm(np.expand_dims(hfmIn_raw['seeds'],axis=2) 191 -np.expand_dims(sens[:,:2].T,axis=1),ord=2,axis=0),axis=0) 192 sens_ordered = np.zeros_like(value) 193 sens_ordered[corresp]=sens[:,2] 194 result.append((value,sens_ordered)) 195 # TODO : speed 196 return result 197 198 return (hfmOut,hfmOut.pop('values')) if hfmIn.get('extractValues',False) else hfmOut 199 200def setkey_safe(dico,key,value): 201 if key in dico: 202 if value is not dico[key]: 203 raise ValueError("Multiple values for key ",key) 204 else: 205 dico[key]=value 206 207def flow_variation(flow,metric): 208 flow = np.moveaxis(flow,-1,0) 209 zeros = np.all(flow==0.,axis=0) 210 flow.__setitem__((slice(None),zeros),np.nan) 211 norm = metric.norm(flow) 212 variation = norm.coef/np.expand_dims(norm.value,axis=-1) 213 variation.__setitem__((zeros,slice(None)),0.) 214 return variation 215 216# ----------------- Preprocessing --------------- 217def PreProcess(key,value,refined_in,raw_out,cache): 218 """ 219 copies key,val from refined to raw, with adequate treatment 220 """ 221 222 verbosity = refined_in.get('verbosity',1) 223 if key=='mode': assert value in ('cpu','cpu_transfer'); return 224 elif key=='float_t': assert value==np.float64; return 225 elif key in ('cost','speed'): 226 if isinstance(value,Metrics.Isotropic): 227 value = value.to_HFM() 228 if isinstance(value,ad.Dense.denseAD): 229 setkey_safe(raw_out,'costVariation', 230 value.coef if key=='cost' else (1/value).coef) 231 value = ad.remove_ad(value) 232 setkey_safe(raw_out,key,value) 233 elif key in ('metric','dualMetric'): 234 if refined_in['model'].startswith('Isotropic') or refined_in['model'].startswith('Diagonal'): 235 assert isinstance(value,(Metrics.Isotropic,Metrics.Diagonal)) 236 return PreProcess('cost' if key=='metric' else 'speed',value.to_HFM(),refined_in,raw_out,cache) 237 if isinstance(value,Metrics.Base): 238 if ad.is_ad(value,iterables=(Metrics.Base,)): 239 metric_ad = value if key=='metric' else value.dual() 240 setkey_safe(raw_out,'costVariation',flow_variation(cache.geodesicFlow(refined_in),metric_ad)) 241 value = ad.remove_ad(value.to_HFM()) 242 setkey_safe(raw_out,key,value) 243 244 elif key=='seedValues': 245 if ad.is_ad(value): 246 setkey_safe(raw_out,'seedValueVariation',value.gradient()) 247 value=ad.remove_ad(value) 248 setkey_safe(raw_out,key,value) 249 250 elif key=='extractValues': 251 setkey_safe(raw_out,'exportValues',value) 252 else: 253 setkey_safe(raw_out,key,value) 254 255#---------- Post processing ------------ 256def PostProcess(key,value,raw_in,refined_out): 257 """ 258 copies key,val from raw to refined, with adequate treatment 259 """ 260 if key.startswith('geodesicPoints'): 261 suffix = key[len('geodesicPoints'):] 262 geodesics = GetGeodesics(raw_in,suffix=suffix) 263 setkey_safe(refined_out,"geodesics"+suffix,geodesics) 264 elif key.startswith('geodesicLengths'): 265 pass 266 267 elif key=='values': 268 if 'valueVariation' in raw_in: 269 value = ad.Dense.denseAD(value,raw_in['valueVariation']) 270 setkey_safe(refined_out,key,value) 271 elif key=='valueVariation': 272 pass 273 274 elif key=='geodesicFlow': 275 setkey_safe(refined_out,'flow',np.moveaxis(value,-1,0)) 276 elif key=='factoringGradients': 277 setkey_safe(refined_out,'factoringGradients',np.moveaxis(value,-1,0)) 278 elif key=='activeOffsets': 279 setkey_safe(refined_out,'offsets',CastOffsets(value)) 280 else: 281 setkey_safe(refined_out,key,value) 282 283def CastOffsets(raw_offsets): 284 """Offsets are exported with their coefficients bundled together in a double. 285 This function unpacks them.""" 286 raw_shape = raw_offsets.shape 287 d = len(raw_shape)-1 288 nOffsets = raw_shape[-1] 289 refined_shape = (d,nOffsets)+raw_shape[:-1] 290 refined_offsets = np.zeros(refined_shape,dtype=int) 291 def coef(x,i): 292 x = x//256**(d-i-1) 293 x = x%256 294 return np.where(x<128,x,x-256) 295 296 for j in range(nOffsets): 297 raw = raw_offsets[...,j].astype(np.int64) 298 for i in range(d): 299 refined_offsets[i,j] = coef(raw,i) 300 return refined_offsets
def
GetGeodesics(output, suffix=''):
class
Cache:
17class Cache(object): 18 def __init__(self,needsflow=False): 19 self.contents = dict() 20 self.verbosity = None 21 self.requested = None 22 self.needsflow = needsflow 23 self.dummy = False 24 25 def empty(self): 26 """Wether the cache lacks data needed to bypass computation""" 27 return not self.contents 28 29# def full(self): 30# return self.contents and ('geodesicFlow' in self.contents or not needsflow) 31 32 def PreProcess(self,hfmIn_raw): 33 if self.dummy: return 34 self.verbosity = hfmIn_raw.get('verbosity',1) 35 self.requested = [] 36 if hfmIn_raw.get('exportValues',False): self.requested.append('values') 37 if hfmIn_raw.get('exportActiveNeighs',False): self.requested.append('activeNeighs') 38 if hfmIn_raw.get('exportGeodesicFlow',False): self.requested.append('geodesicFlow') 39 if self.empty(): 40 if self.verbosity>=1: print("Requesting cacheable data") 41 hfmIn_raw['exportValues']=True 42 hfmIn_raw['exportActiveNeighs']=True 43 if self.needsflow: hfmIn_raw['exportGeodesicFlow']=True 44 else: 45 if self.verbosity>=1: print("Providing cached data") 46 for key in ('values','activeNeighs'): 47 setkey_safe(hfmIn_raw,key,self.contents[key]) 48 # Note : all points set as accepted in HFM algorithm 49 hfmIn_raw['exportValues']=False 50 hfmIn_raw['exportActiveNeighs']=False 51 hfmIn_raw['exportGeodesicFlow']=self.needsflow and ('geodesicFlow' not in self.contents) 52 53 def PostProcess(self,hfmOut_raw): 54 if self.dummy: return 55 if self.empty(): 56 if self.verbosity>=1 : print("Filling cache data") 57 for key in ('values','activeNeighs'): 58 self.contents[key] = hfmOut_raw[key] 59 if self.needsflow: 60 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow'] 61 else: 62 for key in self.requested: 63 if key not in hfmOut_raw: 64 setkey_safe(hfmOut_raw,key,self.contents[key]) 65 if self.needsflow and 'geodesicFlow' not in self.contents: 66 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow'] 67 68 def geodesicFlow(self,hfmIn=None): 69 if 'geodesicFlow' in self.contents: 70 return self.contents['geodesicFlow'] 71 elif hfmIn is None: 72 raise ValueError("geodesicFlow not cached and lacking hfm input data") 73 else: 74 self.dummy = False 75 self.needsflow = True 76 77 hfmIn_ = {key:ad.remove_ad(value,iterables=(Metrics.Base,)) for key,value in hfmIn.items() 78 if key not in [ # Keys useless for evaluating the geodesic flow 79 'tips','tips_Unoriented', 80 'costVariation','seedValueVariation','inspectSensitivity', 81 'exportActiveNeighs','exportActiveOffsets']} 82 83 hfmOut_raw = RunSmart(hfmIn_,cache=self,returns='out_raw') 84 if self.verbosity: 85 print("--- HFM call triggered above to compute geodesic flow ---") 86 self.PostProcess(hfmOut_raw) 87 return self.contents['geodesicFlow']
def
empty(self):
25 def empty(self): 26 """Wether the cache lacks data needed to bypass computation""" 27 return not self.contents
Wether the cache lacks data needed to bypass computation
def
PreProcess(self, hfmIn_raw):
32 def PreProcess(self,hfmIn_raw): 33 if self.dummy: return 34 self.verbosity = hfmIn_raw.get('verbosity',1) 35 self.requested = [] 36 if hfmIn_raw.get('exportValues',False): self.requested.append('values') 37 if hfmIn_raw.get('exportActiveNeighs',False): self.requested.append('activeNeighs') 38 if hfmIn_raw.get('exportGeodesicFlow',False): self.requested.append('geodesicFlow') 39 if self.empty(): 40 if self.verbosity>=1: print("Requesting cacheable data") 41 hfmIn_raw['exportValues']=True 42 hfmIn_raw['exportActiveNeighs']=True 43 if self.needsflow: hfmIn_raw['exportGeodesicFlow']=True 44 else: 45 if self.verbosity>=1: print("Providing cached data") 46 for key in ('values','activeNeighs'): 47 setkey_safe(hfmIn_raw,key,self.contents[key]) 48 # Note : all points set as accepted in HFM algorithm 49 hfmIn_raw['exportValues']=False 50 hfmIn_raw['exportActiveNeighs']=False 51 hfmIn_raw['exportGeodesicFlow']=self.needsflow and ('geodesicFlow' not in self.contents)
def
PostProcess(self, hfmOut_raw):
53 def PostProcess(self,hfmOut_raw): 54 if self.dummy: return 55 if self.empty(): 56 if self.verbosity>=1 : print("Filling cache data") 57 for key in ('values','activeNeighs'): 58 self.contents[key] = hfmOut_raw[key] 59 if self.needsflow: 60 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow'] 61 else: 62 for key in self.requested: 63 if key not in hfmOut_raw: 64 setkey_safe(hfmOut_raw,key,self.contents[key]) 65 if self.needsflow and 'geodesicFlow' not in self.contents: 66 self.contents['geodesicFlow'] = hfmOut_raw['geodesicFlow']
def
geodesicFlow(self, hfmIn=None):
68 def geodesicFlow(self,hfmIn=None): 69 if 'geodesicFlow' in self.contents: 70 return self.contents['geodesicFlow'] 71 elif hfmIn is None: 72 raise ValueError("geodesicFlow not cached and lacking hfm input data") 73 else: 74 self.dummy = False 75 self.needsflow = True 76 77 hfmIn_ = {key:ad.remove_ad(value,iterables=(Metrics.Base,)) for key,value in hfmIn.items() 78 if key not in [ # Keys useless for evaluating the geodesic flow 79 'tips','tips_Unoriented', 80 'costVariation','seedValueVariation','inspectSensitivity', 81 'exportActiveNeighs','exportActiveOffsets']} 82 83 hfmOut_raw = RunSmart(hfmIn_,cache=self,returns='out_raw') 84 if self.verbosity: 85 print("--- HFM call triggered above to compute geodesic flow ---") 86 self.PostProcess(hfmOut_raw) 87 return self.contents['geodesicFlow']
def
RunRaw(hfmIn):
90def RunRaw(hfmIn): 91 """Raw call to the HFM library""" 92 rawIn = hfmIn.copy() 93 mode = rawIn.pop('mode',None); assert mode is None or mode=='cpu_raw' 94 float_t = rawIn.pop('float_t',None); assert float_t is None or float_t==np.float64 95 return RunDispatch(rawIn,GetBinaryDir("FileHFM","HFMpy"))
Raw call to the HFM library
def
RunSmart(hfmIn, co_output=None, cache=None, returns='out'):
98def RunSmart(hfmIn,co_output=None,cache=None,returns="out"): 99 """ 100 Calls the HFM library, a CPU eikonal solver written in C++, 101 with pre-processing and post-processing of data. 102 103 Main input : 104 - hfmIn, a dictionary like structure, containing the eikonal solver data. 105 106 The C++ library embeds some help information, which can be accessed using the 107 following key:value pairs in hfmIn. 108 * 'verbosity' : set to 1 or 2 to display information on run, including the defaulted keys. 109 set to 0 to silence the run. 110 * 'keyHelp' : set as a string of keys separated by spaces, to print help on these. 111 example 'keyHelp':"seeds tips" 112 113 Optional input: 114 - co_output (optional) : used for reverse automatic differentiation. 115 - cache (optional) : store some intermediate results to bypass computations at a later stage. 116 - returns (optional) : early aborts the run (debug purposes). ('in_raw','out_raw','out') 117 """ 118 assert returns in ('in_raw','out_raw','out') 119 120 if factoringPointChoice(hfmIn) == 'Both': 121 hfmIn = hfmIn.copy() 122 hfmIn.SetFactor() 123 124 hfmIn_raw = {} 125 126 if cache is None: 127 cache = Cache() 128 cache.dummy = True 129 130 # Pre-process usual arguments 131 for key,value in hfmIn.items(): 132 PreProcess(key,value,hfmIn,hfmIn_raw,cache) 133 134 # Reverse automatic differentiation 135 if co_output is not None: 136 assert ad.misc.reverse_mode(co_output)=="Reverse" 137 [co_hfmOut,co_value],_ = co_output 138 assert hfmIn.get('extractValues',False) and co_hfmOut is None 139 indices = np.nonzero(co_value) 140 positions = hfmIn.PointFromIndex(np.asarray(indices).T) 141 weights = co_value[indices] 142 setkey_safe(hfmIn_raw,'inspectSensitivity',positions) 143 setkey_safe(hfmIn_raw,'inspectSensitivityWeights',weights) 144 setkey_safe(hfmIn_raw,'inspectSensitivityLengths',[len(weights)]) 145 if 'metric' in hfmIn or 'dualMetric' in hfmIn: 146 cache.needsflow = True 147 cache.dummy = False 148 149 # Dealing with cached data 150 cache.PreProcess(hfmIn_raw) 151 152 # Call to the HFM library 153 if returns=='in_raw': return hfmIn_raw 154 hfmOut_raw = RunDispatch(hfmIn_raw,GetBinaryDir("FileHFM","HFMpy")) 155 if returns=='out_raw': return hfmOut_raw 156 157 # Dealing with cached data 158 cache.PostProcess(hfmOut_raw) 159 160 # Post process 161 hfmOut = {} 162 for key,val in hfmOut_raw.items(): 163 PostProcess(key,val,hfmOut_raw,hfmOut) 164 165 # Reverse automatic differentiation 166 if co_output is not None: 167 result=[] 168 for key,value in hfmIn.items(): 169 if key in ('cost','speed'): 170 if isinstance(value,Metrics.Isotropic): 171 value = value.to_HFM() 172 result.append((value, 173 hfmOut['costSensitivity_0'] if key=='cost' else (hfmOut['costSensitivity_0']/value**2))) 174 if key in ('metric','dualMetric'): 175 shape_bound = value.shape 176 considered = co_output.second 177 value_ad = ad.Dense.register(value,iterables=(Metrics.Base,),shape_bound=shape_bound,considered=considered) 178 metric_ad = value_ad if key=='metric' else value_ad.dual() 179 180 costSensitivity = np.moveaxis(flow_variation(cache.geodesicFlow(hfmIn),metric_ad),-1,0)*hfmOut['costSensitivity_0'] 181 shift = 0 182 size_bound = np.prod(shape_bound,dtype=int) 183 for x in value: 184 if any(x is val for val in considered): 185 xsize_free = x.size//size_bound 186 result.append((x,costSensitivity[shift:(shift+xsize_free)].reshape(x.shape)) ) 187 shift+=xsize_free 188 elif key=='seedValues': 189 sens = hfmOut['seedSensitivity_0'] 190 # Match the seeds with their approx given in sensitivity 191 corresp = np.argmin(ad.Optimization.norm(np.expand_dims(hfmIn_raw['seeds'],axis=2) 192 -np.expand_dims(sens[:,:2].T,axis=1),ord=2,axis=0),axis=0) 193 sens_ordered = np.zeros_like(value) 194 sens_ordered[corresp]=sens[:,2] 195 result.append((value,sens_ordered)) 196 # TODO : speed 197 return result 198 199 return (hfmOut,hfmOut.pop('values')) if hfmIn.get('extractValues',False) else hfmOut
Calls the HFM library, a CPU eikonal solver written in C++, with pre-processing and post-processing of data.
Main input :
- hfmIn, a dictionary like structure, containing the eikonal solver data.
The C++ library embeds some help information, which can be accessed using the following key:value pairs in hfmIn.
- 'verbosity' : set to 1 or 2 to display information on run, including the defaulted keys. set to 0 to silence the run.
- 'keyHelp' : set as a string of keys separated by spaces, to print help on these. example 'keyHelp':"seeds tips"
Optional input:
- co_output (optional) : used for reverse automatic differentiation.
- cache (optional) : store some intermediate results to bypass computations at a later stage.
- returns (optional) : early aborts the run (debug purposes). ('in_raw','out_raw','out')
def
setkey_safe(dico, key, value):
def
flow_variation(flow, metric):
208def flow_variation(flow,metric): 209 flow = np.moveaxis(flow,-1,0) 210 zeros = np.all(flow==0.,axis=0) 211 flow.__setitem__((slice(None),zeros),np.nan) 212 norm = metric.norm(flow) 213 variation = norm.coef/np.expand_dims(norm.value,axis=-1) 214 variation.__setitem__((zeros,slice(None)),0.) 215 return variation
def
PreProcess(key, value, refined_in, raw_out, cache):
218def PreProcess(key,value,refined_in,raw_out,cache): 219 """ 220 copies key,val from refined to raw, with adequate treatment 221 """ 222 223 verbosity = refined_in.get('verbosity',1) 224 if key=='mode': assert value in ('cpu','cpu_transfer'); return 225 elif key=='float_t': assert value==np.float64; return 226 elif key in ('cost','speed'): 227 if isinstance(value,Metrics.Isotropic): 228 value = value.to_HFM() 229 if isinstance(value,ad.Dense.denseAD): 230 setkey_safe(raw_out,'costVariation', 231 value.coef if key=='cost' else (1/value).coef) 232 value = ad.remove_ad(value) 233 setkey_safe(raw_out,key,value) 234 elif key in ('metric','dualMetric'): 235 if refined_in['model'].startswith('Isotropic') or refined_in['model'].startswith('Diagonal'): 236 assert isinstance(value,(Metrics.Isotropic,Metrics.Diagonal)) 237 return PreProcess('cost' if key=='metric' else 'speed',value.to_HFM(),refined_in,raw_out,cache) 238 if isinstance(value,Metrics.Base): 239 if ad.is_ad(value,iterables=(Metrics.Base,)): 240 metric_ad = value if key=='metric' else value.dual() 241 setkey_safe(raw_out,'costVariation',flow_variation(cache.geodesicFlow(refined_in),metric_ad)) 242 value = ad.remove_ad(value.to_HFM()) 243 setkey_safe(raw_out,key,value) 244 245 elif key=='seedValues': 246 if ad.is_ad(value): 247 setkey_safe(raw_out,'seedValueVariation',value.gradient()) 248 value=ad.remove_ad(value) 249 setkey_safe(raw_out,key,value) 250 251 elif key=='extractValues': 252 setkey_safe(raw_out,'exportValues',value) 253 else: 254 setkey_safe(raw_out,key,value)
copies key,val from refined to raw, with adequate treatment
def
PostProcess(key, value, raw_in, refined_out):
257def PostProcess(key,value,raw_in,refined_out): 258 """ 259 copies key,val from raw to refined, with adequate treatment 260 """ 261 if key.startswith('geodesicPoints'): 262 suffix = key[len('geodesicPoints'):] 263 geodesics = GetGeodesics(raw_in,suffix=suffix) 264 setkey_safe(refined_out,"geodesics"+suffix,geodesics) 265 elif key.startswith('geodesicLengths'): 266 pass 267 268 elif key=='values': 269 if 'valueVariation' in raw_in: 270 value = ad.Dense.denseAD(value,raw_in['valueVariation']) 271 setkey_safe(refined_out,key,value) 272 elif key=='valueVariation': 273 pass 274 275 elif key=='geodesicFlow': 276 setkey_safe(refined_out,'flow',np.moveaxis(value,-1,0)) 277 elif key=='factoringGradients': 278 setkey_safe(refined_out,'factoringGradients',np.moveaxis(value,-1,0)) 279 elif key=='activeOffsets': 280 setkey_safe(refined_out,'offsets',CastOffsets(value)) 281 else: 282 setkey_safe(refined_out,key,value)
copies key,val from raw to refined, with adequate treatment
def
CastOffsets(raw_offsets):
284def CastOffsets(raw_offsets): 285 """Offsets are exported with their coefficients bundled together in a double. 286 This function unpacks them.""" 287 raw_shape = raw_offsets.shape 288 d = len(raw_shape)-1 289 nOffsets = raw_shape[-1] 290 refined_shape = (d,nOffsets)+raw_shape[:-1] 291 refined_offsets = np.zeros(refined_shape,dtype=int) 292 def coef(x,i): 293 x = x//256**(d-i-1) 294 x = x%256 295 return np.where(x<128,x,x-256) 296 297 for j in range(nOffsets): 298 raw = raw_offsets[...,j].astype(np.int64) 299 for i in range(d): 300 refined_offsets[i,j] = coef(raw,i) 301 return refined_offsets
Offsets are exported with their coefficients bundled together in a double. This function unpacks them.