euler314 commited on
Commit
a76f82e
·
verified ·
1 Parent(s): 69d5d64

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +346 -237
app.py CHANGED
@@ -2,12 +2,13 @@ import streamlit as st
2
  import sympy as sp
3
  import numpy as np
4
  import plotly.graph_objects as go
 
5
 
6
  # Configure Streamlit for Hugging Face Spaces
7
  st.set_page_config(
8
- page_title="Cubic Root Analysis",
9
- layout="wide",
10
- initial_sidebar_state="collapsed"
11
  )
12
 
13
  #############################
@@ -25,8 +26,8 @@ d_sym = 1
25
 
26
  # Symbolic expression for the standard cubic discriminant
27
  Delta_expr = (
28
- ( (b_sym*c_sym)/(6*a_sym**2) - (b_sym**3)/(27*a_sym**3) - d_sym/(2*a_sym) )**2
29
- + ( c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2) )**3
30
  )
31
 
32
  # Turn that into a fast numeric function:
@@ -34,258 +35,335 @@ discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "
34
 
35
  @st.cache_data
36
  def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps=20000):
37
- """
38
- Numerically scan z in [z_min, z_max] looking for sign changes of
39
- Delta(z) = 0. Returns all roots found via bisection.
40
- """
41
- z_grid = np.linspace(z_min, z_max, steps)
42
- disc_vals = discriminant_func(z_grid, beta, z_a, y)
43
-
44
- roots_found = []
45
-
46
- # Scan for sign changes
47
- for i in range(len(z_grid) - 1):
48
- f1, f2 = disc_vals[i], disc_vals[i+1]
49
- if np.isnan(f1) or np.isnan(f2):
50
- continue
51
-
52
- if f1 == 0.0:
53
- roots_found.append(z_grid[i])
54
- elif f2 == 0.0:
55
- roots_found.append(z_grid[i+1])
56
- elif f1*f2 < 0:
57
- zl = z_grid[i]
58
- zr = z_grid[i+1]
59
- for _ in range(50):
60
- mid = 0.5*(zl + zr)
61
- fm = discriminant_func(mid, beta, z_a, y)
62
- if fm == 0:
63
- zl = zr = mid
64
- break
65
- if np.sign(fm) == np.sign(f1):
66
- zl = mid
67
- f1 = fm
68
- else:
69
- zr = mid
70
- f2 = fm
71
- root_approx = 0.5*(zl + zr)
72
- roots_found.append(root_approx)
73
-
74
- return np.array(roots_found)
75
 
76
  @st.cache_data
77
  def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=51):
78
- """
79
- For each beta, find both the largest and smallest z where discriminant=0.
80
- Returns (betas, z_min_values, z_max_values).
81
- """
82
- betas = np.linspace(0, 1, beta_steps)
83
- z_min_values = []
84
- z_max_values = []
85
-
86
- for b in betas:
87
- roots = find_z_at_discriminant_zero(z_a, y, b, z_min, z_max)
88
- if len(roots) == 0:
89
- z_min_values.append(np.nan)
90
- z_max_values.append(np.nan)
91
- else:
92
- z_min_values.append(np.min(roots))
93
- z_max_values.append(np.max(roots))
94
-
95
- return betas, np.array(z_min_values), np.array(z_max_values)
96
 
97
  @st.cache_data
98
  def compute_low_y_curve(betas, z_a, y):
99
- """
100
- Compute the additional curve with proper handling of divide by zero cases
101
- """
102
- betas = np.array(betas)
103
- with np.errstate(invalid='ignore', divide='ignore'):
104
- sqrt_term = y * betas * (z_a - 1)
105
- sqrt_term = np.where(sqrt_term < 0, np.nan, np.sqrt(sqrt_term))
106
-
107
- term = (-1 + sqrt_term)/z_a
108
- numerator = (y - 2)*term + y * betas * ((z_a - 1)/z_a) - 1/z_a - 1
109
- denominator = term**2 + term
110
-
111
- # Handle division by zero and invalid values
112
- result = np.zeros_like(betas)
113
- mask = (denominator != 0) & ~np.isnan(denominator) & ~np.isnan(numerator)
114
- result[mask] = numerator[mask] / denominator[mask]
115
- result[~mask] = np.nan
116
-
117
- return result
118
 
119
  @st.cache_data
120
  def compute_high_y_curve(betas, z_a, y):
