freealg 0.1.12__py3-none-any.whl → 0.1.13__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.1.12"
1
+ __version__ = "0.1.13"
freealg/_decompress.py CHANGED
@@ -15,13 +15,133 @@ import numpy
15
15
 
16
16
  __all__ = ['decompress', 'reverse_characteristics']
17
17
 
18
+ def secant_complex(f, z0, z1, a=0+0j, tol=1e-12, max_iter=100,
19
+ alpha=0.5, max_bt=12, eps=1e-30, verbose=False):
20
+ """
21
+ Solves :math:``f(z) = a`` for many starting points simultaneously
22
+ using the secant method in the complex plane.
23
+
24
+ Parameters
25
+ ----------
26
+ f : callable
27
+ Function that accepts and returns complex `ndarray`s.
28
+
29
+ z0, z1 : array_like
30
+ Two initial guesses. ``z1`` may be broadcast to ``z0``.
31
+
32
+ a : complex or array_like, optional
33
+ Right‑hand‑side targets (broadcasted to ``z0``). Defaults to ``0+0j``.
34
+
35
+ tol : float, optional
36
+ Convergence criterion on ``|f(z) - a|``. Defaults to ``1e-12``.
37
+
38
+ max_iter : int, optional
39
+ Maximum number of secant iterations. Defaults to ``100``.
40
+
41
+ alpha : float, optional
42
+ Back‑tracking shrink factor (``0 < alpha < 1``). Defaults to ``0.5``.
43
+
44
+ max_bt : int, optional
45
+ Maximum back‑tracking trials per iteration. Defaults to ``12``.
46
+
47
+ eps : float, optional
48
+ Safeguard added to tiny denominators. Defaults to ``1e-30``.
49
+
50
+ verbose : bool, optional
51
+ If *True*, prints progress every 10 iterations.
52
+
53
+ Returns
54
+ -------
55
+ roots : ndarray
56
+ Estimated roots, shaped like the broadcast inputs.
57
+ residuals : ndarray
58
+ Final residuals ``|f(root) - a|``.
59
+ iterations : ndarray
60
+ Iteration count for each point.
61
+ """
62
+
63
+ # Broadcast inputs
64
+ z0, z1, a = numpy.broadcast_arrays(
65
+ numpy.asarray(z0, numpy.complex128),
66
+ numpy.asarray(z1, numpy.complex128),
67
+ numpy.asarray(a, numpy.complex128),
68
+ )
69
+ orig_shape = z0.shape
70
+ z0, z1, a = (x.ravel() for x in (z0, z1, a))
71
+
72
+ n_points = z0.size
73
+ roots = z1.copy()
74
+ iterations = numpy.zeros(n_points, dtype=int)
75
+
76
+ f0 = f(z0) - a
77
+ f1 = f(z1) - a
78
+ residuals = numpy.abs(f1)
79
+ converged = residuals < tol
80
+
81
+ # Entering main loop
82
+ for k in range(max_iter):
83
+ active = ~converged
84
+ if not active.any():
85
+ break
86
+
87
+ # Secant step
88
+ denom = f1 - f0
89
+ denom = numpy.where(numpy.abs(denom) < eps, denom + eps, denom)
90
+ dz = (z1 - z0) * f1 / denom
91
+ z2 = z1 - dz
92
+ f2 = f(z2) - a
93
+
94
+ # Line search by backtracking
95
+ worse = (numpy.abs(f2) >= numpy.abs(f1)) & active
96
+ if worse.any():
97
+ shrink = numpy.ones_like(dz)
98
+ for _ in range(max_bt):
99
+ shrink[worse] *= alpha
100
+ z_try = z1[worse] - shrink[worse] * dz[worse]
101
+ f_try = f(z_try) - a[worse]
102
+
103
+ improved = numpy.abs(f_try) < numpy.abs(f1[worse])
104
+ if not improved.any():
105
+ continue
106
+
107
+ idx = numpy.flatnonzero(worse)[improved]
108
+ z2[idx], f2[idx] = z_try[improved], f_try[improved]
109
+ worse[idx] = False
110
+ if not worse.any():
111
+ break
112
+
113
+ # Book‑keeping
114
+ newly_conv = (numpy.abs(f2) < tol) & active
115
+ converged[newly_conv] = True
116
+ iterations[newly_conv] = k + 1
117
+ roots[newly_conv] = z2[newly_conv]
118
+ residuals[newly_conv] = numpy.abs(f2[newly_conv])
119
+
120
+ still = active & ~newly_conv
121
+ z0[still], z1[still] = z1[still], z2[still]
122
+ f0[still], f1[still] = f1[still], f2[still]
123
+
124
+ if verbose and k % 10 == 0:
125
+ print(f"Iter {k}: {converged.sum()} / {n_points} converged")
126
+
127
+ # Non‑converged points
128
+ remaining = ~converged
129
+ roots[remaining] = z1[remaining]
130
+ residuals[remaining] = numpy.abs(f1[remaining])
131
+ iterations[remaining] = max_iter
132
+
133
+ return (
134
+ roots.reshape(orig_shape),
135
+ residuals.reshape(orig_shape),
136
+ iterations.reshape(orig_shape),
137
+ )
18
138
 
