euler314 commited on
Commit
2690664
·
verified ·
1 Parent(s): ef6361a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +342 -331
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
- page_title="Cubic Root Analysis",
10
- layout="wide",
11
- initial_sidebar_state="collapsed"
12
  )
13
 
14
  #############################
@@ -26,354 +26,350 @@ d_sym = 1
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=20000):
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=51):
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)
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):
131
- if z_a <= 0 or y <= 0 or z_min >= z_max:
132
- st.error("Invalid input parameters.")
133
- return None
134
-
135
- beta_steps = 101
136
- betas = np.linspace(0, 1, beta_steps)
137
-
138
- betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=beta_steps)
139
- low_y_curve = compute_low_y_curve(betas, z_a, y)
140
- high_y_curve = compute_high_y_curve(betas, z_a, y)
141
-
142
- fig = go.Figure()
143
-
144
- fig.add_trace(
145
- go.Scatter(
146
- x=betas,
147
- y=z_maxs,
148
- mode="markers+lines",
149
- name="Upper z*(β)",
150
- marker=dict(size=5, color='blue'),
151
- line=dict(color='blue'),
152
- )
153
- )
154
-
155
- fig.add_trace(
156
- go.Scatter(
157
- x=betas,
158
- y=z_mins,
159
- mode="markers+lines",
160
- name="Lower z*(β)",
161
- marker=dict(size=5, color='lightblue'),
162
- line=dict(color='lightblue'),
163
- )
164
- )
165
-
166
- fig.add_trace(
167
- go.Scatter(
168
- x=betas,
169
- y=low_y_curve,
170
- mode="markers+lines",
171
- name="Low y Expression",
172
- marker=dict(size=5, color='red'),
173
- line=dict(color='red'),
174
- )
175
- )
176
-
177
- fig.add_trace(
178
- go.Scatter(
179
- x=betas,
180
- y=high_y_curve,
181
- mode="markers+lines",
182
- name="High y Expression",
183
- marker=dict(size=5, color='green'),
184
- line=dict(color='green'),
185
- )
186
- )
187
-
188
- fig.update_layout(
189
- title="Curves vs β: z*(β) boundaries and Asymptotic Expressions",
190
- xaxis_title="β",
191
- yaxis_title="Value",
192
- hovermode="x unified",
193
- )
194
- return fig
195
 
196
  def compute_cubic_roots(z, beta, z_a, y):
197
- """
198
- Compute the roots of the cubic equation for given parameters.
199
- """
200
- a = z * z_a
201
- b = z * z_a + z + z_a - z_a*y
202
- c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
203
- d = 1
204
-
205
- coeffs = [a, b, c, d]
206
- roots = np.roots(coeffs)
207
- return roots
208
 
209
- def generate_root_plots(beta, y, z_a, z_min, z_max):
210
- """Generate both Im(s) and Re(s) vs. z plots"""
211
- if z_a <= 0 or y <= 0 or z_min >= z_max:
212
- st.error("Invalid input parameters.")
213
- return None, None
214
-
215
- z_points = np.linspace(z_min, z_max, 1000)
216
- ims = []
217
- res = []
218
-
219
- for z in z_points:
220
- roots = compute_cubic_roots(z, beta, z_a, y)
221
- roots = sorted(roots, key=lambda x: abs(x.imag))
222
- ims.append([root.imag for root in roots])
223
- res.append([root.real for root in roots])
224
-
225
- ims = np.array(ims)
226
- res = np.array(res)
227
-
228
- # Create Im(s) plot
229
- fig_im = go.Figure()
230
- for i in range(3):
231
- fig_im.add_trace(
232
- go.Scatter(
233
- x=z_points,
234
- y=ims[:,i],
235
- mode="lines",
236
- name=f"Im{{s{i+1}}}",
237
- line=dict(width=2),
238
- )
239
- )
240
- fig_im.update_layout(
241
- title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
242
- xaxis_title="z",
243
- yaxis_title="Im{s}",
244
- hovermode="x unified",
245
- )
246
-
247
- # Create Re(s) plot
248
- fig_re = go.Figure()
249
- for i in range(3):
250
- fig_re.add_trace(
251
- go.Scatter(
252
- x=z_points,
253
- y=res[:,i],
254
- mode="lines",
255
- name=f"Re{{s{i+1}}}",
256
- line=dict(width=2),
257
- )
258
- )
259
- fig_re.update_layout(
260
- title=f"Re{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
261
- xaxis_title="z",
262
- yaxis_title="Re{s}",
263
- hovermode="x unified",
264
- )
265
-
266
- return fig_im, fig_re
267
 