121
- """
122
- Compute the expression: ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
123
- """
124
- a = z_a # for clarity in the formula
125
- betas = np.array(betas)
126
-
127
- denominator = 3*(4 - a)
128
- if denominator == 0:
129
- return np.full_like(betas, np.nan)
130
-
131
- numerator = (4*y + 12)*(4 - a) + 16*y*betas*(a - 1)
132
- return numerator/denominator
133
 
134
  def generate_z_vs_beta_plot(z_a, y, z_min, z_max):
135
- if z_a <= 0 or y <= 0 or z_min >= z_max:
136
- st.error("Invalid input parameters.")
137
- return None
138
-
139
- beta_steps = 101
140
- betas = np.linspace(0, 1, beta_steps)
141
-
142
- betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=beta_steps)
143
- low_y_curve = compute_low_y_curve(betas, z_a, y)
144
- high_y_curve = compute_high_y_curve(betas, z_a, y)
145
-
146
- fig = go.Figure()
147
-
148
- # Upper and lower z*(β) boundaries
149
- fig.add_trace(
150
- go.Scatter(
151
- x=betas,
152
- y=z_maxs,
153
- mode="markers+lines",
154
- name="Upper z*(β)",
155
- marker=dict(size=5, color='blue'),
156
- line=dict(color='blue'),
157
- )
158
- )
159
-
160
- fig.add_trace(
161
- go.Scatter(
162
- x=betas,
163
- y=z_mins,
164
- mode="markers+lines",
165
- name="Lower z*(β)",
166
- marker=dict(size=5, color='lightblue'),
167
- line=dict(color='lightblue'),
168
- )
169
- )
170
-
171
- # Asymptotic expressions
172
- fig.add_trace(
173
- go.Scatter(
174
- x=betas,
175
- y=low_y_curve,
176
- mode="markers+lines",
177
- name="Low y Expression",
178
- marker=dict(size=5, color='red'),
179
- line=dict(color='red'),
180
- )
181
- )
182
-
183
- fig.add_trace(
184
- go.Scatter(
185
- x=betas,
186
- y=high_y_curve,
187
- mode="markers+lines",
188
- name="High y Expression",
189
- marker=dict(size=5, color='green'),
190
- line=dict(color='green'),
191
- )
192
- )
193
-
194
- fig.update_layout(
195
- title="Curves vs β: z*(β) boundaries and Asymptotic Expressions",
196
- xaxis_title="β",
197
- yaxis_title="Value",
198
- hovermode="x unified",
199
- )
200
- return fig
201
 
202
- @st.cache_data
203
  def compute_cubic_roots(z, beta, z_a, y):
204
- """
205
- Compute the roots of the cubic equation for given parameters.
206
- Returns array of complex roots.
207
- """
208
- a = z * z_a
209
- b = z * z_a + z + z_a - z_a*y # Fixed coefficient b
210
- c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
211
- d = 1
212
-
213
- coeffs = [a, b, c, d]
214
- roots = np.roots(coeffs)
215
- return roots
216
 
217
  def generate_ims_vs_z_plot(beta, y, z_a, z_min, z_max):
218
- if z_a <= 0 or y <= 0 or z_min >= z_max:
219
- st.error("Invalid input parameters.")
220
- return None
221
-
222
- z_points = np.linspace(z_min, z_max, 1000)
223
- ims = []
224
-
225
- for z in z_points:
226
- roots = compute_cubic_roots(z, beta, z_a, y)
227
- roots = sorted(roots, key=lambda x: abs(x.imag))
228
- ims.append([root.imag for root in roots])
229
-
230
- ims = np.array(ims)
231
-
232
- fig = go.Figure()
233
-
234
- for i in range(3):
235
- fig.add_trace(
236
- go.Scatter(
237
- x=z_points,
238
- y=ims[:,i],
239
- mode="lines",
240
- name=f"Im{{s{i+1}}}",
241
- line=dict(width=2),
242
- )
243
- )
244
-
245
- fig.update_layout(
246
- title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
247
- xaxis_title="z",
248
- yaxis_title="Im{s}",
249
- hovermode="x unified",
250
- )
251
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
  # Streamlit UI
254
  st.title("Cubic Root Analysis")
255
 
256
- tab1, tab2 = st.tabs(["z*(β) Curves", "Im{s} vs. z"])
257
 
258
  with tab1:
