freealg 0.0.2__py3-none-any.whl → 0.1.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.
freealg/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.2"
1
+ __version__ = "0.1.0"
freealg/_decompress.py ADDED
@@ -0,0 +1,136 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # SPDX-FileType: SOURCE
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it under
5
+ # the terms of the license found in the LICENSE.txt file in the root directory
6
+ # of this source tree.
7
+
8
+
9
+ # =======
10
+ # Imports
11
+ # =======
12
+
13
+ import numpy
14
+
15
+ __all__ = ['decompress']
16
+
17
+
18
+ # ==========
19
+ # decompress
20
+ # ==========
21
+
22
+ def decompress(matrix, size, x=None, delta=1e-4, iterations=500, step_size=0.1,
23
+ tolerance=1e-4):
24
+ """
25
+ Free decompression of spectral density.
26
+
27
+ Parameters
28
+ ----------
29
+
30
+ matrix : FreeForm
31
+ The initial matrix to be decompressed
32
+
33
+ size : int
34
+ Size of the decompressed matrix.
35
+
36
+ x : numpy.array, default=None
37
+ Positions where density to be evaluated at. If `None`, an interval
38
+ slightly larger than the support interval will be used.
39
+
40
+ delta: float, default=1e-4
41
+ Size of the perturbation into the upper half plane for Plemelj's
42
+ formula.
43
+
44
+ iterations: int, default=500
45
+ Maximum number of Newton iterations.
46
+
47
+ step_size: float, default=0.1
48
+ Step size for Newton iterations.
49
+
50
+ tolerance: float, default=1e-4
51
+ Tolerance for the solution obtained by the Newton solver. Also
52
+ used for the finite difference approximation to the derivative.
53
+
54
+ Returns
55
+ -------
56
+
57
+ rho : numpy.array
58
+ Spectral density
59
+
60
+ See Also
61
+ --------
62
+
63
+ density
64
+ stieltjes
65
+
66
+ Notes
67
+ -----
68
+
69
+ Work in progress.
70
+
71
+ References
72
+ ----------
73
+
74
+ .. [1] tbd
75
+
76
+ Examples
77
+ --------
78
+
79
+ .. code-block:: python
80
+
81
+ >>> from freealg import FreeForm
82
+ """
83
+
84
+ alpha = size / matrix.n
85
+ m = matrix._eval_stieltjes
86
+ # Lower and upper bound on new support
87
+ hilb_lb = (1 / m(matrix.lam_m + delta * 1j)[1]).real
88
+ hilb_ub = (1 / m(matrix.lam_p + delta * 1j)[1]).real
89
+ lb = matrix.lam_m - (alpha - 1) * hilb_lb
90
+ ub = matrix.lam_p - (alpha - 1) * hilb_ub
91
+
92
+ # Create x if not given
93
+ if x is None:
94
+ radius = 0.5 * (ub - lb)
95
+ center = 0.5 * (ub + lb)
96
+ scale = 1.25
97
+ x_min = numpy.floor(center - radius * scale)
98
+ x_max = numpy.ceil(center + radius * scale)
99
+ x = numpy.linspace(x_min, x_max, 500)
100
+
101
+ def _char_z(z):
102
+ return z + (1 / m(z)[1]) * (1 - alpha)
103
+
104
+ # Ensure that input is an array
105
+ x = numpy.asarray(x)
106
+
107
+ target = x + delta * 1j
108
+
109
+ z = numpy.full(target.shape, numpy.mean(matrix.support) - .1j,
110
+ dtype=numpy.complex128)
111
+
112
+ # Broken Newton steps can produce a lot of warnings. Removing them
113
+ # for now.
114
+ with numpy.errstate(all='ignore'):
115
+ for _ in range(iterations):
116
+ objective = _char_z(z) - target
117
+ mask = numpy.abs(objective) >= tolerance
118
+ if not numpy.any(mask):
119
+ break
120
+ z_m = z[mask]
121
+
122
+ # Perform finite difference approximation
123
+ dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
124
+ dfdz /= 2*tolerance
125
+ dfdz[dfdz == 0] = 1.0
126
+
127
+ # Perform Newton step
128
+ z[mask] = z_m - step_size * objective[mask] / dfdz
129
+
130
+ # Plemelj's formula
131
+ char_s = m(z)[1] / alpha
132
+ rho = numpy.maximum(0, char_s.imag / numpy.pi)
133
+ rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
134
+ rho = rho.reshape(*x.shape)
135
+
136
+ return rho, x, (lb, ub)
freealg/_jacobi.py CHANGED
@@ -174,9 +174,7 @@ def jacobi_stieltjes(z, psi, support, alpha=0.0, beta=0.0, n_base=40):
174
174
  integrand = w_nodes * P_k_nodes # (n_quad,)
