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

Update app.py

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