259
- st.header("Find z Values where Cubic Roots Transition Between Real and Complex")
260
-
261
- col1, col2 = st.columns([1, 2])
262
-
263
- with col1:
264
- z_a_1 = st.number_input("z_a", value=1.0, key="z_a_1")
265
- y_1 = st.number_input("y", value=1.0, key="y_1")
266
- z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
267
- z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
268
-
269
- if st.button("Compute z vs. β Curves"):
270
- with col2:
271
- fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
272
- if fig is not None:
273
- st.plotly_chart(fig, use_container_width=True)
274
-
275
- st.markdown("### Additional Expressions")
276
- st.markdown("""
277
- **Low y Expression (Red):**
278
- ```
279
- ((y - 2)*(-1 + sqrt(y*β*(a-1)))/a + y*β*((a-1)/a) - 1/a - 1) /
280
- ((-1 + sqrt(y*β*(a-1)))/a)^2 + (-1 + sqrt(y*β*(a-1)))/a)
281
- ```
282
-
283
- **High y Expression (Green):**
284
- ```
285
- ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
286
- ```
287
- where a = z_a
288
- """)
289
 
290
  with tab2:
291
  st.header("Plot Imaginary Parts of Roots vs. z")
@@ -303,4 +381,35 @@ with tab2:
303
  with col2:
304
  fig = generate_ims_vs_z_plot(beta, y_2, z_a_2, z_min_2, z_max_2)
305
  if fig is not None:
306
- st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import sympy as sp
3
  import numpy as np
4
  import plotly.graph_objects as go