19
139
  # ==========
20
140
  # decompress
21
141
  # ==========
22
142
 
23
- def decompress(freeform, size, x=None, delta=1e-6, iterations=500,
24
- step_size=0.1, tolerance=1e-9):
143
+ def decompress(freeform, size, x=None, delta=1e-6, max_iter=500,
144
+ tolerance=1e-12):
25
145
  """
26
146
  Free decompression of spectral density.
27
147
 
@@ -42,15 +162,11 @@ def decompress(freeform, size, x=None, delta=1e-6, iterations=500,
42
162
  Size of the perturbation into the upper half plane for Plemelj's
43
163
  formula.
44
164
 
45
- iterations: int, default=500
46
- Maximum number of Newton iterations.
165
+ max_iter: int, default=500
166
+ Maximum number of secant method iterations.
47
167
 
48
- step_size: float, default=0.1
49
- Step size for Newton iterations.
50
-
51
- tolerance: float, default=1e-4
52
- Tolerance for the solution obtained by the Newton solver. Also
53
- used for the finite difference approximation to the derivative.
168
+ tolerance: float, default=1e-12
169
+ Tolerance for the solution obtained by the secant method solver.
54
170
 
55
171
  Returns
56
172
  -------
@@ -99,42 +215,34 @@ def decompress(freeform, size, x=None, delta=1e-6, iterations=500,
99
215
  x_max = numpy.ceil(center + radius * scale)
100
216
  x = numpy.linspace(x_min, x_max, 500)
101
217
 
102
- def _char_z(z):
103
- return z + (1 / m(z)[1]) * (1 - alpha)
104
-
105
218
  # Ensure that input is an array
106
219
  x = numpy.asarray(x)
107
-
108
220
  target = x + delta * 1j
221
+ if numpy.isclose(alpha, 1.0):
222
+ return freeform.density(x), x, freeform.support
109
223
 
110
- z = numpy.full(target.shape, numpy.mean(freeform.support) - .1j,
111
- dtype=numpy.complex128)
112
-
113
- # Broken Newton steps can produce a lot of warnings. Removing them
114
- # for now.
115
- with numpy.errstate(all='ignore'):
116
- for _ in range(iterations):
117
- objective = _char_z(z) - target
118
- mask = numpy.abs(objective) >= tolerance
119
- if not numpy.any(mask):
120
- break
121
- z_m = z[mask]
122
-
123
- # Perform finite difference approximation
124
- dfdz = _char_z(z_m+tolerance) - _char_z(z_m-tolerance)
125
- dfdz /= 2*tolerance
126
- dfdz[dfdz == 0] = 1.0
224
+ # Characteristic curve map
225
+ def _char_z(z):
226
+ return z + (1 / m(z)[1]) * (1 - alpha)
127
227
 
128
- # Perform Newton step
129
- z[mask] = z_m - step_size * objective[mask] / dfdz
228
+ z0 = numpy.full(target.shape, numpy.mean(freeform.support) + delta*1j,
229
+ dtype=numpy.complex128)
230
+ z1 = z0 - numpy.log(alpha) * 1j
231
+
232
+ roots, _, _ = secant_complex(
233
+ _char_z, z0, z1,
234
+ a=target,
235
+ tol=tolerance,
236
+ max_iter=max_iter
237
+ )
130
238
 
131
239
  # Plemelj's formula
240
+ z = roots
132
241
  char_s = m(z)[1] / alpha
133
242
  rho = numpy.maximum(0, char_s.imag / numpy.pi)
134
243
  rho[numpy.isnan(rho) | numpy.isinf(rho)] = 0
135
- rho = rho.reshape(*x.shape)
136
244
 
137
- return rho, x, (lb, ub)
245
+ return rho.reshape(*x.shape), x, (lb, ub)
138
246
 
139
247
 
140
248
  # =======================
freealg/freeform.py CHANGED
@@ -783,7 +783,7 @@ class FreeForm(object):
783
783
  # decompress
784
784
  # ==========
785
785
 
786
- def decompress(self, size, x=None, iterations=500, eigvals=True,
786
+ def decompress(self, size, x=None, max_iter=500, eigvals=True,
787
787
  step_size=0.1, tolerance=1e-9, seed=None, plot=False,
788
788
  latex=False, save=False):
789
789
  """
@@ -799,8 +799,8 @@ class FreeForm(object):
799
799
  Positions where density to be evaluated at. If `None`, an interval
800
800
  slightly larger than the support interval will be used.
801
801
 
802
- iterations: int, default=500
803
- Maximum number of Newton iterations.
802
+ max_iter: int, default=500
803
+ Maximum number of secant method iterations.
804
804
 
805
805
  eigvals: bool, default=True
806
806
  Return estimated (sampled) eigenvalues as well as the density.
@@ -867,8 +867,7 @@ class FreeForm(object):
867
867
  size = int(size)
868
868
 
869
869
  rho, x, (lb, ub) = decompress(self, size, x=x, delta=self.delta,
870
- iterations=iterations,
871
- step_size=step_size, tolerance=tolerance)
870
+ max_iter=max_iter, tolerance=tolerance)
872
871
  x, rho = x.ravel(), rho.ravel()
