euler314 commited on
Commit
7bda4fb
·
verified ·
1 Parent(s): 80ed07e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -207
app.py CHANGED
@@ -5,9 +5,9 @@ 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 +25,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,189 +34,219 @@ 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_additional_curve(betas, z_a, y):
99
- """
100
- Compute the additional curve with proper handling of divide by zero cases
101
- """
102
- with np.errstate(invalid='ignore', divide='ignore'):
103
- sqrt_term = y * betas * (z_a - 1)
104
- sqrt_term = np.where(sqrt_term < 0, np.nan, np.sqrt(sqrt_term))
105
-
106
- term = (-1 + sqrt_term)/z_a
107
- numerator = (y - 2)*term + y * betas * ((z_a - 1)/z_a) - 1/z_a - 1
108
- denominator = term**2 + term
109
-
110
- mask = (denominator == 0) | np.isnan(denominator) | np.isnan(numerator)
111
- result = np.zeros_like(denominator)
112
- result[~mask] = numerator[~mask] / denominator[~mask]
113
- result[mask] = np.nan
114
-
115
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  def generate_z_vs_beta_plot(z_a, y, z_min, z_max):
118
- if z_a <= 0 or y <= 0 or z_min >= z_max:
119
- st.error("Invalid input parameters.")
120
- return None
121
-
122
- beta_steps = 101
123
-
124
- betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=beta_steps)
125
- new_curve = compute_additional_curve(betas, z_a, y)
126
-
127
- fig = go.Figure()
128
-
129
- fig.add_trace(
130
- go.Scatter(
131
- x=betas,
132
- y=z_maxs,
133
- mode="markers+lines",
134
- name="Upper z*(β)",
135
- marker=dict(size=5, color='blue'),
136
- line=dict(color='blue'),
137
- )
138
- )
139
-
140
- fig.add_trace(
141
- go.Scatter(
142
- x=betas,
143
- y=z_mins,
144
- mode="markers+lines",
145
- name="Lower z*(β)",
146
- marker=dict(size=5, color='lightblue'),
147
- line=dict(color='lightblue'),
148
- )
149
- )
150
-
151
- fig.add_trace(
152
- go.Scatter(
153
- x=betas,
154
- y=new_curve,
155
- mode="markers+lines",
156
- name="Additional Expression",
157
- marker=dict(size=5, color='red'),
158
- line=dict(color='red'),
159
- )
160
- )
161
-
162
- fig.update_layout(
163
- title="Curves vs β: z*(β) boundaries (blue) and Additional Expression (red)",
164
- xaxis_title="β",
165
- yaxis_title="Value",
166
- hovermode="x unified",
167
- )
168
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  @st.cache_data
171
  def compute_cubic_roots(z, beta, z_a, y):
172
- """
173
- Compute the roots of the cubic equation for given parameters.
174
- Returns array of complex roots.
175
- """
176
- a = z * z_a
177
- b = z * z_a + z + z_a - z_a*y # Fixed coefficient b
178
- c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
179
- d = 1
180
-
181
- coeffs = [a, b, c, d]
182
- roots = np.roots(coeffs)
183
- return roots
184
 
185
  def generate_ims_vs_z_plot(beta, y, z_a, z_min, z_max):
186
- if z_a <= 0 or y <= 0 or z_min >= z_max:
187
- st.error("Invalid input parameters.")
188
- return None
189
-
190
- z_points = np.linspace(z_min, z_max, 1000)
191
- ims = []
192
-
193
- for z in z_points:
194
- roots = compute_cubic_roots(z, beta, z_a, y)
195
- roots = sorted(roots, key=lambda x: abs(x.imag))
196
- ims.append([root.imag for root in roots])
197
-
198
- ims = np.array(ims)
199
-
200
- fig = go.Figure()
201
-
202
- for i in range(3):
203
- fig.add_trace(
204
- go.Scatter(
205
- x=z_points,
206
- y=ims[:,i],
207
- mode="lines",
208
- name=f"Im{{s{i+1}}}",
209
- line=dict(width=2),
210
- )
211
- )
212
-
213
- fig.update_layout(
214
- title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
215
- xaxis_title="z",
216
- yaxis_title="Im{s}",
217
- hovermode="x unified",
218
- )
219
- return fig
220
 
221
  # Streamlit UI
222
  st.title("Cubic Root Analysis")