268
  def curve1(s, z, y):
269
- """First curve: z*s^2 + (z-y+1)*s + 1"""
270
- return z*s**2 + (z-y+1)*s + 1
271
 
272
  def curve2(s, y, beta, a):
273
- """Second curve: y*β*((a-1)*s)/(a*s+1)"""
274
- return y*beta*((a-1)*s)/(a*s+1)
275
 
276
- def find_intersections(z, y, beta, a, s_range):
277
- """Find intersections between the two curves with improved accuracy"""
278
- def equation(s):
279
- return curve1(s, z, y) - curve2(s, y, beta, a)
280
-
281
- # Create a finer grid of initial guesses
282
- s_guesses = np.linspace(s_range[0], s_range[1], 200)
283
- intersections = []
284
-
285
- # Parameters for accuracy
286
- tolerance = 1e-10
287
-
288
- # First pass: find all potential intersections
289
- for s_guess in s_guesses:
290
- try:
291
- s_sol = fsolve(equation, s_guess, full_output=True, xtol=tolerance)
292
- if s_sol[2] == 1: # Check if convergence was achieved
293
- s_val = s_sol[0][0]
294
- if (s_range[0] <= s_val <= s_range[1] and
295
- not any(abs(s_val - s_prev) < tolerance for s_prev in intersections)):
296
- if abs(equation(s_val)) < tolerance:
297
- intersections.append(s_val)
298
- except:
299
- continue
300
-
301
- # Sort intersections
302
- intersections = np.sort(np.array(intersections))
303
-
304
- # Ensure even number of intersections by checking for missed ones
305
- if len(intersections) % 2 != 0:
306
- refined_intersections = []
307
- for i in range(len(intersections)-1):
308
- mid_point = (intersections[i] + intersections[i+1])/2
309
- try:
310
- s_sol = fsolve(equation, mid_point, full_output=True, xtol=tolerance)
311
- if s_sol[2] == 1:
312
- s_val = s_sol[0][0]
313
- if (intersections[i] < s_val < intersections[i+1] and
314
- abs(equation(s_val)) < tolerance):
315
- refined_intersections.append(s_val)
316
- except:
317
- continue
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
- s = np.linspace(s_range[0], s_range[1], 2000)
325
-
326
- # Compute curves
327
- y1 = curve1(s, z, y)
328
- y2 = curve2(s, y, beta, a)
329
-
330
- # Find intersections with improved accuracy
331
- intersections = find_intersections(z, y, beta, a, s_range)
332
-
333
- fig = go.Figure()
334
-
335
- fig.add_trace(
336
- go.Scatter(
337
- x=s, y=y1,
338
- mode='lines',
339
- name='z*s² + (z-y+1)*s + 1',
340
- line=dict(color='blue', width=2)
341
- )
342
- )
343
-
344
- fig.add_trace(
345
- go.Scatter(
346
- x=s, y=y2,
347
- mode='lines',
348
- name='y*β*((a-1)*s)/(a*s+1)',
349
- line=dict(color='red', width=2)
350
- )
351
- )
352
-
353
- if len(intersections) > 0:
354
- fig.add_trace(
355
- go.Scatter(
356
- x=intersections,
357
- y=curve1(intersections, z, y),
358
- mode='markers',
359
- name='Intersections',
360
- marker=dict(
361
- size=12,
362
- color='green',
363
- symbol='x',
364
- line=dict(width=2)
365
- )
366
- )
367
- )
368
-
369
- fig.update_layout(
370
- title=f"Curve Intersection Analysis (y={y:.4f}, β={beta:.4f}, a={a:.4f})",
371
- xaxis_title="s",
372
- yaxis_title="Value",
373
- hovermode="closest",
374
- showlegend=True,
375
- legend=dict(
376
- yanchor="top",
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: