Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -6,9 +6,9 @@ from scipy.optimize import fsolve
|
|
6 |
|
7 |
# Configure Streamlit for Hugging Face Spaces
|
8 |
st.set_page_config(
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
)
|
13 |
|
14 |
#############################
|
@@ -26,354 +26,350 @@ d_sym = 1
|
|
26 |
|
27 |
# Symbolic expression for the standard cubic discriminant
|
28 |
Delta_expr = (
|
29 |
-
|
30 |
-
|
31 |
)
|
32 |
|
33 |
# Turn that into a fast numeric function:
|
34 |
discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "numpy")
|
35 |
|
36 |
@st.cache_data
|
37 |
-
def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
|
77 |
@st.cache_data
|
78 |
-
def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
|
98 |
@st.cache_data
|
99 |
def compute_low_y_curve(betas, z_a, y):
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
|
116 |
@st.cache_data
|
117 |
def compute_high_y_curve(betas, z_a, y):
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
|
130 |
-
def generate_z_vs_beta_plot(z_a, y, z_min, z_max):
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
return fig
|
195 |
|
196 |
def compute_cubic_roots(z, beta, z_a, y):
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
|
209 |
-
def generate_root_plots(beta, y, z_a, z_min, z_max):
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
|
268 |
def curve1(s, z, y):
|
269 |
-
|
270 |
-
|
271 |
|
272 |
def curve2(s, y, beta, a):
|
273 |
-
|
274 |
-
|
275 |
|
276 |
-
def find_intersections(z, y, beta, a, s_range):
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
intersections = np.sort(np.append(intersections, refined_intersections))
|
320 |
-
|
321 |
-
return intersections
|
322 |
|
323 |
-
def generate_curves_plot(z, y, beta, a, s_range):
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
y=0.99,
|
378 |
xanchor="left",
|
379 |
x=0.01
|
@@ -398,9 +394,13 @@ with tab1:
|
|
398 |
z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
|
399 |
z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
|
400 |
|
|
|
|
|
|
|
|
|
401 |
if st.button("Compute z vs. β Curves"):
|
402 |
with col2:
|
403 |
-
fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
|
404 |
if fig is not None:
|
405 |
st.plotly_chart(fig, use_container_width=True)
|
406 |
|
@@ -431,9 +431,12 @@ with tab2:
|
|
431 |
z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_2")
|
432 |
z_max_2 = st.number_input("z_max", value=10.0, key="z_max_2")
|
433 |
|
|
|
|
|
|
|
434 |
if st.button("Compute Complex Roots vs. z"):
|
435 |
with col2:
|
436 |
-
fig_im, fig_re = generate_root_plots(beta, y_2, z_a_2, z_min_2, z_max_2)
|
437 |
if fig_im is not None and fig_re is not None:
|
438 |
st.plotly_chart(fig_im, use_container_width=True)
|
439 |
st.plotly_chart(fig_re, use_container_width=True)
|
@@ -449,15 +452,23 @@ with tab3:
|
|
449 |
beta_3 = st.slider("β", min_value=0.0, max_value=1.0, value=0.5, step=0.01, key="beta_3")
|
450 |
a = st.slider("a", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
|
451 |
|
452 |
-
# Add range inputs for s
|
453 |
st.subheader("s Range")
|
454 |
s_min = st.number_input("s_min", value=-5.0)
|
455 |
s_max = st.number_input("s_max", value=5.0)
|
456 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
457 |
if st.button("Compute Intersections"):
|
458 |
with col2:
|
459 |
s_range = (s_min, s_max)
|
460 |
-
fig, intersections = generate_curves_plot(z, y_3, beta_3, a, s_range)
|
461 |
st.plotly_chart(fig, use_container_width=True)
|
462 |
|
463 |
if len(intersections) > 0:
|
|
|
6 |
|
7 |
# Configure Streamlit for Hugging Face Spaces
|
8 |
st.set_page_config(
|
9 |
+
page_title="Cubic Root Analysis",
|
10 |
+
layout="wide",
|
11 |
+
initial_sidebar_state="collapsed"
|
12 |
)
|
13 |
|
14 |
#############################
|
|
|
26 |
|
27 |
# Symbolic expression for the standard cubic discriminant
|
28 |
Delta_expr = (
|
29 |
+
( (b_sym*c_sym)/(6*a_sym**2) - (b_sym**3)/(27*a_sym**3) - d_sym/(2*a_sym) )**2
|
30 |
+
+ ( c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2) )**3
|
31 |
)
|
32 |
|
33 |
# Turn that into a fast numeric function:
|
34 |
discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "numpy")
|
35 |
|
36 |
@st.cache_data
|
37 |
+
def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
|
38 |
+
"""
|
39 |
+
Numerically scan z in [z_min, z_max] looking for sign changes of
|
40 |
+
Delta(z) = 0. Returns all roots found via bisection.
|
41 |
+
"""
|
42 |
+
z_grid = np.linspace(z_min, z_max, steps)
|
43 |
+
disc_vals = discriminant_func(z_grid, beta, z_a, y)
|
44 |
+
|
45 |
+
roots_found = []
|
46 |
+
|
47 |
+
# Scan for sign changes
|
48 |
+
for i in range(len(z_grid) - 1):
|
49 |
+
f1, f2 = disc_vals[i], disc_vals[i+1]
|
50 |
+
if np.isnan(f1) or np.isnan(f2):
|
51 |
+
continue
|
52 |
+
|
53 |
+
if f1 == 0.0:
|
54 |
+
roots_found.append(z_grid[i])
|
55 |
+
elif f2 == 0.0:
|
56 |
+
roots_found.append(z_grid[i+1])
|
57 |
+
elif f1*f2 < 0:
|
58 |
+
zl = z_grid[i]
|
59 |
+
zr = z_grid[i+1]
|
60 |
+
for _ in range(50):
|
61 |
+
mid = 0.5*(zl + zr)
|
62 |
+
fm = discriminant_func(mid, beta, z_a, y)
|
63 |
+
if fm == 0:
|
64 |
+
zl = zr = mid
|
65 |
+
break
|
66 |
+
if np.sign(fm) == np.sign(f1):
|
67 |
+
zl = mid
|
68 |
+
f1 = fm
|
69 |
+
else:
|
70 |
+
zr = mid
|
71 |
+
f2 = fm
|
72 |
+
root_approx = 0.5*(zl + zr)
|
73 |
+
roots_found.append(root_approx)
|
74 |
+
|
75 |
+
return np.array(roots_found)
|
76 |
|
77 |
@st.cache_data
|
78 |
+
def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps):
|
79 |
+
"""
|
80 |
+
For each beta, find both the largest and smallest z where discriminant=0.
|
81 |
+
Returns (betas, z_min_values, z_max_values).
|
82 |
+
"""
|
83 |
+
betas = np.linspace(0, 1, beta_steps)
|
84 |
+
z_min_values = []
|
85 |
+
z_max_values = []
|
86 |
+
|
87 |
+
for b in betas:
|
88 |
+
roots = find_z_at_discriminant_zero(z_a, y, b, z_min, z_max, z_steps)
|
89 |
+
if len(roots) == 0:
|
90 |
+
z_min_values.append(np.nan)
|
91 |
+
z_max_values.append(np.nan)
|
92 |
+
else:
|
93 |
+
z_min_values.append(np.min(roots))
|
94 |
+
z_max_values.append(np.max(roots))
|
95 |
+
|
96 |
+
return betas, np.array(z_min_values), np.array(z_max_values)
|
97 |
|
98 |
@st.cache_data
|
99 |
def compute_low_y_curve(betas, z_a, y):
|
100 |
+
"""
|
101 |
+
Compute the additional curve with proper handling of divide by zero cases
|
102 |
+
"""
|
103 |
+
betas = np.array(betas)
|
104 |
+
with np.errstate(invalid='ignore', divide='ignore'):
|
105 |
+
sqrt_term = y * betas * (z_a - 1)
|
106 |
+
sqrt_term = np.where(sqrt_term < 0, np.nan, np.sqrt(sqrt_term))
|
107 |
+
|
108 |
+
term = (-1 + sqrt_term)/z_a
|
109 |
+
numerator = (y - 2)*term + y * betas * ((z_a - 1)/z_a) - 1/z_a - 1
|
110 |
+
denominator = term**2 + term
|
111 |
+
|
112 |
+
# Handle division by zero and invalid values
|
113 |
+
mask = (denominator != 0) & ~np.isnan(denominator) & ~np.isnan(numerator)
|
114 |
+
return np.where(mask, numerator/denominator, np.nan)
|
115 |
|
116 |
@st.cache_data
|
117 |
def compute_high_y_curve(betas, z_a, y):
|
118 |
+
"""
|
119 |
+
Compute the expression: ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
|
120 |
+
"""
|
121 |
+
betas = np.array(betas)
|
122 |
+
denominator = 3*(4 - z_a)
|
123 |
+
|
124 |
+
if denominator == 0:
|
125 |
+
return np.full_like(betas, np.nan)
|
126 |
+
|
127 |
+
numerator = (4*y + 12)*(4 - z_a) + 16*y*betas*(z_a - 1)
|
128 |
+
return numerator/denominator
|
129 |
|
130 |
+
def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps):
|
131 |
+
if z_a <= 0 or y <= 0 or z_min >= z_max:
|
132 |
+
st.error("Invalid input parameters.")
|
133 |
+
return None
|
134 |
+
|
135 |
+
betas = np.linspace(0, 1, beta_steps)
|
136 |
+
|
137 |
+
betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps, z_steps)
|
138 |
+
low_y_curve = compute_low_y_curve(betas, z_a, y)
|
139 |
+
high_y_curve = compute_high_y_curve(betas, z_a, y)
|
140 |
+
|
141 |
+
fig = go.Figure()
|
142 |
+
|
143 |
+
fig.add_trace(
|
144 |
+
go.Scatter(
|
145 |
+
x=betas,
|
146 |
+
y=z_maxs,
|
147 |
+
mode="markers+lines",
|
148 |
+
name="Upper z*(β)",
|
149 |
+
marker=dict(size=5, color='blue'),
|
150 |
+
line=dict(color='blue'),
|
151 |
+
)
|
152 |
+
)
|
153 |
+
|
154 |
+
fig.add_trace(
|
155 |
+
go.Scatter(
|
156 |
+
x=betas,
|
157 |
+
y=z_mins,
|
158 |
+
mode="markers+lines",
|
159 |
+
name="Lower z*(β)",
|
160 |
+
marker=dict(size=5, color='lightblue'),
|
161 |
+
line=dict(color='lightblue'),
|
162 |
+
)
|
163 |
+
)
|
164 |
+
|
165 |
+
fig.add_trace(
|
166 |
+
go.Scatter(
|
167 |
+
x=betas,
|
168 |
+
y=low_y_curve,
|
169 |
+
mode="markers+lines",
|
170 |
+
name="Low y Expression",
|
171 |
+
marker=dict(size=5, color='red'),
|
172 |
+
line=dict(color='red'),
|
173 |
+
)
|
174 |
+
)
|
175 |
+
|
176 |
+
fig.add_trace(
|
177 |
+
go.Scatter(
|
178 |
+
x=betas,
|
179 |
+
y=high_y_curve,
|
180 |
+
mode="markers+lines",
|
181 |
+
name="High y Expression",
|
182 |
+
marker=dict(size=5, color='green'),
|
183 |
+
line=dict(color='green'),
|
184 |
+
)
|
185 |
+
)
|
186 |
+
|
187 |
+
fig.update_layout(
|
188 |
+
title="Curves vs β: z*(β) boundaries and Asymptotic Expressions",
|
189 |
+
xaxis_title="β",
|
190 |
+
yaxis_title="Value",
|
191 |
+
hovermode="x unified",
|
192 |
+
)
|
193 |
+
return fig
|
|
|
194 |
|
195 |
def compute_cubic_roots(z, beta, z_a, y):
|
196 |
+
"""
|
197 |
+
Compute the roots of the cubic equation for given parameters.
|
198 |
+
"""
|
199 |
+
a = z * z_a
|
200 |
+
b = z * z_a + z + z_a - z_a*y
|
201 |
+
c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
|
202 |
+
d = 1
|
203 |
+
|
204 |
+
coeffs = [a, b, c, d]
|
205 |
+
roots = np.roots(coeffs)
|
206 |
+
return roots
|
207 |
|
208 |
+
def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
|
209 |
+
"""Generate both Im(s) and Re(s) vs. z plots"""
|
210 |
+
if z_a <= 0 or y <= 0 or z_min >= z_max:
|
211 |
+
st.error("Invalid input parameters.")
|
212 |
+
return None, None
|
213 |
+
|
214 |
+
z_points = np.linspace(z_min, z_max, n_points)
|
215 |
+
ims = []
|
216 |
+
res = []
|
217 |
+
|
218 |
+
for z in z_points:
|
219 |
+
roots = compute_cubic_roots(z, beta, z_a, y)
|
220 |
+
roots = sorted(roots, key=lambda x: abs(x.imag))
|
221 |
+
ims.append([root.imag for root in roots])
|
222 |
+
res.append([root.real for root in roots])
|
223 |
+
|
224 |
+
ims = np.array(ims)
|
225 |
+
res = np.array(res)
|
226 |
+
|
227 |
+
# Create Im(s) plot
|
228 |
+
fig_im = go.Figure()
|
229 |
+
for i in range(3):
|
230 |
+
fig_im.add_trace(
|
231 |
+
go.Scatter(
|
232 |
+
x=z_points,
|
233 |
+
y=ims[:,i],
|
234 |
+
mode="lines",
|
235 |
+
name=f"Im{{s{i+1}}}",
|
236 |
+
line=dict(width=2),
|
237 |
+
)
|
238 |
+
)
|
239 |
+
fig_im.update_layout(
|
240 |
+
title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
|
241 |
+
xaxis_title="z",
|
242 |
+
yaxis_title="Im{s}",
|
243 |
+
hovermode="x unified",
|
244 |
+
)
|
245 |
+
|
246 |
+
# Create Re(s) plot
|
247 |
+
fig_re = go.Figure()
|
248 |
+
for i in range(3):
|
249 |
+
fig_re.add_trace(
|
250 |
+
go.Scatter(
|
251 |
+
x=z_points,
|
252 |
+
y=res[:,i],
|
253 |
+
mode="lines",
|
254 |
+
name=f"Re{{s{i+1}}}",
|
255 |
+
line=dict(width=2),
|
256 |
+
)
|
257 |
+
)
|
258 |
+
fig_re.update_layout(
|
259 |
+
title=f"Re{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
|
260 |
+
xaxis_title="z",
|
261 |
+
yaxis_title="Re{s}",
|
262 |
+
hovermode="x unified",
|
263 |
+
)
|
264 |
+
|
265 |
+
return fig_im, fig_re
|
266 |
|
267 |
def curve1(s, z, y):
|
268 |
+
"""First curve: z*s^2 + (z-y+1)*s + 1"""
|
269 |
+
return z*s**2 + (z-y+1)*s + 1
|
270 |
|
271 |
def curve2(s, y, beta, a):
|
272 |
+
"""Second curve: y*β*((a-1)*s)/(a*s+1)"""
|
273 |
+
return y*beta*((a-1)*s)/(a*s+1)
|
274 |
|
275 |
+
def find_intersections(z, y, beta, a, s_range, n_guesses, tolerance):
|
276 |
+
"""Find intersections between the two curves with improved accuracy"""
|
277 |
+
def equation(s):
|
278 |
+
return curve1(s, z, y) - curve2(s, y, beta, a)
|
279 |
+
|
280 |
+
# Create a finer grid of initial guesses
|
281 |
+
s_guesses = np.linspace(s_range[0], s_range[1], n_guesses)
|
282 |
+
intersections = []
|
283 |
+
|
284 |
+
# First pass: find all potential intersections
|
285 |
+
for s_guess in s_guesses:
|
286 |
+
try:
|
287 |
+
s_sol = fsolve(equation, s_guess, full_output=True, xtol=tolerance)
|
288 |
+
if s_sol[2] == 1: # Check if convergence was achieved
|
289 |
+
s_val = s_sol[0][0]
|
290 |
+
if (s_range[0] <= s_val <= s_range[1] and
|
291 |
+
not any(abs(s_val - s_prev) < tolerance for s_prev in intersections)):
|
292 |
+
if abs(equation(s_val)) < tolerance:
|
293 |
+
intersections.append(s_val)
|
294 |
+
except:
|
295 |
+
continue
|
296 |
+
|
297 |
+
# Sort intersections
|
298 |
+
intersections = np.sort(np.array(intersections))
|
299 |
+
|
300 |
+
# Ensure even number of intersections by checking for missed ones
|
301 |
+
if len(intersections) % 2 != 0:
|
302 |
+
refined_intersections = []
|
303 |
+
for i in range(len(intersections)-1):
|
304 |
+
mid_point = (intersections[i] + intersections[i+1])/2
|
305 |
+
try:
|
306 |
+
s_sol = fsolve(equation, mid_point, full_output=True, xtol=tolerance)
|
307 |
+
if s_sol[2] == 1:
|
308 |
+
s_val = s_sol[0][0]
|
309 |
+
if (intersections[i] < s_val < intersections[i+1] and
|
310 |
+
abs(equation(s_val)) < tolerance):
|
311 |
+
refined_intersections.append(s_val)
|
312 |
+
except:
|
313 |
+
continue
|
314 |
+
|
315 |
+
intersections = np.sort(np.append(intersections, refined_intersections))
|
316 |
+
|
317 |
+
return intersections
|
|
|
|
|
|
|
318 |
|
319 |
+
def generate_curves_plot(z, y, beta, a, s_range, n_points, n_guesses, tolerance):
|
320 |
+
s = np.linspace(s_range[0], s_range[1], n_points)
|
321 |
+
|
322 |
+
# Compute curves
|
323 |
+
y1 = curve1(s, z, y)
|
324 |
+
y2 = curve2(s, y, beta, a)
|
325 |
+
|
326 |
+
# Find intersections with improved accuracy
|
327 |
+
intersections = find_intersections(z, y, beta, a, s_range, n_guesses, tolerance)
|
328 |
+
|
329 |
+
fig = go.Figure()
|
330 |
+
|
331 |
+
fig.add_trace(
|
332 |
+
go.Scatter(
|
333 |
+
x=s, y=y1,
|
334 |
+
mode='lines',
|
335 |
+
name='z*s² + (z-y+1)*s + 1',
|
336 |
+
line=dict(color='blue', width=2)
|
337 |
+
)
|
338 |
+
)
|
339 |
+
|
340 |
+
fig.add_trace(
|
341 |
+
go.Scatter(
|
342 |
+
x=s, y=y2,
|
343 |
+
mode='lines',
|
344 |
+
name='y*β*((a-1)*s)/(a*s+1)',
|
345 |
+
line=dict(color='red', width=2)
|
346 |
+
)
|
347 |
+
)
|
348 |
+
|
349 |
+
if len(intersections) > 0:
|
350 |
+
fig.add_trace(
|
351 |
+
go.Scatter(
|
352 |
+
x=intersections,
|
353 |
+
y=curve1(intersections, z, y),
|
354 |
+
mode='markers',
|
355 |
+
name='Intersections',
|
356 |
+
marker=dict(
|
357 |
+
size=12,
|
358 |
+
color='green',
|
359 |
+
symbol='x',
|
360 |
+
line=dict(width=2)
|
361 |
+
)
|
362 |
+
)
|
363 |
+
)
|
364 |
+
|
365 |
+
fig.update_layout(
|
366 |
+
title=f"Curve Intersection Analysis (y={y:.4f}, β={beta:.4f}, a={a:.4f})",
|
367 |
+
xaxis_title="s",
|
368 |
+
yaxis_title="Value",
|
369 |
+
hovermode="closest",
|
370 |
+
showlegend=True,
|
371 |
+
legend=dict(
|
372 |
+
yanchor="top",
|
373 |
y=0.99,
|
374 |
xanchor="left",
|
375 |
x=0.01
|
|
|
394 |
z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
|
395 |
z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
|
396 |
|
397 |
+
with st.expander("Resolution Settings"):
|
398 |
+
beta_steps = st.slider("β steps", min_value=51, max_value=501, value=201, step=50)
|
399 |
+
z_steps = st.slider("z grid steps", min_value=1000, max_value=100000, value=50000, step=1000)
|
400 |
+
|
401 |
if st.button("Compute z vs. β Curves"):
|
402 |
with col2:
|
403 |
+
fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1, beta_steps, z_steps)
|
404 |
if fig is not None:
|
405 |
st.plotly_chart(fig, use_container_width=True)
|
406 |
|
|
|
431 |
z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_2")
|
432 |
z_max_2 = st.number_input("z_max", value=10.0, key="z_max_2")
|
433 |
|
434 |
+
with st.expander("Resolution Settings"):
|
435 |
+
z_points = st.slider("z grid points", min_value=1000, max_value=10000, value=5000, step=500)
|
436 |
+
|
437 |
if st.button("Compute Complex Roots vs. z"):
|
438 |
with col2:
|
439 |
+
fig_im, fig_re = generate_root_plots(beta, y_2, z_a_2, z_min_2, z_max_2, z_points)
|
440 |
if fig_im is not None and fig_re is not None:
|
441 |
st.plotly_chart(fig_im, use_container_width=True)
|
442 |
st.plotly_chart(fig_re, use_container_width=True)
|
|
|
452 |
beta_3 = st.slider("β", min_value=0.0, max_value=1.0, value=0.5, step=0.01, key="beta_3")
|
453 |
a = st.slider("a", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
|
454 |
|
|
|
455 |
st.subheader("s Range")
|
456 |
s_min = st.number_input("s_min", value=-5.0)
|
457 |
s_max = st.number_input("s_max", value=5.0)
|
458 |
|
459 |
+
with st.expander("Resolution Settings"):
|
460 |
+
s_points = st.slider("s grid points", min_value=1000, max_value=10000, value=5000, step=500)
|
461 |
+
intersection_guesses = st.slider("Intersection search points", min_value=200, max_value=2000, value=1000, step=100)
|
462 |
+
intersection_tolerance = st.select_slider(
|
463 |
+
"Intersection tolerance",
|
464 |
+
options=[1e-6, 1e-8, 1e-10, 1e-12, 1e-14],
|
465 |
+
value=1e-10
|
466 |
+
)
|
467 |
+
|
468 |
if st.button("Compute Intersections"):
|
469 |
with col2:
|
470 |
s_range = (s_min, s_max)
|
471 |
+
fig, intersections = generate_curves_plot(z, y_3, beta_3, a, s_range, s_points, intersection_guesses, intersection_tolerance)
|
472 |
st.plotly_chart(fig, use_container_width=True)
|
473 |
|
474 |
if len(intersections) > 0:
|