TransitionListener 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transitionlistener/__init__.py +21 -0
- transitionlistener/bubbledynamics.py +2250 -0
- transitionlistener/bubbledynamics_fixedstep.py +2929 -0
- transitionlistener/check_installation.py +309 -0
- transitionlistener/ckm.py +91 -0
- transitionlistener/colors.py +288 -0
- transitionlistener/config.py +185 -0
- transitionlistener/constants.py +64 -0
- transitionlistener/counterterms/__init__.py +230 -0
- transitionlistener/counterterms/cw.py +298 -0
- transitionlistener/counterterms/tensors.py +343 -0
- transitionlistener/counterterms/twohdm.py +520 -0
- transitionlistener/counterterms/twohdm_solver.py +374 -0
- transitionlistener/errors.py +315 -0
- transitionlistener/examples/bp_et.yaml +64 -0
- transitionlistener/examples/bp_lisa.yaml +70 -0
- transitionlistener/examples/bp_muares.yaml +69 -0
- transitionlistener/examples/bp_pta.yaml +64 -0
- transitionlistener/examples/example_dark_photon.yaml +73 -0
- transitionlistener/examples/example_grid.yaml +55 -0
- transitionlistener/examples/example_line.yaml +51 -0
- transitionlistener/examples/example_point.yaml +73 -0
- transitionlistener/examples/example_random_conformal.yaml +42 -0
- transitionlistener/examples/example_ultranest.yaml +39 -0
- transitionlistener/finiteT.py +468 -0
- transitionlistener/generic_potential.py +1521 -0
- transitionlistener/gridplots.py +592 -0
- transitionlistener/gwfopt.py +604 -0
- transitionlistener/helper_functions.py +843 -0
- transitionlistener/hydrodynamics.py +647 -0
- transitionlistener/interface/__init__.py +95 -0
- transitionlistener/interface/check.py +51 -0
- transitionlistener/interface/cli.py +211 -0
- transitionlistener/interface/config_loader.py +264 -0
- transitionlistener/interface/logging_utils.py +30 -0
- transitionlistener/interface/output_schema.py +296 -0
- transitionlistener/interface/pipeline.py +300 -0
- transitionlistener/interface/samplers.py +565 -0
- transitionlistener/interface/scans.py +468 -0
- transitionlistener/interface/single_point.py +581 -0
- transitionlistener/interface/state.py +61 -0
- transitionlistener/lineplots.py +597 -0
- transitionlistener/logo/TL-logo_large.png +0 -0
- transitionlistener/logo/TL-logo_small.png +0 -0
- transitionlistener/models/TL_2HDM.py +512 -0
- transitionlistener/models/TL_conformal_dark_u1.py +202 -0
- transitionlistener/models/TL_dark_U1.py +198 -0
- transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
- transitionlistener/models/TL_dark_flipflop.py +274 -0
- transitionlistener/models/templatePotential.py +260 -0
- transitionlistener/nucleation.py +412 -0
- transitionlistener/observability.py +790 -0
- transitionlistener/particles.py +436 -0
- transitionlistener/pathDeformation.py +1462 -0
- transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
- transitionlistener/percolation_adaptive_rate.py +1049 -0
- transitionlistener/percolation_adaptivestepsize.py +1632 -0
- transitionlistener/percolation_fixedstepsize.py +618 -0
- transitionlistener/phases.py +1499 -0
- transitionlistener/plot_settings.py +121 -0
- transitionlistener/plots.py +1781 -0
- transitionlistener/runtime_options.py +278 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
- transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
- transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
- transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
- transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
- transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
- transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
- transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
- transitionlistener/tab_data/PLI-dataset.csv +327 -0
- transitionlistener/tab_data/energydensities.dat +1001 -0
- transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
- transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
- transitionlistener/tab_data/geff_SM.dat +10009 -0
- transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
- transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
- transitionlistener/tab_data/noise-dataset.csv +327 -0
- transitionlistener/tab_data/numberdensities.dat +1001 -0
- transitionlistener/tab_data/pressures.dat +1001 -0
- transitionlistener/thermodynamics.py +686 -0
- transitionlistener/transitionObservables.py +1199 -0
- transitionlistener/transitionObservables_fixedstep.py +976 -0
- transitionlistener/transitions.py +1000 -0
- transitionlistener/tunneling1D.py +910 -0
- transitionlistener-2.0.0.dist-info/METADATA +817 -0
- transitionlistener-2.0.0.dist-info/RECORD +95 -0
- transitionlistener-2.0.0.dist-info/WHEEL +4 -0
- transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
- transitionlistener-2.0.0.dist-info/licenses/LICENSE.txt +674 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A collection of (mostly) stand alone helper functions.
|
|
3
|
+
|
|
4
|
+
Part of TransitionListener v2.0
|
|
5
|
+
Documentation: https://tasillo.de/TransitionListener/
|
|
6
|
+
|
|
7
|
+
Authors:
|
|
8
|
+
Jonas Matuszak <jonas.matuszak@kit.edu>
|
|
9
|
+
Carlo Tasillo <carlo.tasillo@ific.uv.es>
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from collections import namedtuple
|
|
14
|
+
import importlib.util
|
|
15
|
+
from itertools import product as _product
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Miscellaneous functions
|
|
21
|
+
~~~~~~~~~~~~~~~~~~~~~~~
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def product(*iterables, repeat=1):
|
|
25
|
+
"""Cartesian product with __len__ support."""
|
|
26
|
+
sequences = [tuple(it) for it in iterables]
|
|
27
|
+
length = 1
|
|
28
|
+
for seq in sequences:
|
|
29
|
+
length *= len(seq)
|
|
30
|
+
length = length ** repeat
|
|
31
|
+
|
|
32
|
+
class ProductWithLen:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._it = _product(*sequences, repeat=repeat)
|
|
35
|
+
|
|
36
|
+
def __iter__(self):
|
|
37
|
+
return self._it
|
|
38
|
+
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return length
|
|
41
|
+
|
|
42
|
+
return ProductWithLen()
|
|
43
|
+
|
|
44
|
+
def import_file(filename : str ) -> object:
|
|
45
|
+
"""Import a module from an arbitrary path and return the loaded module object.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
filename : str | pathlib.Path
|
|
50
|
+
Absolute or relative path to a Python file containing module-level code.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
ModuleType
|
|
55
|
+
The imported, ready-to-use module object.
|
|
56
|
+
"""
|
|
57
|
+
spec = importlib.util.spec_from_file_location("module.name", filename)
|
|
58
|
+
try:
|
|
59
|
+
module = importlib.util.module_from_spec(spec)
|
|
60
|
+
except AttributeError:
|
|
61
|
+
print("Could not import module from file '%s'. " % filename)
|
|
62
|
+
exit()
|
|
63
|
+
spec.loader.exec_module(module)
|
|
64
|
+
return module
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def load_potential(file: str, pot_name: str) -> Any:
|
|
68
|
+
"""Load a potential class from the specified file
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
file : str
|
|
73
|
+
Name of the file with path
|
|
74
|
+
pot_name : str
|
|
75
|
+
Name of the potential
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
object :
|
|
80
|
+
Child of the generic_potential class. """
|
|
81
|
+
spec = importlib.util.spec_from_file_location("module.name", file)
|
|
82
|
+
try:
|
|
83
|
+
module = importlib.util.module_from_spec(spec)
|
|
84
|
+
spec.loader.exec_module(module)
|
|
85
|
+
return getattr(module, pot_name)
|
|
86
|
+
except AttributeError:
|
|
87
|
+
print("Could not load potential %s from file '%s'. " % (pot_name, file))
|
|
88
|
+
print("Make sure you have specified the relative path and " +
|
|
89
|
+
"named the potential correctly!")
|
|
90
|
+
exit()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def setDefaultArgs(func, **kwargs):
|
|
94
|
+
"""
|
|
95
|
+
Changes the default args in `func` to match `kwargs`.
|
|
96
|
+
|
|
97
|
+
This can be useful when dealing with deeply nested functions for which the
|
|
98
|
+
default parameters cannot be set directly in the top-level function.
|
|
99
|
+
|
|
100
|
+
Raises
|
|
101
|
+
------
|
|
102
|
+
ValueError
|
|
103
|
+
if `func` does not have default arguments that match `kwargs`.
|
|
104
|
+
|
|
105
|
+
Example
|
|
106
|
+
-------
|
|
107
|
+
>>> def foo(bar="Hello world!"):
|
|
108
|
+
... print bar
|
|
109
|
+
>>> setDefaultArgs(foo, bar="The world has changed!")
|
|
110
|
+
>>> foo()
|
|
111
|
+
The world has changed!
|
|
112
|
+
"""
|
|
113
|
+
import inspect
|
|
114
|
+
args, varargs, varkwargs, defaults = inspect.getargspec(func)
|
|
115
|
+
have_defaults = args[-len(defaults):]
|
|
116
|
+
defaults = list(defaults) # so that we can write to it
|
|
117
|
+
for kwd in kwargs:
|
|
118
|
+
try:
|
|
119
|
+
i = have_defaults.index(kwd)
|
|
120
|
+
except ValueError:
|
|
121
|
+
raise ValueError("Function '%s()' does not have default argument "
|
|
122
|
+
"named '%s'." % (func.__name__, kwd))
|
|
123
|
+
defaults[i] = kwargs[kwd]
|
|
124
|
+
func.__defaults__ = tuple(defaults)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def monotonicIndices(x):
|
|
128
|
+
"""
|
|
129
|
+
Returns the indices of `x` such that `x[i]` is purely increasing.
|
|
130
|
+
"""
|
|
131
|
+
x = np.array(x)
|
|
132
|
+
if x[0] > x[-1]:
|
|
133
|
+
x = x[::-1]
|
|
134
|
+
is_reversed = True
|
|
135
|
+
else:
|
|
136
|
+
is_reversed = False
|
|
137
|
+
I = [0]
|
|
138
|
+
for i in range(1, len(x)-1):
|
|
139
|
+
if x[i] > x[I[-1]] and x[i] < x[-1]:
|
|
140
|
+
I.append(i)
|
|
141
|
+
I.append(len(x)-1)
|
|
142
|
+
if is_reversed:
|
|
143
|
+
return len(x)-1-np.array(I)
|
|
144
|
+
else:
|
|
145
|
+
return np.array(I)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def clampVal(x, a, b):
|
|
149
|
+
"""
|
|
150
|
+
Clamp the value `x` to be between `a` and `b`.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
x, a, b : array_like
|
|
155
|
+
Must have the same shape or be broadcastable.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
array_like
|
|
160
|
+
"""
|
|
161
|
+
s = np.sign(b-a) # +1 for b > a, -1 for b < a
|
|
162
|
+
sa = 1+s*np.sign(x-a)
|
|
163
|
+
x = (x*sa + a*(2-sa)) // 2
|
|
164
|
+
sb = 1+s*np.sign(b-x)
|
|
165
|
+
x = (x*sb + b*(2-sb)) // 2
|
|
166
|
+
return x
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def derivative(f, x, dx):
|
|
170
|
+
"""
|
|
171
|
+
Calculate the derivative of `f` at `x` using a centered finite difference
|
|
172
|
+
with step size `dx`. The derivative is calculated to 4th order in `dx`.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
f : callable
|
|
177
|
+
The function to differentiate. The first argument should be `x`.
|
|
178
|
+
x : array_like
|
|
179
|
+
The point at which to evaluate the derivative.
|
|
180
|
+
dx : float
|
|
181
|
+
The step size to use in the finite difference.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
----------
|
|
185
|
+
array_like
|
|
186
|
+
The derivative of `f` at `x`.
|
|
187
|
+
|
|
188
|
+
"""
|
|
189
|
+
df = -f(x + 2*dx) + 8*f(x + dx) - 8*f(x - dx) + f(x - 2*dx)
|
|
190
|
+
df = df/(12.0*dx)
|
|
191
|
+
return df
|
|
192
|
+
|
|
193
|
+
def eigenvalues_2x2(a, b, c):
|
|
194
|
+
"""Return the eigenvalues (small, large) of a 2x2 symmetric matrix.
|
|
195
|
+
|
|
196
|
+
The matrix has the form
|
|
197
|
+
|
|
198
|
+
.. math::
|
|
199
|
+
|
|
200
|
+
\\begin{pmatrix}
|
|
201
|
+
a & b \\\\
|
|
202
|
+
b & c
|
|
203
|
+
\\end{pmatrix}
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
a, b, c : array_like
|
|
208
|
+
The elements of the matrix.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
tuple[array_like, array_like]
|
|
213
|
+
``(small, large)`` eigenvalues of the matrix.
|
|
214
|
+
"""
|
|
215
|
+
trace = a + c
|
|
216
|
+
det = a * c - b * b
|
|
217
|
+
discriminant = np.maximum(trace * trace - 4.0 * det, 0.0)
|
|
218
|
+
root = np.sqrt(discriminant)
|
|
219
|
+
eig1 = 0.5 * (trace - root)
|
|
220
|
+
eig2 = 0.5 * (trace + root)
|
|
221
|
+
small = np.minimum(eig1, eig2)
|
|
222
|
+
large = np.maximum(eig1, eig2)
|
|
223
|
+
return small, large
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
Numerical integration
|
|
228
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class IntegrationError(Exception):
|
|
233
|
+
"""
|
|
234
|
+
Used to indicate an integration error, primarily in :func:`rkqs`.
|
|
235
|
+
"""
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
_rkqs_rval = namedtuple("rkqs_rval", "Delta_y Delta_t dtnxt")
|
|
240
|
+
def rkqs(y,dydt,t,f, dt_try, epsfrac, epsabs, args=()):
|
|
241
|
+
"""
|
|
242
|
+
Take a single 5th order Runge-Kutta step with error monitoring.
|
|
243
|
+
|
|
244
|
+
This function is adapted from Numerical Recipes in C.
|
|
245
|
+
|
|
246
|
+
The step size dynamically changes such that the error in `y` is smaller
|
|
247
|
+
than the larger of `epsfrac` and `epsabs`. That way, if one wants to
|
|
248
|
+
disregard the fractional error, set `epsfrac` to zero but keep `epsabs`
|
|
249
|
+
non-zero.
|
|
250
|
+
|
|
251
|
+
Parameters
|
|
252
|
+
----------
|
|
253
|
+
y, dydt : array_like
|
|
254
|
+
The initial value and its derivative at the start of the step.
|
|
255
|
+
They should satisfy ``dydt = f(y,t)``. `dydt` is included here for
|
|
256
|
+
efficiency (in case the calling function already calculated it).
|
|
257
|
+
t : float
|
|
258
|
+
The integration variable.
|
|
259
|
+
f : callable
|
|
260
|
+
The derivative function.
|
|
261
|
+
dt_try : float
|
|
262
|
+
An initial guess for the step size.
|
|
263
|
+
epsfrac, epsabs : array_like
|
|
264
|
+
The maximual fractional and absolute errors. Should be either length 1
|
|
265
|
+
or the same size as `y`.
|
|
266
|
+
args : tuple
|
|
267
|
+
Optional arguments for `f`.
|
|
268
|
+
|
|
269
|
+
Returns
|
|
270
|
+
-------
|
|
271
|
+
Delta_y : array_like
|
|
272
|
+
Change in `y` during this step.
|
|
273
|
+
Delta_t : float
|
|
274
|
+
Change in `t` during this step.
|
|
275
|
+
dtnext : float
|
|
276
|
+
Best guess for next step size.
|
|
277
|
+
|
|
278
|
+
Raises
|
|
279
|
+
------
|
|
280
|
+
IntegrationError
|
|
281
|
+
If the step size gets smaller than the floating point error.
|
|
282
|
+
|
|
283
|
+
References
|
|
284
|
+
----------
|
|
285
|
+
Based on algorithms described in [1]_.
|
|
286
|
+
|
|
287
|
+
.. [1] W. H. Press, et. al. "Numerical Recipes in C: The Art of Scientific
|
|
288
|
+
Computing. Second Edition." Cambridge, 1992.
|
|
289
|
+
"""
|
|
290
|
+
dt = dt_try
|
|
291
|
+
while True:
|
|
292
|
+
dy,yerr = _rkck(y,dydt,t,f,dt,args)
|
|
293
|
+
errmax = np.nan_to_num(np.max(np.min([
|
|
294
|
+
abs(yerr/epsabs), abs(yerr)/((abs(y)+1e-300)*epsfrac)
|
|
295
|
+
], axis=0)))
|
|
296
|
+
if(errmax < 1.0):
|
|
297
|
+
break # Step succeeded
|
|
298
|
+
dttemp = 0.9*dt*errmax**-.25
|
|
299
|
+
dt = max(dttemp,dt*.1) if dt > 0 else min(dttemp,dt*.1)
|
|
300
|
+
if t + dt == t:
|
|
301
|
+
raise IntegrationError("Stepsize rounds down to zero.")
|
|
302
|
+
if errmax > 1.89e-4:
|
|
303
|
+
dtnext = 0.9 * dt * errmax**-.2
|
|
304
|
+
else:
|
|
305
|
+
dtnext = 5*dt
|
|
306
|
+
return _rkqs_rval(dy, dt, dtnext)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def rkqs2(y,dydt,t,f, dt_try, inv_epsabs, args=()):
|
|
310
|
+
"""
|
|
311
|
+
Same as :func:`rkqs`, but ``inv_epsabs = 1/epsabs`` and ``epsfrac`` is
|
|
312
|
+
not used.
|
|
313
|
+
"""
|
|
314
|
+
dt = dt_try
|
|
315
|
+
while True:
|
|
316
|
+
dy,yerr = _rkck(y,dydt,t,f,dt,args)
|
|
317
|
+
errmax = np.max(yerr * inv_epsabs)
|
|
318
|
+
if(errmax < 1.0):
|
|
319
|
+
break # Step succeeded
|
|
320
|
+
dttemp = 0.9*dt*errmax**-.25
|
|
321
|
+
dt = max(dttemp,dt*.1) if dt > 0 else min(dttemp,dt*.1)
|
|
322
|
+
if t+dt == t:
|
|
323
|
+
raise IntegrationError("Stepsize rounds down to zero.")
|
|
324
|
+
if errmax > 1.89e-4:
|
|
325
|
+
dtnext = 0.9 * dt * errmax**-.2
|
|
326
|
+
else:
|
|
327
|
+
dtnext = 5*dt
|
|
328
|
+
return _rkqs_rval(dy, dt, dtnext)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _rkck(y,dydt,t,f,dt,args=()):
|
|
332
|
+
"""
|
|
333
|
+
Take one 5th-order Cash-Karp Runge-Kutta step.
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
array_like
|
|
338
|
+
The change in `y` during this step.
|
|
339
|
+
array_like
|
|
340
|
+
An error estimate for `y`.
|
|
341
|
+
"""
|
|
342
|
+
a2=0.2;a3=0.3;a4=0.6;a5=1.0;a6=0.875;b21=0.2 # noqa
|
|
343
|
+
b31=3.0/40.0;b32=9.0/40.0;b41=0.3;b42 = -0.9;b43=1.2; # noqa
|
|
344
|
+
b51 = -11.0/54.0; b52=2.5;b53 = -70.0/27.0;b54=35.0/27.0; # noqa
|
|
345
|
+
b61=1631.0/55296.0;b62=175.0/512.0;b63=575.0/13824.0 # noqa
|
|
346
|
+
b64=44275.0/110592.0;b65=253.0/4096.0;c1=37.0/378.0; # noqa
|
|
347
|
+
c3=250.0/621.0;c4=125.0/594.0;c6=512.0/1771.0; # noqa
|
|
348
|
+
dc5 = -277.00/14336.0; # noqa
|
|
349
|
+
dc1=c1-2825.0/27648.0;dc3=c3-18575.0/48384.0; # noqa
|
|
350
|
+
dc4=c4-13525.0/55296.0;dc6=c6-0.25 # noqa
|
|
351
|
+
|
|
352
|
+
with np.errstate(invalid='ignore'):
|
|
353
|
+
ytemp = y+b21*dt*dydt
|
|
354
|
+
|
|
355
|
+
ak2 = f(ytemp, t+a2*dt, *args)
|
|
356
|
+
ytemp = y+dt*(b31*dydt+b32*ak2)
|
|
357
|
+
ak3 = f(ytemp, t+a3*dt, *args)
|
|
358
|
+
ytemp = y+dt*(b41*dydt+b42*ak2+b43*ak3)
|
|
359
|
+
ak4 = f(ytemp, t+a4*dt, *args)
|
|
360
|
+
ytemp = y + dt*(b51*dydt+b52*ak2+b53*ak3+b54*ak4)
|
|
361
|
+
ak5 = f(ytemp, t+a5*dt, *args)
|
|
362
|
+
ytemp = y + dt*(b61*dydt+b62*ak2+b63*ak3+b64*ak4+b65*ak5)
|
|
363
|
+
ak6 = f(ytemp, t+a6*dt, *args)
|
|
364
|
+
dyout = dt*(c1*dydt+c3*ak3+c4*ak4+c6*ak6)
|
|
365
|
+
yerr = dt*(dc1*dydt+dc3*ak3+dc4*ak4+dc5*ak5+dc6*ak6)
|
|
366
|
+
return dyout, yerr
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
"""
|
|
370
|
+
Numerical derivatives
|
|
371
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
372
|
+
|
|
373
|
+
The *derivij()* functions accept arrays as input and return arrays as output.
|
|
374
|
+
In contrast, :class:`gradientFunction` and :class:hessianFunction` accept
|
|
375
|
+
functions as input and return callable class instances (essentially functions)
|
|
376
|
+
as output. The returned functions can then be used to find derivatives.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def deriv14(y,x):
|
|
381
|
+
R"""
|
|
382
|
+
Calculates :math:`dy/dx` to fourth-order in :math:`\Delta x` using
|
|
383
|
+
finite differences. The derivative is taken along the last dimension of `y`.
|
|
384
|
+
|
|
385
|
+
Both `y` and `x` should be numpy arrays. The derivatives are centered
|
|
386
|
+
in the interior of the array, but not at the edges. The spacing in `x`
|
|
387
|
+
does not need to be uniform.
|
|
388
|
+
"""
|
|
389
|
+
n = len(x)
|
|
390
|
+
j = np.arange(5)
|
|
391
|
+
j[j > 4//2] -= 5
|
|
392
|
+
i = np.arange(n) - j[:,np.newaxis]
|
|
393
|
+
i[i < 0] += 5
|
|
394
|
+
i[i >= n] -= 5
|
|
395
|
+
|
|
396
|
+
d1 = x[i[1]]-x[i[0]]
|
|
397
|
+
d2 = x[i[2]]-x[i[0]]
|
|
398
|
+
d3 = x[i[3]]-x[i[0]]
|
|
399
|
+
d4 = x[i[4]]-x[i[0]]
|
|
400
|
+
|
|
401
|
+
w4 = (d1*d2*d3) / (
|
|
402
|
+
-d4 * (-d1*d2*d3 + d4 * (d1*d2+d2*d3+d3*d1 + d4 * (+d4-d1-d2-d3))))
|
|
403
|
+
w3 = (d1*d2*d4) / (
|
|
404
|
+
-d3 * (-d1*d2*d4 + d3 * (d1*d2+d2*d4+d4*d1 + d3 * (-d4-d1-d2+d3))))
|
|
405
|
+
w2 = (d1*d4*d3) / (
|
|
406
|
+
-d2 * (-d1*d4*d3 + d2 * (d1*d4+d4*d3+d3*d1 + d2 * (-d4-d1+d2-d3))))
|
|
407
|
+
w1 = (d4*d2*d3) / (
|
|
408
|
+
-d1 * (-d4*d2*d3 + d1 * (d4*d2+d2*d3+d3*d4 + d1 * (-d4+d1-d2-d3))))
|
|
409
|
+
w0 = -(w1+w2+w3+w4)
|
|
410
|
+
|
|
411
|
+
dy = (w0*y[...,i[0]] + w1*y[...,i[1]] +
|
|
412
|
+
w2*y[...,i[2]] + w3*y[...,i[3]] + w4*y[...,i[4]])
|
|
413
|
+
|
|
414
|
+
return dy
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def deriv14_const_dx(y, dx=1.0):
|
|
418
|
+
R"""
|
|
419
|
+
Calculates :math:`dy/dx` to fourth-order in :math:`\Delta x` using
|
|
420
|
+
finite differences. The derivative is taken along the last dimension of `y`.
|
|
421
|
+
|
|
422
|
+
The output of this function should be identical to :func:`deriv14` when the
|
|
423
|
+
spacing in `x` is constant, but this will be faster.
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
y : array_like
|
|
428
|
+
dx : float, optional
|
|
429
|
+
"""
|
|
430
|
+
y = y.T # now the derivative is along the first dimension
|
|
431
|
+
dy = np.empty_like(y)
|
|
432
|
+
|
|
433
|
+
dy[2:-2] = y[:-4] - 8*y[1:-3] + 8*y[3:-1] - y[4:]
|
|
434
|
+
dy[+0] = -25*y[+0] + 48*y[+1] - 36*y[+2] + 16*y[+3] - 3*y[+4]
|
|
435
|
+
dy[+1] = -3*y[+0] - 10*y[+1] + 18*y[+2] - 6*y[+3] + y[+4]
|
|
436
|
+
dy[-2] = +3*y[-1] + 10*y[-2] - 18*y[-3] + 6*y[-4] - y[-5]
|
|
437
|
+
dy[-1] = +25*y[-1] - 48*y[-2] + 36*y[-3] - 16*y[-4] + 3*y[-5]
|
|
438
|
+
|
|
439
|
+
return dy.T / (12.0 * dx)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def deriv1n(y,x,n):
|
|
443
|
+
r"""
|
|
444
|
+
Calculates :math:`dy/dx` to nth-order in :math:`\Delta x` using
|
|
445
|
+
finite differences. The derivative is taken along the last dimension of `y`.
|
|
446
|
+
|
|
447
|
+
Both `y` and `x` should be numpy arrays. The derivatives are centered in the
|
|
448
|
+
interior of the array, but not at the edges. The spacing in `x` does not
|
|
449
|
+
need to be uniform.
|
|
450
|
+
"""
|
|
451
|
+
nx = len(x)
|
|
452
|
+
j = np.arange(n+1)
|
|
453
|
+
j[j > n//2] -= n+1
|
|
454
|
+
i = np.arange(nx) - j[:,np.newaxis]
|
|
455
|
+
i[i < 0] += n+1
|
|
456
|
+
i[i >= nx] -= n+1
|
|
457
|
+
|
|
458
|
+
d = np.empty((n,n,nx), dtype=x.dtype)*1.0
|
|
459
|
+
d[0] = x[i[1:]] - x[i[0]]
|
|
460
|
+
for j in range(1,n):
|
|
461
|
+
d[j] = np.roll(d[j-1], -1, axis=0)
|
|
462
|
+
d[:,0] *= -1
|
|
463
|
+
w = np.zeros((n+1,nx), dtype=y.dtype)*1.
|
|
464
|
+
|
|
465
|
+
# For example, when calculating w[1], we need only use
|
|
466
|
+
# w[1]: d1 = d[0,0], d2 = d[0,1], d3 = d[0,2], ..., dn = d[0,n-1]
|
|
467
|
+
# and for the other weights we just increment the first index:
|
|
468
|
+
# w[2]: d2 = d[1,0], d3 = d[1,1], d4 = d[1,2], ..., dn = d[1,n-2],
|
|
469
|
+
# d1 = d[1,n-1]
|
|
470
|
+
# So we should be able to calculate all of them at once like this.
|
|
471
|
+
s = ((2**np.arange(n-1)) & np.arange(2**(n-1))[:,np.newaxis])
|
|
472
|
+
s[s > 0] = (np.arange(1,n) * np.ones(2**(n-1))[:,np.newaxis])[s > 0]
|
|
473
|
+
w[1:] = (np.sum(np.prod(d[:,s],axis=2), axis=1)*d[:,0]
|
|
474
|
+
/ np.prod(d[:,1:], axis=1))
|
|
475
|
+
w[1:] = -w[1:]**-1
|
|
476
|
+
w[0] = -np.sum(w[1:],axis=0)
|
|
477
|
+
|
|
478
|
+
dy = np.sum(w*y[...,i], axis=-2)
|
|
479
|
+
|
|
480
|
+
return dy
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def deriv23(y,x):
|
|
484
|
+
r"""
|
|
485
|
+
Calculates :math:`d^2y/dx^2` to third-order in :math:`\Delta x` using
|
|
486
|
+
finite differences. The derivative is taken along the last dimension of `y`.
|
|
487
|
+
|
|
488
|
+
Both `y` and `x` should be numpy arrays. The derivatives are centered in the
|
|
489
|
+
interior of the array, but not at the edges. The spacing in `x` does not
|
|
490
|
+
need to be uniform. The accuracy increases to fourth-order if the spacing
|
|
491
|
+
is uniform.
|
|
492
|
+
"""
|
|
493
|
+
n = len(x)
|
|
494
|
+
j = np.arange(5)
|
|
495
|
+
j[j > 4//2] -= 5
|
|
496
|
+
i = np.arange(n) - j[:,np.newaxis]
|
|
497
|
+
i[i < 0] += 5
|
|
498
|
+
i[i >= n] -= 5
|
|
499
|
+
|
|
500
|
+
d1 = x[i[1]]-x[i[0]]
|
|
501
|
+
d2 = x[i[2]]-x[i[0]]
|
|
502
|
+
d3 = x[i[3]]-x[i[0]]
|
|
503
|
+
d4 = x[i[4]]-x[i[0]]
|
|
504
|
+
|
|
505
|
+
w4 = 2*(d1*d2+d2*d3+d3*d1) / (
|
|
506
|
+
d4 * (-d1*d2*d3 + d4 * (d1*d2+d2*d3+d3*d1 + d4 * (+d4-d1-d2-d3) ) ) )
|
|
507
|
+
w3 = 2*(d1*d2+d2*d4+d4*d1) / (
|
|
508
|
+
d3 * (-d1*d2*d4 + d3 * (d1*d2+d2*d4+d4*d1 + d3 * (-d4-d1-d2+d3) ) ) )
|
|
509
|
+
w2 = 2*(d1*d4+d4*d3+d3*d1) / (
|
|
510
|
+
d2 * (-d1*d4*d3 + d2 * (d1*d4+d4*d3+d3*d1 + d2 * (-d4-d1+d2-d3) ) ) )
|
|
511
|
+
w1 = 2*(d4*d2+d2*d3+d3*d4) / (
|
|
512
|
+
d1 * (-d4*d2*d3 + d1 * (d4*d2+d2*d3+d3*d4 + d1 * (-d4+d1-d2-d3) ) ) )
|
|
513
|
+
w0 = -(w1+w2+w3+w4)
|
|
514
|
+
|
|
515
|
+
d2y = (w0*y[...,i[0]] + w1*y[...,i[1]] +
|
|
516
|
+
w2*y[...,i[2]] + w3*y[...,i[3]] + w4*y[...,i[4]])
|
|
517
|
+
return d2y
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def deriv23_const_dx(y, dx=1.0):
|
|
521
|
+
r"""
|
|
522
|
+
Calculates :math:`d^2y/dx^2` to third-order in :math:`\Delta x` using
|
|
523
|
+
finite differences. The derivative is taken along the last dimension of `y`.
|
|
524
|
+
|
|
525
|
+
The output of this function should be identical to :func:`deriv23` when the
|
|
526
|
+
spacing in `x` is constant, but this will be faster.
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
y : array_like
|
|
531
|
+
dx : float, optional
|
|
532
|
+
"""
|
|
533
|
+
y = y.T # now the derivative is along the first dimension
|
|
534
|
+
dy = np.empty_like(y)
|
|
535
|
+
|
|
536
|
+
dy[2:-2] = -y[:-4] + 16*y[1:-3] - 30*y[2:-2] + 16*y[3:-1] - y[4:]
|
|
537
|
+
dy[+0] = 35*y[+0] - 104*y[+1] + 114*y[+2] - 56*y[+3] + 11*y[+4]
|
|
538
|
+
dy[+1] = 11*y[+0] - 20*y[+1] + 6*y[+2] + 4*y[+3] - y[+4]
|
|
539
|
+
dy[-2] = 11*y[-1] - 20*y[-2] + 6*y[-3] + 4*y[-4] - y[-5]
|
|
540
|
+
dy[-1] = 35*y[-1] - 104*y[-2] + 114*y[-3] - 56*y[-4] + 11*y[-5]
|
|
541
|
+
|
|
542
|
+
return dy.T / (12.0 * dx)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class gradientFunction(object):
|
|
546
|
+
"""
|
|
547
|
+
Make a function which returns the gradient of some scalar function.
|
|
548
|
+
|
|
549
|
+
parameters
|
|
550
|
+
----------
|
|
551
|
+
f : callable
|
|
552
|
+
The first argument `x` should either be a single point with length
|
|
553
|
+
`Ndim` or an array (or matrix, etc.) of points with shape
|
|
554
|
+
``(..., Ndim)``, where ``...`` is some arbitrary shape. The return
|
|
555
|
+
shape should be the same as the input shape, but with the last axis
|
|
556
|
+
stripped off (i.e., it should be a scalar function). Additional
|
|
557
|
+
required or optional arguments are allowed.
|
|
558
|
+
eps : float or array_like
|
|
559
|
+
The small change in `x` used to calculate the finite differences.
|
|
560
|
+
Can either be a scalar or have length `Ndim`.
|
|
561
|
+
Ndim : int
|
|
562
|
+
Number of dimensions for each point.
|
|
563
|
+
order : 2 or 4
|
|
564
|
+
Calculate the derivatives to either 2nd or 4th order in `eps`.
|
|
565
|
+
|
|
566
|
+
Example
|
|
567
|
+
-------
|
|
568
|
+
>>> def f(X):
|
|
569
|
+
... x,y = np.asarray(X).T
|
|
570
|
+
... return (x*x + x*y +3.*y*y*y).T
|
|
571
|
+
>>> df = gradientFunction(f, eps=.01, Ndim=2, order=4)
|
|
572
|
+
>>> x = np.array([[0,0],[0,1],[1,0],[1,1]])
|
|
573
|
+
>>> print df(x)
|
|
574
|
+
array([[ 0., 0.], [ 1., 9.], [ 2., 1.], [ 3., 10.]])
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
def __init__(self, f, eps, Ndim, order=4):
|
|
578
|
+
"""Store the finite-difference stencil required to evaluate gradients."""
|
|
579
|
+
assert order == 2 or order == 4
|
|
580
|
+
eps = np.asanyarray(eps)
|
|
581
|
+
dx = np.empty((order, Ndim, Ndim))
|
|
582
|
+
dx[:] = np.diag(np.ones(Ndim)*eps)
|
|
583
|
+
dxT = dx.T
|
|
584
|
+
coef = np.empty((order, Ndim))
|
|
585
|
+
coef[:] = 1.0/eps
|
|
586
|
+
coefT = coef.T
|
|
587
|
+
if order == 2:
|
|
588
|
+
dxT *= [-1, 1]
|
|
589
|
+
coefT *= [-.5, .5]
|
|
590
|
+
if order == 4:
|
|
591
|
+
dxT *= [-2, -1, 1, 2]
|
|
592
|
+
coefT *= [1, -8, 8, -1]
|
|
593
|
+
coefT /= 12.0
|
|
594
|
+
self.f = f
|
|
595
|
+
self.dx = dx
|
|
596
|
+
self.coef = coef
|
|
597
|
+
|
|
598
|
+
def __call__(self, x, *args, **kwargs):
|
|
599
|
+
"""
|
|
600
|
+
Calculate the gradient. Output shape is the same as the input shape.
|
|
601
|
+
"""
|
|
602
|
+
x = np.asanyarray(x)[...,np.newaxis,np.newaxis,:]
|
|
603
|
+
return np.sum(self.f(x+self.dx, *args, **kwargs)*self.coef, axis=-2)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class hessianFunction(object):
|
|
607
|
+
"""
|
|
608
|
+
Make a function which returns the Hessian (second derivative) matrix of
|
|
609
|
+
some scalar function.
|
|
610
|
+
|
|
611
|
+
Parameters
|
|
612
|
+
----------
|
|
613
|
+
f : callable
|
|
614
|
+
The first argument `x` should either be a single point with length
|
|
615
|
+
`Ndim` or an array (or matrix, etc.) of points with shape
|
|
616
|
+
``(..., Ndim)``, where ``...`` is some arbitrary shape. The return
|
|
617
|
+
shape should be the same as the input shape, but with the last axis
|
|
618
|
+
stripped off (i.e., it should be a scalar function). Additional
|
|
619
|
+
required or optional arguments are allowed.
|
|
620
|
+
eps : float or array_like
|
|
621
|
+
The small change in `x` used to calculate the finite differences.
|
|
622
|
+
Can either be a scalar or have length `Ndim`.
|
|
623
|
+
Ndim : int
|
|
624
|
+
Number of dimensions for each point.
|
|
625
|
+
order : 2 or 4
|
|
626
|
+
Calculate the derivatives to either 2nd or 4th order in `eps`.
|
|
627
|
+
"""
|
|
628
|
+
def __init__(self, f : callable, eps : float, Ndim : int, order : int=4):
|
|
629
|
+
"""Pre-compute second-order finite-difference coefficients and offsets.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
f : callable
|
|
634
|
+
The function for which to compute the Hessian.
|
|
635
|
+
eps : float
|
|
636
|
+
The small change in `x` used to calculate the finite differences.
|
|
637
|
+
Ndim : int
|
|
638
|
+
Number of dimensions for each point.
|
|
639
|
+
order : int
|
|
640
|
+
Calculate the derivatives to either 2nd or 4th order in `eps`.
|
|
641
|
+
|
|
642
|
+
Raises
|
|
643
|
+
------
|
|
644
|
+
AssertionError
|
|
645
|
+
If `order` is not 2 or 4.
|
|
646
|
+
"""
|
|
647
|
+
assert order == 2 or order == 4
|
|
648
|
+
eps = np.ones(Ndim) * eps
|
|
649
|
+
dx = []
|
|
650
|
+
coef = []
|
|
651
|
+
for i in range(Ndim):
|
|
652
|
+
dx.append([])
|
|
653
|
+
coef.append([])
|
|
654
|
+
for j in range(i):
|
|
655
|
+
dx_ = np.zeros((order, order, Ndim))
|
|
656
|
+
if (order == 2):
|
|
657
|
+
dx_[:,:,i] = np.array([-1,1]) * eps[i]
|
|
658
|
+
dx_ = np.rollaxis(dx_, 1)
|
|
659
|
+
dx_[:,:,j] = np.array([-1,1]) * eps[j]
|
|
660
|
+
coef_ = np.array([-.5, .5])
|
|
661
|
+
coef_ = coef_[:,np.newaxis] * coef_[np.newaxis,:]
|
|
662
|
+
coef_ /= eps[i]*eps[j]
|
|
663
|
+
if (order == 4):
|
|
664
|
+
dx_[:,:,i] = np.array([-2,-1,1,2]) * eps[i]
|
|
665
|
+
dx_ = np.rollaxis(dx_, 1)
|
|
666
|
+
dx_[:,:,j] = np.array([-2,-1,1,2]) * eps[j]
|
|
667
|
+
coef_ = np.array([1, -8, 8, -1.])
|
|
668
|
+
coef_ = coef_[:,np.newaxis] * coef_[np.newaxis,:]
|
|
669
|
+
coef_ /= 144.*eps[i]*eps[j]
|
|
670
|
+
dx[-1].append(dx_.reshape(order*order, Ndim))
|
|
671
|
+
coef[-1].append(coef_.reshape(order*order))
|
|
672
|
+
dx_ = np.zeros((order+1, Ndim))
|
|
673
|
+
if order == 2:
|
|
674
|
+
dx_[:,i] = np.array([-1,0,1]) * eps[i]
|
|
675
|
+
coef_ = np.array([1,-2,1.]) / (eps[i]*eps[i])
|
|
676
|
+
if order == 4:
|
|
677
|
+
dx_[:,i] = np.array([-2,-1,0,1,2]) * eps[i]
|
|
678
|
+
coef_ = np.array([-1,16,-30,16,-1]) / (eps[i]*eps[i]*12)
|
|
679
|
+
dx[-1].append(dx_)
|
|
680
|
+
coef[-1].append(coef_)
|
|
681
|
+
self.f = f
|
|
682
|
+
self.coef = coef
|
|
683
|
+
self.dx = dx
|
|
684
|
+
self.Ndim = Ndim
|
|
685
|
+
|
|
686
|
+
def __call__(self, x, *args, **kwargs):
|
|
687
|
+
"""
|
|
688
|
+
Calculate the gradient. Output shape is ``input.shape + (Ndim,)``.
|
|
689
|
+
|
|
690
|
+
Returns
|
|
691
|
+
-------
|
|
692
|
+
array_like
|
|
693
|
+
The Hessian matrix evaluated at each input point.
|
|
694
|
+
"""
|
|
695
|
+
Ndim = self.Ndim
|
|
696
|
+
f = self.f
|
|
697
|
+
coef = self.coef
|
|
698
|
+
dx = self.dx
|
|
699
|
+
x = np.asanyarray(x)
|
|
700
|
+
y = np.empty(x.shape + (Ndim,))
|
|
701
|
+
x = x[...,np.newaxis,:]
|
|
702
|
+
for i in range(Ndim):
|
|
703
|
+
for j in range(i):
|
|
704
|
+
y_ = np.sum(f(x+dx[i][j], *args, **kwargs)
|
|
705
|
+
* coef[i][j], axis=-1)
|
|
706
|
+
y[...,i,j] = y[...,j,i] = y_
|
|
707
|
+
y[...,i,i] = np.sum(f(x+dx[i][i], *args, **kwargs)
|
|
708
|
+
* coef[i][i], axis=-1)
|
|
709
|
+
return y
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
"""
|
|
713
|
+
Two-point interpolation
|
|
714
|
+
~~~~~~~~~~~~~~~~~~~~~~~
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def makeInterpFuncs(y0, dy0, d2y0, y1, dy1, d2y1):
|
|
719
|
+
"""
|
|
720
|
+
Create interpolating functions between two points with a quintic polynomial.
|
|
721
|
+
|
|
722
|
+
If we're given the first and second derivatives of a function
|
|
723
|
+
at x=0 and x=1, we can make a 5th-order interpolation between
|
|
724
|
+
the two.
|
|
725
|
+
"""
|
|
726
|
+
a0,a1,a2, z,dz,d2z = y0, dy0, 0.5*d2y0, y1, dy1, d2y1
|
|
727
|
+
b1,b2,b3 = z-a0-a1-a2, dz-a1-2*a2, d2z-2*a2
|
|
728
|
+
a5 = .5*b3 - 3*b2 + 6*b1
|
|
729
|
+
a4 = b2 - 3*b1 - 2*a5
|
|
730
|
+
a3 = b1 - a4 - a5
|
|
731
|
+
coefs = np.array([a0,a1,a2,a3,a4,a5])
|
|
732
|
+
pows = np.arange(6)
|
|
733
|
+
df = lambda x, c=coefs[1:], p=pows[1:]: np.sum(p*c*x**(p-1))
|
|
734
|
+
f = lambda x, c=coefs, p=pows: np.sum(c*x**p)
|
|
735
|
+
return f, df
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class cubicInterpFunction(object):
|
|
739
|
+
"""
|
|
740
|
+
Create an interpolating function between two points with a cubic polynomial.
|
|
741
|
+
|
|
742
|
+
Like :func:`makeInterpFuncs`, but only uses the first derivatives.
|
|
743
|
+
"""
|
|
744
|
+
def __init__(self, y0, dy0, y1, dy1):
|
|
745
|
+
"""Store the cubic Bézier control points defined by the boundary data."""
|
|
746
|
+
# Easiest to treat this as a bezier curve
|
|
747
|
+
y3 = y1
|
|
748
|
+
y1 = y0 + dy0/3.0
|
|
749
|
+
y2 = y3 - dy1/3.0
|
|
750
|
+
self.Y = y0, y1, y2, y3
|
|
751
|
+
|
|
752
|
+
def __call__(self, t):
|
|
753
|
+
"""Evaluate the cubic interpolant at the normalised coordinate ``t``."""
|
|
754
|
+
mt = 1-t
|
|
755
|
+
y0, y1, y2, y3 = self.Y
|
|
756
|
+
return y0*mt**3 + 3*y1*mt*mt*t + 3*y2*mt*t*t + y3*t**3
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
"""
|
|
760
|
+
Spline interpolation
|
|
761
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def Nbspl(t, x, k=3):
|
|
766
|
+
"""
|
|
767
|
+
Calculate the B-spline basis functions for the knots t evaluated at the
|
|
768
|
+
points x.
|
|
769
|
+
|
|
770
|
+
Parameters
|
|
771
|
+
----------
|
|
772
|
+
t : array_like
|
|
773
|
+
An array of knots which define the basis functions.
|
|
774
|
+
x : array_like
|
|
775
|
+
The different values at which to calculate the functions.
|
|
776
|
+
k : int, optional
|
|
777
|
+
The order of the spline. Must satisfy ``k <= len(t)-2``.
|
|
778
|
+
|
|
779
|
+
Returns
|
|
780
|
+
-------
|
|
781
|
+
array_like
|
|
782
|
+
Has shape ``(len(x), len(t)-k-1)``.
|
|
783
|
+
|
|
784
|
+
Notes
|
|
785
|
+
-----
|
|
786
|
+
This is fairly speedy, although it does spend a fair amount of time
|
|
787
|
+
calculating things that will end up being zero. On a 2.5Ghz machine, it
|
|
788
|
+
takes a few milliseconds to calculate when ``len(x) == 500; len(t) == 20;
|
|
789
|
+
k == 3``.
|
|
790
|
+
"""
|
|
791
|
+
kmax = k
|
|
792
|
+
if kmax > len(t)-2:
|
|
793
|
+
raise Exception("Input error in Nbspl: require that k < len(t)-2")
|
|
794
|
+
t = np.array(t)
|
|
795
|
+
x = np.array(x)[:, np.newaxis]
|
|
796
|
+
N = 1.0*((x > t[:-1]) & (x <= t[1:]))
|
|
797
|
+
for k in range(1, kmax+1):
|
|
798
|
+
dt = t[k:] - t[:-k]
|
|
799
|
+
_dt = dt.copy()
|
|
800
|
+
_dt[dt != 0] = 1./dt[dt != 0]
|
|
801
|
+
N = N[:,:-1]*(x-t[:-k-1])*_dt[:-1] - N[:,1:]*(x-t[k+1:])*_dt[1:]
|
|
802
|
+
return N
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def Nbspld1(t, x, k=3):
|
|
806
|
+
"""Same as :func:`Nbspl`, but returns the first derivative too."""
|
|
807
|
+
kmax = k
|
|
808
|
+
if kmax > len(t)-2:
|
|
809
|
+
raise Exception("Input error in Nbspl: require that k < len(t)-2")
|
|
810
|
+
t = np.array(t)
|
|
811
|
+
x = np.array(x)[:, np.newaxis]
|
|
812
|
+
N = 1.0*((x > t[:-1]) & (x <= t[1:]))
|
|
813
|
+
dN = np.zeros_like(N)
|
|
814
|
+
for k in range(1, kmax+1):
|
|
815
|
+
dt = t[k:] - t[:-k]
|
|
816
|
+
_dt = dt.copy()
|
|
817
|
+
_dt[dt != 0] = 1./dt[dt != 0]
|
|
818
|
+
dN = dN[:,:-1]*(x-t[:-k-1])*_dt[:-1] - dN[:,1:]*(x-t[k+1:])*_dt[1:]
|
|
819
|
+
dN += N[:,:-1]*_dt[:-1] - N[:,1:]*_dt[1:]
|
|
820
|
+
N = N[:,:-1]*(x-t[:-k-1])*_dt[:-1] - N[:,1:]*(x-t[k+1:])*_dt[1:]
|
|
821
|
+
return N, dN
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def Nbspld2(t, x, k=3):
|
|
825
|
+
"""Same as :func:`Nbspl`, but returns first and second derivatives too."""
|
|
826
|
+
kmax = k
|
|
827
|
+
if kmax > len(t)-2:
|
|
828
|
+
raise Exception("Input error in Nbspl: require that k < len(t)-2")
|
|
829
|
+
t = np.array(t)
|
|
830
|
+
x = np.array(x)[:, np.newaxis]
|
|
831
|
+
N = 1.0*((x > t[:-1]) & (x <= t[1:]))
|
|
832
|
+
dN = np.zeros_like(N)
|
|
833
|
+
d2N = np.zeros_like(N)
|
|
834
|
+
for k in range(1, kmax+1):
|
|
835
|
+
dt = t[k:] - t[:-k]
|
|
836
|
+
_dt = dt.copy()
|
|
837
|
+
_dt[dt != 0] = 1./dt[dt != 0]
|
|
838
|
+
d2N = d2N[:,:-1]*(x-t[:-k-1])*_dt[:-1] - d2N[:,1:]*(x-t[k+1:])*_dt[1:] \
|
|
839
|
+
+ 2*dN[:,:-1]*_dt[:-1] - 2*dN[:,1:]*_dt[1:]
|
|
840
|
+
dN = dN[:,:-1]*(x-t[:-k-1])*_dt[:-1] - dN[:,1:]*(x-t[k+1:])*_dt[1:] \
|
|
841
|
+
+ N[:,:-1]*_dt[:-1] - N[:,1:]*_dt[1:]
|
|
842
|
+
N = N[:,:-1]*(x-t[:-k-1])*_dt[:-1] - N[:,1:]*(x-t[k+1:])*_dt[1:]
|
|
843
|
+
return N, dN, d2N
|