873
872
 
874
873
  if plot:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freealg
3
- Version: 0.1.12
3
+ Version: 0.1.13
4
4
  Summary: Free probability for large matrices
5
5
  Keywords: leaderboard bot chat
6
6
  Platform: Linux
@@ -1,24 +1,24 @@
1
1
  freealg/__init__.py,sha256=YqewBd3fq4nm-L3oGcExhEDR2wtVcrtggkSGzfpDqr4,528
2
- freealg/__version__.py,sha256=LcIlFjHZFfiF9Rd4UHoakmombOFkxIYk00I181frGBM,23
2
+ freealg/__version__.py,sha256=khDKUuWafURKVs5EAZkpOMiUHI2-V7axlqrWLPUpuZo,23
3
3
  freealg/_chebyshev.py,sha256=oDJtRCwKEHazitzVBDbfdQz1jkyfsJOJfcAfo-PNkNo,5745
4
4
  freealg/_damp.py,sha256=k2vtBtWOxQBf4qXaWu_En81lQBXbEO4QbxxWpvuVhdE,1802
5
- freealg/_decompress.py,sha256=WdKXkZ9cbrzIHEACEZyVmLNR9kMK7OQLaBFsZKUjIKQ,4723
5
+ freealg/_decompress.py,sha256=vTnxV_7XPYUVVnWCnALCrZkQT77IAPm_CzrWMgiTiqg,7986
6
6
  freealg/_jacobi.py,sha256=AT4ONSHGGDxVKE3MGMLyMR8uDFiO-e9u3x5udYfdJJk,5635
7
7
  freealg/_pade.py,sha256=yREJYSmnWaVUNRBNxjuQUqeLe_XSaGa9_VzV6HG5RkA,15164
8
8
  freealg/_plot_util.py,sha256=BOYre8FPhrxmW1VRj3I40dCjWTFqUBTInmXc3wFunKQ,19648
9
9
  freealg/_sample.py,sha256=ckC75eqv-mRP1F5BnhvsjfLTaoAzHK8bebl9bCRZYDo,2561
10
10
  freealg/_support.py,sha256=A8hUjfKnSkHm09KLcEkeEXeTieKjhH-sVPd7I3_p4VE,3100
11
11
  freealg/_util.py,sha256=PWLXcsTb0-FinGWvNiY12D-f4CHQB5bP_W3ThqfY4FY,3681
12
- freealg/freeform.py,sha256=OtFiLeaViEUUzxI4Ivp5twf6Pkr7hpqERFppa6C6kCA,30435
12
+ freealg/freeform.py,sha256=_HDgchJaeryUTgywobSM4Yr8SjXi6pRVG8kQkTKDdMM,30375
13
13
  freealg/distributions/__init__.py,sha256=t_yZyEkW_W_tSV9IvgYXtVASxD2BEdiNVXcV2ebMy8M,579
14
14
  freealg/distributions/_kesten_mckay.py,sha256=HDMjbM1AcNxlwrpYeGmRqcbP10QsLI5RCeKvjVK3tOk,19566
15
15
  freealg/distributions/_marchenko_pastur.py,sha256=th921hlEEtTbnHnRyBgT54a_e-9ZzAl9rB78O9FjorY,16688
16
16
  freealg/distributions/_meixner.py,sha256=ItE0zYG2vhyUkObxbx4bDZaJ0BHVQWPzAJGLdMz10l4,17206
17
17
  freealg/distributions/_wachter.py,sha256=lw70PT3TZlCf7mHU8IqoygXFUWB4IL57obkng0_ZGeI,16591
18
18
  freealg/distributions/_wigner.py,sha256=2ZSPjgmDr9q9qiz6jO6yhXFo4ALHfxK1f0EzolzhRNE,15565
19
- freealg-0.1.12.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
20
- freealg-0.1.12.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
21
- freealg-0.1.12.dist-info/METADATA,sha256=Xwep_keCRRaY_GBIsLUnZ8Opx5-I20dhXJ8us53_wAI,4029
22
- freealg-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- freealg-0.1.12.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
24
- freealg-0.1.12.dist-info/RECORD,,
19
+ freealg-0.1.13.dist-info/licenses/AUTHORS.txt,sha256=0b67Nz4_JgIzUupHJTAZxu5QdSUM_HRM_X_w4xCb17o,30
20
+ freealg-0.1.13.dist-info/licenses/LICENSE.txt,sha256=J-EEYEtxb3VVf_Bn1TYfWnpY5lMFIM15iLDDcnaDTPA,1443
21
+ freealg-0.1.13.dist-info/METADATA,sha256=83nEmVJt6xwHPu6o0qlYN0JQ_zYuqgIPgQBvHsAdvkE,4029
22
+ freealg-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ freealg-0.1.13.dist-info/top_level.txt,sha256=eR2wrgYwDdnnJ9Zf5PruPqe4kQav0GMvRsqct6y00Q8,8
24
+ freealg-0.1.13.dist-info/RECORD,,