@@ -224,36 +254,51 @@ st.title("Cubic Root Analysis")
224
  tab1, tab2 = st.tabs(["z*(β) Curves", "Im{s} vs. z"])
225
 
226
  with tab1:
227
- st.header("Find z Values where Cubic Roots Transition Between Real and Complex")
228
-
229
- col1, col2 = st.columns([1, 2])
230
-
231
- with col1:
232
- z_a_1 = st.number_input("z_a", value=1.0, key="z_a_1")
233
- y_1 = st.number_input("y", value=1.0, key="y_1")
234
- z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
235
- z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
236
-
237
- if st.button("Compute z vs. β Curves"):
238
- with col2:
239
- fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
240
- if fig is not None:
241
- st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
  with tab2:
244
- st.header("Plot Imaginary Parts of Roots vs. z")
245
-
246
- col1, col2 = st.columns([1, 2])
247
-
248
- with col1:
249
- beta = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0)
250
- y_2 = st.number_input("y", value=1.0, key="y_2")
251
- z_a_2 = st.number_input("z_a", value=1.0, key="z_a_2")
252
- z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_2")
253
- z_max_2 = st.number_input("z_max", value=10.0, key="z_max_2")
254
-
255
- if st.button("Compute Im{s} vs. z"):
256
- with col2:
257
- fig = generate_ims_vs_z_plot(beta, y_2, z_a_2, z_min_2, z_max_2)
258
- if fig is not None:
259
- st.plotly_chart(fig, use_container_width=True)
 
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
 
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
 
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
+ with np.errstate(invalid='ignore', divide='ignore'):
103
+ sqrt_term = y * betas * (z_a - 1)
104
+ sqrt_term = np.where(sqrt_term < 0, np.nan, np.sqrt(sqrt_term))
105
+
106
+ term = (-1 + sqrt_term)/z_a
107
+ numerator = (y - 2)*term + y * betas * ((z_a - 1)/z_a) - 1/z_a - 1
108
+ denominator = term**2 + term
109
+
110
+ mask = (denominator == 0) | np.isnan(denominator) | np.isnan(numerator)
111
+ result = np.zeros_like(denominator)
112
+ result[~mask] = numerator[~mask] / denominator[~mask]
113
+ result[mask] = np.nan
114
+
115
+ return result
116
+
117
+ @st.cache_data
118
+ def compute_high_y_curve(betas, z_a, y):
119
+ """
120
+ Compute the expression: ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
121
+ """
122
+ a = z_a # for clarity in the formula
123
+ with np.errstate(invalid='ignore', divide='ignore'):
124
+ numerator = (4*y + 12)*(4 - a) + 16*y*betas*(a - 1)
125
+ denominator = 3*(4 - a)
126
+
127
+ # Handle division by zero
128
+ mask = (denominator == 0)
129
+ result = np.zeros_like(denominator, dtype=float)
130
+ result[~mask] = numerator[~mask] / denominator[~mask]
131
+ result[mask] = np.nan
132
+
133
+ return result
134
 
135
  def generate_z_vs_beta_plot(z_a, y, z_min, z_max):
136
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
137
+ st.error("Invalid input parameters.")
138
+ return None
139
+
140
+ beta_steps = 101
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
+ fig.add_trace(
149
+ go.Scatter(
150
+ x=betas,
151
+ y=z_maxs,
152
+ mode="markers+lines",
153
+ name="Upper z*(β)",
154
+ marker=dict(size=5, color='blue'),
155
+ line=dict(color='blue'),
156
+ )
157
+ )
158
+
159
+ fig.add_trace(
160
+ go.Scatter(
161
+ x=betas,
162
+ y=z_mins,
163
+ mode="markers+lines",
164
+ name="Lower z*(β)",
165
+ marker=dict(size=5, color='lightblue'),
166
+ line=dict(color='lightblue'),
167
+ )
168
+ )
169
+
170
+ fig.add_trace(
171
+ go.Scatter(
172
+ x=betas,
173
+ y=low_y_curve,
174
+ mode="markers+lines",
175
+ name="Low y Expression",
176
+ marker=dict(size=5, color='red'),
177
+ line=dict(color='red'),
178
+ )
179
+ )
180
+
181
+ fig.add_trace(
182
+ go.Scatter(
183
+ x=betas,
184
+ y=high_y_curve,
185
+ mode="markers+lines",
186
+ name="High y Expression",
187
+ marker=dict(size=5, color='green'),
188
+ line=dict(color='green'),
189
+ )
190
+ )
191
+
192
+ fig.update_layout(
193
+ title="Curves vs β: z*(β) boundaries and Asymptotic Expressions",
194
+ xaxis_title="β",
195
+ yaxis_title="Value",
196
+ hovermode="x unified",
197
+ )
198
+ return fig
199
 
