agd.ODE.backtrack
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 4 5import itertools 6import copy 7import numpy as np 8from ..Interpolation import map_coordinates,origin_scale_shape #UniformGridInterpolation 9 10def odeint_array(f,y,t,grid,t_delay=0,t_substeps=2,order=1,**kwargs): 11 """ 12 Solve an ODE where the vector field is interpolated from an array. 13 The vector field is linearly interpolated, and the Euler midpoint scheme is used. 14 15 Inputs : 16 - f : generator of the vector fields to be interpolated, len(next(f))=vdim 17 - y : initial value of the solution, len(y) = vdim 18 - t : time steps 19 - grid : interpolation grid for the vector field f 20 - t_delay : number of time steps to drop initially 21 - t_substeps : number of substeps 22 - order : passed to odeint_array 23 - **kwargs : passed to UniformGridInterpolation 24 """ 25 nt = len(t) 26 solution = np.full_like(y,np.nan,shape=(nt,*y.shape)) 27 y0 = copy.copy(y) 28 fit = iter(f) 29 origin,scale,_ = origin_scale_shape(grid) 30 def I(values,positions): # Interpolation 31 return map_coordinates(values,positions,origin=origin,scale=scale, 32 depth=1,order=order,**kwargs) 33 34 f1 = next(fit) 35 for i in range(nt-1): 36 f0,f1 = f1,next(fit) # Vector fields to be interpolated 37 t0,t1 = t[i],t[i+1] 38 dt = (t1-t0)/t_substeps 39 solution[i][:,t_delay==i] = y[:,t_delay==i] # Insert initial values 40 valid = t_delay <= i 41 y0 = solution[i] if np.all(valid) else solution[i][:,valid] 42 for s in range(t_substeps): 43 w = s/t_substeps 44 y1 = y0 + 0.5*dt * ((1-w)*I(f0,y0) + w*I(f1,y0)) # Compute midpoint 45 w = (s+0.5)/t_substeps 46 y0 = y0 + dt * ((1-w)*I(f0,y1) + w*I(f1,y1)) # Make step 47 if np.all(valid): solution[i+1] = y0 48 else: solution[i+1][:,valid]=y0 49 50 return solution 51 52class RecurseRewind: 53 r""" 54 This class is designed to iterate a function, and then roll back the iterations, 55 with a limited memory usage. For that purpose appropriate keypoints are stored, 56 in a multilevel manner. 57 __init__ args: 58 - next : method to compute the next step. 59 - initial : initial value 60 - params : passed to next, in addition to current value 61 - base : base $b$ used to internally represent the iteration counter, which balances 62 a tradeoff between memory usage and computational cost. Iterating $O(b^n)$ times 63 and then rewinding these iterations, has a storage cost of $O(n*b)$ and a 64 computational cost of $O(n * b^n)$ function evaluations. 65 66 members: 67 - reversed : wether __next__ should advance or rewind the iterations 68 """ 69 70 def __init__(self,next,initial,params=tuple(),base=5): 71 self.next = next 72 self.params = params 73 self._base = base 74 self._keys = {0:initial} 75 self._index = 0 76 self.reversed = False 77 self._initial_value = True 78 79 def __call__(self,index=None): 80 """The value of the current iteration if index is None, otherwise moves to index""" 81 if index is not None: self.moveto(index) 82 return self.value 83 84 def __iter__(self): 85 self._initial_value = True 86 return self 87 88 def __next__(self): 89 if self._initial_value: self._initial_value = False 90 elif self.reversed: self.rewind() 91 else: self.advance() 92 return self.value 93 94 95 def advance(self): 96 """Compute next iterate of f, and update keypoints""" 97 self._keys[self.index+1] = self.next(self._keys[self.index],*self.params) 98 self._index += 1 99 # Delete previous keys to save storage 100 for i in range(self._basepow): 101 k = self.base**i 102 for j in range(1,self.base): 103 self._keys.pop(self.index - j*k) 104 105 def rewind(self): 106 """Compute previous iterate of f, and update keypoints""" 107 if self.index<=0: raise StopIteration 108 self._keys.pop(self.index) 109 i = self._basepow 110 if i: 111 k = self.base**i 112 self._index -= k 113 for _ in range(k-1): 114 self.advance() 115 else: 116 self._index -= 1 117 118 def moveto(self,index): 119 """Compute prescribed iterate of f, and update keypoints""" 120 # Possible improvements here, especially for functions which can be iterated at a reduced 121 # cost, e.g. by merging the final and initial substeps of symplectic schemes for wave PDEs 122 if index < self.index: 123 for i in range(self.index-index): self.rewind() 124 if index > self.index: 125 for i in range(index-self.index): self.advance() 126 127 @property 128 def _basepow(self): 129 """Returns the largest i such that self.base**i divides self.index""" 130 assert self.index!=0 131 basepow = self.base 132 i = 0 133 while self.index%basepow == 0: 134 basepow *= self.base 135 i+=1 136 return i 137 138 @property 139 def index(self): 140 """The index of the current iteration""" 141 return self._index 142 @property 143 def value(self): 144 """The value of the current iteration""" 145 return self._keys[self.index] 146 @property 147 def base(self):return self._base 148 149
def
odeint_array(f, y, t, grid, t_delay=0, t_substeps=2, order=1, **kwargs):
11def odeint_array(f,y,t,grid,t_delay=0,t_substeps=2,order=1,**kwargs): 12 """ 13 Solve an ODE where the vector field is interpolated from an array. 14 The vector field is linearly interpolated, and the Euler midpoint scheme is used. 15 16 Inputs : 17 - f : generator of the vector fields to be interpolated, len(next(f))=vdim 18 - y : initial value of the solution, len(y) = vdim 19 - t : time steps 20 - grid : interpolation grid for the vector field f 21 - t_delay : number of time steps to drop initially 22 - t_substeps : number of substeps 23 - order : passed to odeint_array 24 - **kwargs : passed to UniformGridInterpolation 25 """ 26 nt = len(t) 27 solution = np.full_like(y,np.nan,shape=(nt,*y.shape)) 28 y0 = copy.copy(y) 29 fit = iter(f) 30 origin,scale,_ = origin_scale_shape(grid) 31 def I(values,positions): # Interpolation 32 return map_coordinates(values,positions,origin=origin,scale=scale, 33 depth=1,order=order,**kwargs) 34 35 f1 = next(fit) 36 for i in range(nt-1): 37 f0,f1 = f1,next(fit) # Vector fields to be interpolated 38 t0,t1 = t[i],t[i+1] 39 dt = (t1-t0)/t_substeps 40 solution[i][:,t_delay==i] = y[:,t_delay==i] # Insert initial values 41 valid = t_delay <= i 42 y0 = solution[i] if np.all(valid) else solution[i][:,valid] 43 for s in range(t_substeps): 44 w = s/t_substeps 45 y1 = y0 + 0.5*dt * ((1-w)*I(f0,y0) + w*I(f1,y0)) # Compute midpoint 46 w = (s+0.5)/t_substeps 47 y0 = y0 + dt * ((1-w)*I(f0,y1) + w*I(f1,y1)) # Make step 48 if np.all(valid): solution[i+1] = y0 49 else: solution[i+1][:,valid]=y0 50 51 return solution
Solve an ODE where the vector field is interpolated from an array. The vector field is linearly interpolated, and the Euler midpoint scheme is used.
Inputs :
- f : generator of the vector fields to be interpolated, len(next(f))=vdim
- y : initial value of the solution, len(y) = vdim
- t : time steps
- grid : interpolation grid for the vector field f
- t_delay : number of time steps to drop initially
- t_substeps : number of substeps
- order : passed to odeint_array
- **kwargs : passed to UniformGridInterpolation
class
RecurseRewind:
53class RecurseRewind: 54 r""" 55 This class is designed to iterate a function, and then roll back the iterations, 56 with a limited memory usage. For that purpose appropriate keypoints are stored, 57 in a multilevel manner. 58 __init__ args: 59 - next : method to compute the next step. 60 - initial : initial value 61 - params : passed to next, in addition to current value 62 - base : base $b$ used to internally represent the iteration counter, which balances 63 a tradeoff between memory usage and computational cost. Iterating $O(b^n)$ times 64 and then rewinding these iterations, has a storage cost of $O(n*b)$ and a 65 computational cost of $O(n * b^n)$ function evaluations. 66 67 members: 68 - reversed : wether __next__ should advance or rewind the iterations 69 """ 70 71 def __init__(self,next,initial,params=tuple(),base=5): 72 self.next = next 73 self.params = params 74 self._base = base 75 self._keys = {0:initial} 76 self._index = 0 77 self.reversed = False 78 self._initial_value = True 79 80 def __call__(self,index=None): 81 """The value of the current iteration if index is None, otherwise moves to index""" 82 if index is not None: self.moveto(index) 83 return self.value 84 85 def __iter__(self): 86 self._initial_value = True 87 return self 88 89 def __next__(self): 90 if self._initial_value: self._initial_value = False 91 elif self.reversed: self.rewind() 92 else: self.advance() 93 return self.value 94 95 96 def advance(self): 97 """Compute next iterate of f, and update keypoints""" 98 self._keys[self.index+1] = self.next(self._keys[self.index],*self.params) 99 self._index += 1 100 # Delete previous keys to save storage 101 for i in range(self._basepow): 102 k = self.base**i 103 for j in range(1,self.base): 104 self._keys.pop(self.index - j*k) 105 106 def rewind(self): 107 """Compute previous iterate of f, and update keypoints""" 108 if self.index<=0: raise StopIteration 109 self._keys.pop(self.index) 110 i = self._basepow 111 if i: 112 k = self.base**i 113 self._index -= k 114 for _ in range(k-1): 115 self.advance() 116 else: 117 self._index -= 1 118 119 def moveto(self,index): 120 """Compute prescribed iterate of f, and update keypoints""" 121 # Possible improvements here, especially for functions which can be iterated at a reduced 122 # cost, e.g. by merging the final and initial substeps of symplectic schemes for wave PDEs 123 if index < self.index: 124 for i in range(self.index-index): self.rewind() 125 if index > self.index: 126 for i in range(index-self.index): self.advance() 127 128 @property 129 def _basepow(self): 130 """Returns the largest i such that self.base**i divides self.index""" 131 assert self.index!=0 132 basepow = self.base 133 i = 0 134 while self.index%basepow == 0: 135 basepow *= self.base 136 i+=1 137 return i 138 139 @property 140 def index(self): 141 """The index of the current iteration""" 142 return self._index 143 @property 144 def value(self): 145 """The value of the current iteration""" 146 return self._keys[self.index] 147 @property 148 def base(self):return self._base
This class is designed to iterate a function, and then roll back the iterations, with a limited memory usage. For that purpose appropriate keypoints are stored, in a multilevel manner. __init__ args:
- next : method to compute the next step.
- initial : initial value
- params : passed to next, in addition to current value
- base : base $b$ used to internally represent the iteration counter, which balances a tradeoff between memory usage and computational cost. Iterating $O(b^n)$ times and then rewinding these iterations, has a storage cost of $O(n*b)$ and a computational cost of $O(n * b^n)$ function evaluations.
members:
- reversed : wether __next__ should advance or rewind the iterations
def
advance(self):
96 def advance(self): 97 """Compute next iterate of f, and update keypoints""" 98 self._keys[self.index+1] = self.next(self._keys[self.index],*self.params) 99 self._index += 1 100 # Delete previous keys to save storage 101 for i in range(self._basepow): 102 k = self.base**i 103 for j in range(1,self.base): 104 self._keys.pop(self.index - j*k)
Compute next iterate of f, and update keypoints
def
rewind(self):
106 def rewind(self): 107 """Compute previous iterate of f, and update keypoints""" 108 if self.index<=0: raise StopIteration 109 self._keys.pop(self.index) 110 i = self._basepow 111 if i: 112 k = self.base**i 113 self._index -= k 114 for _ in range(k-1): 115 self.advance() 116 else: 117 self._index -= 1
Compute previous iterate of f, and update keypoints
def
moveto(self, index):
119 def moveto(self,index): 120 """Compute prescribed iterate of f, and update keypoints""" 121 # Possible improvements here, especially for functions which can be iterated at a reduced 122 # cost, e.g. by merging the final and initial substeps of symplectic schemes for wave PDEs 123 if index < self.index: 124 for i in range(self.index-index): self.rewind() 125 if index > self.index: 126 for i in range(index-self.index): self.advance()
Compute prescribed iterate of f, and update keypoints
index
139 @property 140 def index(self): 141 """The index of the current iteration""" 142 return self._index
The index of the current iteration