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.
Files changed (95) hide show
  1. transitionlistener/__init__.py +21 -0
  2. transitionlistener/bubbledynamics.py +2250 -0
  3. transitionlistener/bubbledynamics_fixedstep.py +2929 -0
  4. transitionlistener/check_installation.py +309 -0
  5. transitionlistener/ckm.py +91 -0
  6. transitionlistener/colors.py +288 -0
  7. transitionlistener/config.py +185 -0
  8. transitionlistener/constants.py +64 -0
  9. transitionlistener/counterterms/__init__.py +230 -0
  10. transitionlistener/counterterms/cw.py +298 -0
  11. transitionlistener/counterterms/tensors.py +343 -0
  12. transitionlistener/counterterms/twohdm.py +520 -0
  13. transitionlistener/counterterms/twohdm_solver.py +374 -0
  14. transitionlistener/errors.py +315 -0
  15. transitionlistener/examples/bp_et.yaml +64 -0
  16. transitionlistener/examples/bp_lisa.yaml +70 -0
  17. transitionlistener/examples/bp_muares.yaml +69 -0
  18. transitionlistener/examples/bp_pta.yaml +64 -0
  19. transitionlistener/examples/example_dark_photon.yaml +73 -0
  20. transitionlistener/examples/example_grid.yaml +55 -0
  21. transitionlistener/examples/example_line.yaml +51 -0
  22. transitionlistener/examples/example_point.yaml +73 -0
  23. transitionlistener/examples/example_random_conformal.yaml +42 -0
  24. transitionlistener/examples/example_ultranest.yaml +39 -0
  25. transitionlistener/finiteT.py +468 -0
  26. transitionlistener/generic_potential.py +1521 -0
  27. transitionlistener/gridplots.py +592 -0
  28. transitionlistener/gwfopt.py +604 -0
  29. transitionlistener/helper_functions.py +843 -0
  30. transitionlistener/hydrodynamics.py +647 -0
  31. transitionlistener/interface/__init__.py +95 -0
  32. transitionlistener/interface/check.py +51 -0
  33. transitionlistener/interface/cli.py +211 -0
  34. transitionlistener/interface/config_loader.py +264 -0
  35. transitionlistener/interface/logging_utils.py +30 -0
  36. transitionlistener/interface/output_schema.py +296 -0
  37. transitionlistener/interface/pipeline.py +300 -0
  38. transitionlistener/interface/samplers.py +565 -0
  39. transitionlistener/interface/scans.py +468 -0
  40. transitionlistener/interface/single_point.py +581 -0
  41. transitionlistener/interface/state.py +61 -0
  42. transitionlistener/lineplots.py +597 -0
  43. transitionlistener/logo/TL-logo_large.png +0 -0
  44. transitionlistener/logo/TL-logo_small.png +0 -0
  45. transitionlistener/models/TL_2HDM.py +512 -0
  46. transitionlistener/models/TL_conformal_dark_u1.py +202 -0
  47. transitionlistener/models/TL_dark_U1.py +198 -0
  48. transitionlistener/models/TL_dark_U1_g_parameterization.py +198 -0
  49. transitionlistener/models/TL_dark_flipflop.py +274 -0
  50. transitionlistener/models/templatePotential.py +260 -0
  51. transitionlistener/nucleation.py +412 -0
  52. transitionlistener/observability.py +790 -0
  53. transitionlistener/particles.py +436 -0
  54. transitionlistener/pathDeformation.py +1462 -0
  55. transitionlistener/percolation_adaptive_gridbuilders.py +1385 -0
  56. transitionlistener/percolation_adaptive_rate.py +1049 -0
  57. transitionlistener/percolation_adaptivestepsize.py +1632 -0
  58. transitionlistener/percolation_fixedstepsize.py +618 -0
  59. transitionlistener/phases.py +1499 -0
  60. transitionlistener/plot_settings.py +121 -0
  61. transitionlistener/plots.py +1781 -0
  62. transitionlistener/runtime_options.py +278 -0
  63. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/bandwidths.npy +0 -0
  64. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/density.npy +0 -0
  65. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/freqs.npy +0 -0
  66. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log.txt +16 -0
  67. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rhogrid.npy +0 -0
  68. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/log10rholabels.txt +30 -0
  69. transitionlistener/tab_data/30f_fs{hd}_100fDMGP_ceffyl/pulsar_list.txt +1 -0
  70. transitionlistener/tab_data/HLV-O2-PLI.csv +82 -0
  71. transitionlistener/tab_data/HLV-O2-noise.csv +83 -0
  72. transitionlistener/tab_data/HLVK-design-PLI.csv +61 -0
  73. transitionlistener/tab_data/HLVK-design-noise.csv +83 -0
  74. transitionlistener/tab_data/NG15_PLI_hc.csv +31 -0
  75. transitionlistener/tab_data/NG15_noise_hc.csv +56 -0
  76. transitionlistener/tab_data/PLI-dataset.csv +327 -0
  77. transitionlistener/tab_data/energydensities.dat +1001 -0
  78. transitionlistener/tab_data/finiteT_b.dat.txt +1999 -0
  79. transitionlistener/tab_data/finiteT_f.dat.txt +1000 -0
  80. transitionlistener/tab_data/geff_SM.dat +10009 -0
  81. transitionlistener/tab_data/muAres_f_Hz_PLI_h2OmegaGW.csv +131 -0
  82. transitionlistener/tab_data/muAres_f_Hz_h2Omega_noise.csv +99 -0
  83. transitionlistener/tab_data/noise-dataset.csv +327 -0
  84. transitionlistener/tab_data/numberdensities.dat +1001 -0
  85. transitionlistener/tab_data/pressures.dat +1001 -0
  86. transitionlistener/thermodynamics.py +686 -0
  87. transitionlistener/transitionObservables.py +1199 -0
  88. transitionlistener/transitionObservables_fixedstep.py +976 -0
  89. transitionlistener/transitions.py +1000 -0
  90. transitionlistener/tunneling1D.py +910 -0
  91. transitionlistener-2.0.0.dist-info/METADATA +817 -0
  92. transitionlistener-2.0.0.dist-info/RECORD +95 -0
  93. transitionlistener-2.0.0.dist-info/WHEEL +4 -0
  94. transitionlistener-2.0.0.dist-info/entry_points.txt +2 -0
  95. 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