5
+ 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
 
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:
 
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
+ Returns array of complex roots.
200
+ """
201
+ a = z * z_a
202
+ b = z * z_a + z + z_a - z_a*y
203
+ c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
204
+ d = 1
205
+
206
+ coeffs = [a, b, c, d]
207
+ roots = np.roots(coeffs)
208
+ return roots
209
 
210
  def generate_ims_vs_z_plot(beta, y, z_a, z_min, z_max):
211
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
212
+ st.error("Invalid input parameters.")
213
+ return None
214
+
215
+ z_points = np.linspace(z_min, z_max, 1000)
216
+ ims = []
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
+
223
+ ims = np.array(ims)
224
+
225
+ fig = go.Figure()
226
+
227
+ for i in range(3):
228
+ fig.add_trace(
229
+ go.Scatter(
230
+ x=z_points,
231
+ y=ims[:,i],
232
+ mode="lines",
233
+ name=f"Im{{s{i+1}}}",
234
+ line=dict(width=2),
235
+ )
236
+ )
237
+
238
+ fig.update_layout(
239
+ title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
240
+ xaxis_title="z",
241
+ yaxis_title="Im{s}",
242
+ hovermode="x unified",
243
+ )
244
+ return fig
245
+
246
+ def curve1(s, z, y):
247
+ """First curve: z*s^2 + (z-y+1)*s + 1"""
248
+ return z*s**2 + (z-y+1)*s + 1
249
+
250
+ def curve2(s, y, beta, a):
251
+ """Second curve: y*β*((a-1)*s)/(a*s+1)"""
252
+ return y*beta*((a-1)*s)/(a*s+1)
253
+
254
+ def find_intersections(z, y, beta, a, s_range):
255
+ """Find intersections between the two curves"""
256
+ def equation(s):
257
+ return curve1(s, z, y) - curve2(s, y, beta, a)
258
+
259
+ # Create grid of initial guesses
260
+ s_guesses = np.linspace(s_range[0], s_range[1], 20)
261
+ intersections = []
262
+
263
+ for s_guess in s_guesses:
264
+ try:
265
+ s_sol = fsolve(equation, s_guess)[0]
266
+ # Check if solution is within range and not already found
267
+ if (s_range[0] <= s_sol <= s_range[1] and
268
+ not any(abs(s_sol - s_prev) < 1e-6 for s_prev in intersections)):
269
+ intersections.append(s_sol)
270
+ except:
271
+ continue
272
+
273
+ return np.array(intersections)
274
+
275
+ def generate_curves_plot(z, y, beta, a, s_range):
276
+ s = np.linspace(s_range[0], s_range[1], 1000)
277
+
278
+ # Compute curves
279
+ y1 = curve1(s, z, y)
280
+ y2 = curve2(s, y, beta, a)
281
+
282
+ # Find intersections
283
+ intersections = find_intersections(z, y, beta, a, s_range)
284
+
285
+ fig = go.Figure()
286
+
287
+ # Plot curves
288
+ fig.add_trace(
289
+ go.Scatter(
290
+ x=s, y=y1,
291
+ mode='lines',
292
+ name='z*s² + (z-y+1)*s + 1',
293
+ line=dict(color='blue')
294
+ )
295
+ )
296
+
297
+ fig.add_trace(
298
+ go.Scatter(
299
+ x=s, y=y2,
300
+ mode='lines',
301
+ name='y*β*((a-1)*s)/(a*s+1)',
302
+ line=dict(color='red')
303
+ )
304
+ )
305
+
306
+ # Plot intersections
307
+ if len(intersections) > 0:
308
+ fig.add_trace(
309
+ go.Scatter(
310
+ x=intersections,
311
+ y=curve1(intersections, z, y),
312
+ mode='markers',
313
+ name='Intersections',
314
+ marker=dict(
315
+ size=10,
316
+ color='green',
317
+ symbol='x'
318
+ )
319
+ )
320
+ )
321
+
322
+ fig.update_layout(
323
+ title=f"Curve Intersection Analysis (y={y:.2f}, β={beta:.2f}, a={a:.2f})",
324
+ xaxis_title="s",
325
+ yaxis_title="Value",
326
+ hovermode="closest"
327
+ )
328
+
329
+ return fig, intersections
330
 
331
  # Streamlit UI
332
  st.title("Cubic Root Analysis")
333
 
334
+ tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Im{s} vs. z", "Curve Intersections"])
335
 
336
  with tab1:
337
+ st.header("Find z Values where Cubic Roots Transition Between Real and Complex")
338
+
339
+ col1, col2 = st.columns([1, 2])
340
+
341
+ with col1:
342
+ z_a_1 = st.number_input("z_a", value=1.0, key="z_a_1")
343
+ y_1 = st.number_input("y", value=1.0, key="y_1")
344
+ z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
345
+ z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
346
+
347
+ if st.button("Compute z vs. β Curves"):
348
+ with col2:
349
+ fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
350
+ if fig is not None:
351
+ st.plotly_chart(fig, use_container_width=True)
352
+
353
+ st.markdown("### Additional Expressions")
354
+ st.markdown("""
355
+ **Low y Expression (Red):**
356
+ ```
357
+ ((y - 2)*(-1 + sqrt(y*β*(a-1)))/a + y*β*((a-1)/a) - 1/a - 1) /
358
+ ((-1 + sqrt(y*β*(a-1)))/a)^2 + (-1 + sqrt(y*β*(a-1)))/a)
359
+ ```
360
+
361
+ **High y Expression (Green):**
362
+ ```
363
+ ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
364
+ ```
365
+ where a = z_a
366
+ """)
367
 
368
  with tab2:
369
  st.header("Plot Imaginary Parts of Roots vs. z")
 
381
  with col2:
382
  fig = generate_ims_vs_z_plot(beta, y_2, z_a_2, z_min_2, z_max_2)
383
  if fig is not None:
384
+ st.plotly_chart(fig, use_container_width=True)
385
+
386
+ with tab3:
387
+ st.header("Curve Intersection Analysis")
388
+
389
+ col1, col2 = st.columns([1, 2])
390
+
391
+ with col1:
392
+ # Add sliders for parameters
393
+ z = st.slider("z", min_value=-10.0, max_value=10.0, value=1.0, step=0.1)
394
+ y_3 = st.slider("y", min_value=0.1, max_value=10.0, value=1.0, step=0.1, key="y_3")
395
+ beta_3 = st.slider("β", min_value=0.0, max_value=1.0, value=0.5, step=0.01, key="beta_3")
396
+ a = st.slider("a", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
397
+
398
+ # Add range inputs for s
399
+ st.subheader("s Range")
400
+ s_min = st.number_input("s_min", value=-5.0)
401
+ s_max = st.number_input("s_max", value=5.0)
402
+
403
+ if st.button("Compute Intersections"):
404
+ with col2:
405
+ s_range = (s_min, s_max)
406
+ fig, intersections = generate_curves_plot(z, y_3, beta_3, a, s_range)
407
+ st.plotly_chart(fig, use_container_width=True)
408
+
409
+ if len(intersections) > 0:
410
+ st.subheader("Intersection Points")
411
+ for i, s_val in enumerate(intersections):
412
+ y_val = curve1(s_val, z, y_3)
413
+ st.write(f"Point {i+1}: s = {s_val:.4f}, y = {y_val:.4f}")
414
+ else:
415
+ st.write("No intersections found in the given range.")