175
175
 
176
176
  # Broadcast over z: shape (n_quad, ...) / ...
177
- # diff = u_z[None, ...] - t_nodes[:, None] # (n_quad, ...)
178
177
  diff = u_z[None, ...] - t_nodes[:, None, None] # (n_quad, Ny, Nx)
179
- # m_k = (integrand[:, None] / diff).sum(axis=0) # shape like z
180
178
  m_k = (integrand[:, None, None] / diff).sum(axis=0)
181
179
 
182
180
  # Accumulate with factor 2/span
freealg/_pade.py CHANGED
@@ -12,18 +12,342 @@
12
12
  # =======
13
13
 
14
14
  import numpy
15
+ from numpy.linalg import lstsq
15
16
  from itertools import product
16
17
  from scipy.optimize import least_squares, differential_evolution
17
18
 
18
19
  __all__ = ['fit_pade', 'eval_pade']
19
20
 
20
21
 
22
+ # =============
23
+ # default poles
24
+ # =============
25
+
26
+ def _default_poles(q, lam_m, lam_p, safety=1.0, odd_side='left'):
27
+ """
28
+ Generate q real poles outside [lam_m, lam_p].
29
+
30
+ • even q : q/2 on each side (Chebyshev-like layout)
31
+ • odd q : (q+1)/2 on the *left*, (q–1)/2 on the right
32
+ so q=1 => single pole on whichever side `odd_side` says.
33
+
34
+ safety >= 1: 1, then poles start half an interval away; >1 pushes them
35
+ farther.
36
+ """
37
+
38
+ if q == 0:
39
+ return numpy.empty(0)
40
+
41
+ Delta = 0.5 * (lam_p - lam_m)
42
+
43
+ # Decide how many poles on each side. m_L and m_R determine how many poles
44
+ # to be on the left and right of the support interval.
45
+ if q % 2 == 0:
46
+ m_L = m_R = q // 2
47
+ else:
48
+ if odd_side == 'left':
49
+ m_L = (q + 1) // 2
50
+ m_R = q // 2
51
+ else:
52
+ m_L = q // 2
53
+ m_R = (q + 1) // 2
54
+
55
+ # Chebyshev-extrema offsets (all positive)
56
+ kL = numpy.arange(m_L)
57
+ tL = (2 * kL + 1) * numpy.pi / (2 * m_L)
58
+ offsL = safety * Delta * (1 + numpy.cos(tL))
59
+
60
+ kR = numpy.arange(m_R)
61
+ tR = (2 * kR + 1) * numpy.pi / (2 * m_R + (m_R == 0))
62
+ offsR = safety * Delta * (1 + numpy.cos(tR))
63
+
64
+ left = lam_m - offsL
65
+ right = lam_p + offsR
66
+
67
+ return numpy.sort(numpy.concatenate([left, right]))
68
+
69
+
70
+ # ============
71
+ # encode poles
72
+ # ============
73
+
74
+ def _encode_poles(a, lam_m, lam_p):
75
+ """
76
+ Map real pole a_j → unconstrained s_j,
77
+ so that the default left-of-interval pole stays left.
78
+ """
79
+
80
+ # half-width of the interval
81
+ d = 0.5 * (lam_p - lam_m)
82
+ # if a < lam_m, we want s ≥ 0; if a > lam_p, s < 0
83
+ return numpy.where(
84
+ a < lam_m,
85
+ numpy.log((lam_m - a) / d), # zero at a = lam_m - d
86
+ -numpy.log((a - lam_p) / d) # zero at a = lam_p + d
87
+ )
88
+
89
+
90
+ # ============
91
+ # decode poles
92
+ # ============
93
+
94
+ def _decode_poles(s, lam_m, lam_p):
95
+ """
96
+ Inverse map s_j → real pole a_j outside the interval.
97
+ """
98
+
99
+ d = 0.5 * (lam_p - lam_m)
100
+ return numpy.where(
101
+ s >= 0,
102
+ lam_m - d * numpy.exp(s), # maps s=0 to a=lam_m−d (left)
103
+ lam_p + d * numpy.exp(-s) # maps s=0 to a=lam_p+d (right)
104
+ )
105
+
106
+
107
+ # ========
108
+ # inner ls
109
+ # ========
110
+
111
+ # def _inner_ls(x, f, poles): # TEST
112
+ def _inner_ls(x, f, poles, p=1):
113
+ """
114
+ This is the inner least square (blazing fast).
115
+ """
116
+
117
+ if poles.size == 0 and p == -1:
118
+ return 0.0, 0.0, numpy.empty(0)
119
+
120
+ if poles.size == 0: # q = 0
121
+ # A = numpy.column_stack((numpy.ones_like(x), x))
122
+ cols = [numpy.ones_like(x)] if p >= 0 else []
123
+ if p == 1:
124
+ cols.append(x)
125
+ A = numpy.column_stack(cols)
126
+ # ---
127
+ theta, *_ = lstsq(A, f, rcond=None)
128
+ # c, D = theta # TEST
129
+ if p == -1:
130
+ c = 0.0
131
+ D = 0.0
132
+ resid = numpy.empty(0)
133
+ elif p == 0:
134
+ c = theta[0]
135
+ D = 0.0
136
+ resid = numpy.empty(0)
137
+ else: # p == 1
138
+ c, D = theta
139
+ resid = numpy.empty(0)
140
+ else:
141
+ # phi = 1.0 / (x[:, None] - poles[None, :])
142
+ # # A = numpy.column_stack((numpy.ones_like(x), x, phi)) # TEST
143
+ # # theta, *_ = lstsq(A, f, rcond=None)
144
+ # # c, D, resid = theta[0], theta[1], theta[2:]
145
+ # phi = 1.0 / (x[:, None] - poles[None, :])
146
+ # cols = [numpy.ones_like(x)] if p >= 0 else []
147
+ # if p == 1:
148
+ # cols.append(x)
149
+ # cols.append(phi)
150
+ # A = numpy.column_stack(cols)
151
+ # theta, *_ = lstsq(A, f, rcond=None)
152
+ # if p == -1:
153
+ # c = 0.0
154
+ # D = 0.0
155
+ # resid = theta
156
+ # elif p == 0:
157
+ # c = theta[0]
158
+ # D = 0.0
159
+ # resid = theta[1:]
160
+ # else: # p == 1
161
+ # c = theta[0]
162
+ # D = theta[1]
163
+ # resid = theta[2:]
164
+
165
+ phi = 1.0 / (x[:, None] - poles[None, :])
166
+ cols = [numpy.ones_like(x)] if p >= 0 else []
167
+ if p == 1:
168
+ cols.append(x)
169
+ cols.append(phi)
170
+
171
+ A = numpy.column_stack(cols)
172
+ theta, *_ = lstsq(A, f, rcond=None)
173
+
174
+ if p == -1:
175
+ c, D, resid = 0.0, 0.0, theta
176
+ elif p == 0:
177
+ c, D, resid = theta[0], 0.0, theta[1:]
178
+ else: # p == 1
179
+ c, D, resid = theta[0], theta[1], theta[2:]
180
+
181
+ return c, D, resid
182
+
183
+
184
+ # =============
185
+ # eval rational
186
+ # =============
187
+
188
+ def _eval_rational(z, c, D, poles, resid):
189
+ """
190
+ """
191
+
192
+ # z = z[:, None]
193
+ # if poles.size == 0:
194
+ # term = 0.0
195
+ # else:
196
+ # term = numpy.sum(resid / (z - poles), axis=1)
197
+ #
198
+ # return c + D * z.ravel() + term
199
+
200
+ # ensure z is a 1-D array
201
+ z = numpy.asarray(z)
202
+ z_col = z[:, None]
203
+
204
+ if poles.size == 0:
205
+ term = 0.0
206
+ else:
207
+ term = numpy.sum(resid / (z_col - poles[None, :]), axis=1)
208
+
209
+ return c + D * z + term
210
+
211
+
21
212
  # ========