200
  @st.cache_data
201
  def compute_cubic_roots(z, beta, z_a, y):
202
+ """
203
+ Compute the roots of the cubic equation for given parameters.
204
+ Returns array of complex roots.
205
+ """
206
+ a = z * z_a
207
+ b = z * z_a + z + z_a - z_a*y # Fixed coefficient b
208
+ c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
209
+ d = 1
210
+
211
+ coeffs = [a, b, c, d]
212
+ roots = np.roots(coeffs)
213
+ return roots
214
 
215
  def generate_ims_vs_z_plot(beta, y, z_a, z_min, z_max):
216
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
217
+ st.error("Invalid input parameters.")
218
+ return None
219
+
220
+ z_points = np.linspace(z_min, z_max, 1000)
221
+ ims = []
222
+
223
+ for z in z_points:
224
+ roots = compute_cubic_roots(z, beta, z_a, y)
225
+ roots = sorted(roots, key=lambda x: abs(x.imag))
226
+ ims.append([root.imag for root in roots])
227
+
228
+ ims = np.array(ims)
229
+
230
+ fig = go.Figure()
231
+
232
+ for i in range(3):
233
+ fig.add_trace(
234
+ go.Scatter(
235
+ x=z_points,
236
+ y=ims[:,i],
237
+ mode="lines",
238
+ name=f"Im{{s{i+1}}}",
239
+ line=dict(width=2),
240
+ )
241
+ )
242
+
243
+ fig.update_layout(
244
+ title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
245
+ xaxis_title="z",
246
+ yaxis_title="Im{s}",
247
+ hovermode="x unified",
248
+ )
249
+ return fig
250
 
251
  # Streamlit UI
252
  st.title("Cubic Root Analysis")
 
254
  tab1, tab2 = st.tabs(["z*(β) Curves", "Im{s} vs. z"])
255
 
256
  with tab1:
257
+ st.header("Find z Values where Cubic Roots Transition Between Real and Complex")
258
+
259
+ col1, col2 = st.columns([1, 2])
260
+
261
+ with col1:
262
+ z_a_1 = st.number_input("z_a", value=1.0, key="z_a_1")
263
+ y_1 = st.number_input("y", value=1.0, key="y_1")
264
+ z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
265
+ z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
266
+
267
+ if st.button("Compute z vs. β Curves"):
268
+ with col2:
269
+ fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
270
+ if fig is not None:
271
+ st.plotly_chart(fig, use_container_width=True)
272
+
273
+ st.markdown("### Additional Expressions")
274
+ st.markdown("""
275
+ **Low y Expression (Red):**
276
+ ```
277
+ ((y - 2)*(-1 + sqrt(y*β*(a-1)))/a + y*β*((a-1)/a) - 1/a - 1) /
278
+ ((-1 + sqrt(y*β*(a-1)))/a)^2 + (-1 + sqrt(y*β*(a-1)))/a)
279
+ ```
280
+
281
+ **High y Expression (Green):**
282
+ ```
283
+ ((4y + 12)(4 - a) + 16y*β*(a - 1))/(3(4 - a))
284
+ ```
285
+ where a = z_a
286
+ """)
287
 
288
  with tab2:
289
+ st.header("Plot Imaginary Parts of Roots vs. z")
290
+
291
+ col1, col2 = st.columns([1, 2])
292
+
293
+ with col1:
294
+ beta = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0)
295
+ y_2 = st.number_input("y", value=1.0, key="y_2")
296
+ z_a_2 = st.number_input("z_a", value=1.0, key="z_a_2")
297
+ z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_2")
298
+ z_max_2 = st.number_input("z_max", value=10.0, key="z_max_2")
299
+
300
+ if st.button("Compute Im{s} vs. z"):
301
+ with col2:
302
+ fig = generate_ims_vs_z_plot(beta, y_2, z_a_2, z_min_2, z_max_2)
303
+ if fig is not None:
304
+ st.plotly_chart(fig, use_container_width=True)