22
213
  # fit pade
23
214
  # ========
24
215
 
25
- def fit_pade(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf, S=numpy.inf,
26
- B_default=10.0, S_factor=2.0, maxiter_de=200):
216
+ def fit_pade(x, f, lam_m, lam_p, p=1, q=2, odd_side='left', safety=1.0,
217
+ max_outer=40, xtol=1e-12, ftol=1e-12, optimizer='ls', verbose=0):
218
+ """
219
+ This is the outer optimiser.
220
+
221
+ Fits G(x) = (p>=1 ? c : 0) + (p==1 ? D x : 0) + sum r_j/(x - a_j) # TEST
222
+ """
223
+
224
+ # Checks
225
+ if not (odd_side in ['left', 'right']):
226
+ raise ValueError('"odd_side" can only be "left" or "right".')
227
+
228
+ if not (p in [-1, 0, 1]):
229
+ raise ValueError('"pade_p" can only be -1, 0, or 1.')
230
+
231
+ x = numpy.asarray(x, float)
232
+ f = numpy.asarray(f, float)
233
+
234
+ poles0 = _default_poles(q, lam_m, lam_p, safety=safety, odd_side=odd_side)
235
+ # if q == 0: # nothing to optimise
236
+ if q == 0 and p <= 0:
237
+ # c, D, resid = _inner_ls(x, f, poles0) # TEST
238
+ c, D, resid = _inner_ls(x, f, poles0, p)
239
+ pade_sol = {
240
+ 'c': c, 'D': D, 'poles': poles0, 'resid': resid,
241
+ 'outer_iters': 0
242
+ }
243
+
244
+ return pade_sol
245
+
246
+ s0 = _encode_poles(poles0, lam_m, lam_p)
247
+
248
+ # --------
249
+ # residual
250
+ # --------
251
+
252
+ # def residual(s): # TEST
253
+ def residual(s, p=p):
254
+ poles = _decode_poles(s, lam_m, lam_p)
255
+ # c, D, resid = _inner_ls(x, f, poles) # TEST
256
+ c, D, resid = _inner_ls(x, f, poles, p)
257
+ return _eval_rational(x, c, D, poles, resid) - f
258
+
259
+ # ----------------
260
+
261
+ # Optimizer
262
+ if optimizer == 'ls':
263
+ # scale = numpy.maximum(1.0, numpy.abs(s0))
264
+ res = least_squares(residual, s0,
265
+ method='trf',
266
+ # method='lm',
267
+ # x_scale=scale,
268
+ max_nfev=max_outer, xtol=xtol, ftol=ftol,
269
+ verbose=verbose)
270
+
271
+ elif optimizer == 'de':
272
+
273
+ # Bounds
274
+ # span = lam_p - lam_m
275
+ # B = 3.0 # multiples of span
276
+ # L = numpy.log(B * span)
277
+ # bounds = [(-L, L)] * len(s0)
278
+
279
+ d = 0.5*(lam_p - lam_m)
280
+ # the minimum factor so that lam_m - d*exp(s)=0 is exp(s)=lam_m/d
281
+ min_factor = lam_m/d
282
+ B = max(10.0, min_factor*10.0)
283
+ L = numpy.log(B)
284
+ bounds = [(-L, L)] * len(s0)
285
+
286
+ # Global stage
287
+ glob = differential_evolution(lambda s: numpy.sum(residual(s)**2),
288
+ bounds, maxiter=50, popsize=10,
289
+ polish=False)
290
+
291
+ # local polish
292
+ res = least_squares(
293
+ residual, glob.x,
294
+ method='lm',
295
+ max_nfev=max_outer, xtol=xtol, ftol=ftol,
296
+ verbose=verbose)
297
+
298
+ else:
299
+ raise RuntimeError('"optimizer" is invalid.')
300
+
301
+ poles = _decode_poles(res.x, lam_m, lam_p)
302
+ # c, D, resid = _inner_ls(x, f, poles) # TEST
303
+ c, D, resid = _inner_ls(x, f, poles, p)
304
+
305
+ pade_sol = {
306
+ 'c': c, 'D': D, 'poles': poles, 'resid': resid,
307
+ 'outer_iters': res.nfev
308
+ }
309
+
310
+ return pade_sol
311
+
312
+
313
+ # =========
314
+ # eval pade
315
+ # =========
316
+
317
+ def eval_pade(z, pade_sol):
318
+ """
319
+ """
320
+
321
+ # z_arr = numpy.asanyarray(z) # shape=(M,N)
322
+ # flat = z_arr.ravel() # shape=(M·N,)
323
+ # c, D = pade_sol['c'], pade_sol['D']
324
+ # poles = pade_sol['poles']
325
+ # resid = pade_sol['resid']
326
+ #
327
+ # # _eval_rational takes a 1-D array of z's and returns 1-D outputs
328
+ # flat_out = _eval_rational(flat, c, D, poles, resid)
329
+ #
330
+ # # restore the original shape
331
+ # out = flat_out.reshape(z_arr.shape) # shape=(M,N)
332
+ #
333
+ # return out
334
+
335
+ z = numpy.asanyarray(z) # complex or real, any shape
336
+ c, D = pade_sol['c'], pade_sol['D']
337
+ poles, resid = pade_sol['poles'], pade_sol['resid']
338
+
339
+ out = c + D*z
340
+ for bj, rj in zip(poles, resid):
341
+ out += rj/(z - bj) # each is an (N,) op, no N×q temp
342
+ return out
343
+
344
+
345
+ # ============
346
+ # fit pade old
347
+ # ============
348
+
349
+ def fit_pade_old(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf,
350
+ S=numpy.inf, B_default=10.0, S_factor=2.0, maxiter_de=200):
27
351
  """
28
352
  Fit a [p/q] rational P/Q of the form:
29
353
  P(x) = s * prod_{i=0..p-1}(x - a_i)
@@ -125,11 +449,11 @@ def fit_pade(x, f, lam_m, lam_p, p, q, delta=1e-8, B=numpy.inf, S=numpy.inf,
125
449
  }
126
450
 
127
451
 
128
- # =========
129
- # eval pade
130
- # =========
452
+ # =============
453
+ # eval pade old
454
+ # =============
131
455
 
132
- def eval_pade(z, s, a, b):
456
+ def eval_pade_old(z, s, a, b):
133
457
  """
134
458
  """
135
459
 
freealg/_plot_util.py CHANGED
@@ -19,28 +19,51 @@ import colorsys
19
19
  import matplotlib.ticker as ticker
20
20
  import matplotlib.gridspec as gridspec
21
21
 
22
- __all__ = ['plot_coeff', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
23
- 'plot_stieltjes_on_disk', 'plot_glue_fit']
22
+ __all__ = ['plot_fit', 'plot_density', 'plot_hilbert', 'plot_stieltjes',
23
+ 'plot_stieltjes_on_disk']
24
24
 
25
25
 
26
- # ==========
27
- # plot coeff
28
- # ==========
26
+ # ========
27
+ # plot fit
28
+ # ========
29
29
 
30
- def plot_coeff(psi, latex=False, save=False):
30
+ def plot_fit(psi, x_supp, g_supp, g_supp_approx, support, latex=False,
31
+ save=False):
31
32
  """
32
33
  """
33
34
 
34
35
  with texplot.theme(use_latex=latex):
35
36
 
36
- fig, ax = plt.subplots(figsize=(6, 2.7))
37
+ fig, ax = plt.subplots(figsize=(9, 3), ncols=2)
38
+
39
+ # Plot psi
37
40
  n = numpy.arange(1, 1+psi.size)
38
- ax.plot(n, psi**2, '-o', markersize=3, color='black')
39
- ax.set_xlim([n[0]-1e-3, n[-1]+1e-3])
40
- ax.set_xlabel(r'$k$')
41
- ax.set_ylabel(r'$\vert \psi_k \vert^2$')
42
- ax.set_title('Spectral Energy per Mode')
43
- ax.set_yscale('log')
41
+ ax[0].plot(n, psi**2, '-o', markersize=3, color='black')
42
+ ax[0].set_xlim([n[0]-1e-3, n[-1]+1e-3])
43
+ ax[0].set_xlabel(r'$k$')
44
+ ax[0].set_ylabel(r'$\vert \psi_k \vert^2$')
45
+ ax[0].set_title('Spectral Energy per Mode')
46
+ ax[0].set_yscale('log')
47
+
48
+ # Plot pade
49
+ lam_m, lam_p = support
50
+ g_supp_min = numpy.min(g_supp)
51
+ g_supp_max = numpy.max(g_supp)
52
+ g_supp_dif = g_supp_max - g_supp_min
53
+ g_min = g_supp_min - g_supp_dif * 1.1
54
+ g_max = g_supp_max + g_supp_dif * 1.1
55
+
56
+ ax[1].plot(x_supp, g_supp, color='firebrick',
57
+ label=r'$2 \pi \times $ Hilbert Transform')
58
+ ax[1].plot(x_supp, g_supp_approx, color='black', label='Pade estimate')
59
+ ax[1].legend(fontsize='small')
60
+ ax[1].set_xlim([lam_m, lam_p])
61
+ ax[1].set_ylim([g_min, g_max])
62
+ ax[1].set_title('Approximation of Glue Function')
63
+ ax[1].set_xlabel(r'$x$')
64
+ ax[1].set_ylabel(r'$G(x)$')
65
+
66
+ plt.tight_layout()
44
67
 
45
68
  # Save
46
69
  if save is False:
@@ -51,7 +74,7 @@ def plot_coeff(psi, latex=False, save=False):
51
74
  if isinstance(save, str):
52
75
  save_filename = save
53
76
  else:
54
- save_filename = 'energy.pdf'
77
+ save_filename = 'fit.pdf'
55
78
 
56
79
  texplot.show_or_save_plot(plt, default_filename=save_filename,
57
80
  transparent_background=True, dpi=400,
@@ -349,6 +372,12 @@ def plot_stieltjes_on_disk(r, t, m1_D, m2_D, support, latex=False, save=False):
349
372
  lam_m_z = (lam_m - 1j) / (lam_m + 1j)
350
373
  theta_p = numpy.angle(lam_p_z)
351
374
  theta_n = numpy.angle(lam_m_z)
375
+
376
+ if theta_n < 0:
377
+ theta_n += 2.0 * numpy.pi
378
+ if theta_p < 0:
379
+ theta_p += 2.0 * numpy.pi
380
+
352
381
  theta_branch = numpy.linspace(theta_n, theta_p, 100)
353
382
  theta_alt_branch = numpy.linspace(theta_p, theta_n + 2*numpy.pi, 100)
354
383
  r_branch = numpy.ones_like(theta_branch)
@@ -462,45 +491,6 @@ def plot_stieltjes_on_disk(r, t, m1_D, m2_D, support, latex=False, save=False):
462
491
  show_and_save=save_status, verbose=True)
463
492
 
464
493
 
465
- # =============
466
- # plot glue fit
467
- # =============
468
-
469
- def plot_glue_fit(x_supp, g_supp, g_supp_approx, support, latex=False,
470
- save=False):
471
- """
472
- """
473
-
474
- with texplot.theme(use_latex=latex):
475
-
476
- fig, ax = plt.subplots(figsize=(6, 3))
477
-
478
- lam_m, lam_p = support
479
- ax.plot(x_supp, g_supp, color='black', label='Glue target')
480
- ax.plot(x_supp, g_supp_approx, color='firebrick',
481
- label='Glue estimate')
482
- ax.legend(fontsize='small')
483
- ax.set_xlim([lam_m, lam_p])
484
- ax.set_title('Approximation of Glue function on real axis')
485
- ax.set_xlabel(r'$x$')
486
- ax.set_ylabel(r'$G(x)$')
487
-
488
- # Save
489
- if save is False:
490
- save_status = False
491
- save_filename = ''
492
- else:
493
- save_status = True
494
- if isinstance(save, str):
495
- save_filename = save
496
- else:
497
- save_filename = 'glue_fit.pdf'
498
-
499
- texplot.show_or_save_plot(plt, default_filename=save_filename,
500
- transparent_background=True, dpi=400,
501
- show_and_save=save_status, verbose=True)
502
-
503
-
504
494
  # ============
505
495
  # plot samples
506
496
  # ============