euler314 commited on
Commit
dd5ca7d
·
verified ·
1 Parent(s): 7faea95

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -0
app.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import sympy as sp
3
+ import numpy as np
4
+ import plotly.graph_objects as go
5
+
6
+ #############################
7
+ # 1) Define the discriminant
8
+ #############################
9
+
10
+ # Symbolic variables to build a symbolic expression of discriminant
11
+ z_sym, beta_sym, z_a_sym, y_sym = sp.symbols("z beta z_a y", real=True, positive=True)
12
+
13
+ # Define a, b, c, d in terms of z_sym, beta_sym, z_a_sym, y_sym
14
+ a_sym = z_sym * z_a_sym
15
+ b_sym = z_sym * z_a_sym + z_sym + z_a_sym
16
+ c_sym = z_sym + z_a_sym + 1 - y_sym*(beta_sym*z_a_sym + 1 - beta_sym)
17
+ d_sym = 1
18
+
19
+ # Symbolic expression for the standard cubic discriminant
20
+ Delta_expr = (
21
+ ( (b_sym*c_sym)/(6*a_sym**2) - (b_sym**3)/(27*a_sym**3) - d_sym/(2*a_sym) )**2
22
+ + ( c_sym/(3*a_sym) - (b_sym**2)/(9*a_sym**2) )**3
23
+ )
24
+
25
+ # Turn that into a fast numeric function:
26
+ discriminant_func = sp.lambdify((z_sym, beta_sym, z_a_sym, y_sym), Delta_expr, "numpy")
27
+
28
+ @st.cache_data
29
+ def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps=20000):
30
+ """
31
+ Numerically scan z in [z_min, z_max] looking for sign changes of
32
+ Delta(z) = 0. Returns all roots found via bisection.
33
+ """
34
+ z_grid = np.linspace(z_min, z_max, steps)
35
+ disc_vals = discriminant_func(z_grid, beta, z_a, y)
36
+
37
+ roots_found = []
38
+
39
+ # Scan for sign changes
40
+ for i in range(len(z_grid) - 1):
41
+ f1, f2 = disc_vals[i], disc_vals[i+1]
42
+ if np.isnan(f1) or np.isnan(f2):
43
+ continue
44
+
45
+ if f1 == 0.0:
46
+ roots_found.append(z_grid[i])
47
+ elif f2 == 0.0:
48
+ roots_found.append(z_grid[i+1])
49
+ elif f1*f2 < 0:
50
+ zl = z_grid[i]
51
+ zr = z_grid[i+1]
52
+ for _ in range(50):
53
+ mid = 0.5*(zl + zr)
54
+ fm = discriminant_func(mid, beta, z_a, y)
55
+ if fm == 0:
56
+ zl = zr = mid
57
+ break
58
+ if np.sign(fm) == np.sign(f1):
59
+ zl = mid
60
+ f1 = fm
61
+ else:
62
+ zr = mid
63
+ f2 = fm
64
+ root_approx = 0.5*(zl + zr)
65
+ roots_found.append(root_approx)
66
+
67
+ return np.array(roots_found)
68
+
69
+ @st.cache_data
70
+ def sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=51):
71
+ """
72
+ For each beta, find both the largest and smallest z where discriminant=0.
73
+ Returns (betas, z_min_values, z_max_values).
74
+ """
75
+ betas = np.linspace(0, 1, beta_steps)
76
+ z_min_values = []
77
+ z_max_values = []
78
+
79
+ for b in betas:
80
+ roots = find_z_at_discriminant_zero(z_a, y, b, z_min, z_max)
81
+ if len(roots) == 0:
82
+ z_min_values.append(np.nan)
83
+ z_max_values.append(np.nan)
84
+ else:
85
+ z_min_values.append(np.min(roots))
86
+ z_max_values.append(np.max(roots))
87
+
88
+ return betas, np.array(z_min_values), np.array(z_max_values)
89
+
90
+ @st.cache_data
91
+ def compute_additional_curve(betas, z_a, y):
92
+ """
93
+ Compute the additional curve with proper handling of divide by zero cases
94
+ """
95
+ with np.errstate(invalid='ignore', divide='ignore'):
96
+ sqrt_term = y * betas * (z_a - 1)
97
+ sqrt_term = np.where(sqrt_term < 0, np.nan, np.sqrt(sqrt_term))
98
+
99
+ term = (-1 + sqrt_term)/z_a
100
+ numerator = (y - 2)*term + y * betas * ((z_a - 1)/z_a) - 1/z_a - 1
101
+ denominator = term**2 + term
102
+
103
+ mask = (denominator == 0) | np.isnan(denominator) | np.isnan(numerator)
104
+ result = np.zeros_like(denominator)
105
+ result[~mask] = numerator[~mask] / denominator[~mask]
106
+ result[mask] = np.nan
107
+
108
+ return result
109
+
110
+ def generate_z_vs_beta_plot(z_a, y, z_min, z_max):
111
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
112
+ st.error("Invalid input parameters.")
113
+ return None
114
+
115
+ beta_steps = 101
116
+
117
+ betas, z_mins, z_maxs = sweep_beta_and_find_z_bounds(z_a, y, z_min, z_max, beta_steps=beta_steps)
118
+ new_curve = compute_additional_curve(betas, z_a, y)
119
+
120
+ fig = go.Figure()
121
+
122
+ fig.add_trace(
123
+ go.Scatter(
124
+ x=betas,
125
+ y=z_maxs,
126
+ mode="markers+lines",
127
+ name="Upper z*(β)",
128
+ marker=dict(size=5, color='blue'),
129
+ line=dict(color='blue'),
130
+ )
131
+ )
132
+
133
+ fig.add_trace(
134
+ go.Scatter(
135
+ x=betas,
136
+ y=z_mins,
137
+ mode="markers+lines",
138
+ name="Lower z*(β)",
139
+ marker=dict(size=5, color='lightblue'),
140
+ line=dict(color='lightblue'),
141
+ )
142
+ )
143
+
144
+ fig.add_trace(
145
+ go.Scatter(
146
+ x=betas,
147
+ y=new_curve,
148
+ mode="markers+lines",
149
+ name="Additional Expression",
150
+ marker=dict(size=5, color='red'),
151
+ line=dict(color='red'),
152
+ )
153
+ )
154
+
155
+ fig.update_layout(
156
+ title="Curves vs β: z*(β) boundaries (blue) and Additional Expression (red)",
157
+ xaxis_title="β",
158
+ yaxis_title="Value",
159
+ hovermode="x unified",
160
+ )
161
+ return fig
162
+
163
+ @st.cache_data
164
+ def compute_cubic_roots(z, beta, z_a, y):
165
+ """
166
+ Compute the roots of the cubic equation for given parameters.
167
+ Returns array of complex roots.
168
+ """
169
+ a = z * z_a
170
+ b = z * z_a + z + z_a
171
+ c = z + z_a + 1 - y*(beta*z_a + 1 - beta)
172
+ d = 1
173
+
174
+ coeffs = [a, b, c, d]
175
+ roots = np.roots(coeffs)
176
+ return roots
177
+
178
+ def generate_ims_vs_z_plot(beta, y, z_a, z_min, z_max):
179
+ if z_a <= 0 or y <= 0 or z_min >= z_max:
180
+ st.error("Invalid input parameters.")
181
+ return None
182
+
183
+ z_points = np.linspace(z_min, z_max, 1000)
184
+ ims = []
185
+
186
+ for z in z_points:
187
+ roots = compute_cubic_roots(z, beta, z_a, y)
188
+ roots = sorted(roots, key=lambda x: abs(x.imag))
189
+ ims.append([root.imag for root in roots])
190
+
191
+ ims = np.array(ims)
192
+
193
+ fig = go.Figure()
194
+
195
+ for i in range(3):
196
+ fig.add_trace(
197
+ go.Scatter(
198
+ x=z_points,
199
+ y=ims[:,i],
200
+ mode="lines",
201
+ name=f"Im{{s{i+1}}}",
202
+ line=dict(width=2),
203
+ )
204
+ )
205
+
206
+ fig.update_layout(
207
+ title=f"Im{{s}} vs. z (β={beta:.3f}, y={y:.3f}, z_a={z_a:.3f})",
208
+ xaxis_title="z",
209
+ yaxis_title="Im{s}",
210
+ hovermode="x unified",
211
+ )
212
+ return fig
213
+
214
+ # Streamlit UI
215
+ st.set_page_config(page_title="Cubic Root Analysis", layout="wide")
216
+
217
+ st.title("Cubic Root Analysis")
218
+
219
+ tab1, tab2 = st.tabs(["z*(β) Curves", "Im{s} vs. z"])
220
+
221
+ with tab1:
222
+ st.header("Find z Values where Cubic Roots Transition Between Real and Complex")
223
+
224
+ col1, col2 = st.columns([1, 2])
225
+
226
+ with col1:
227
+ z_a_1 = st.number_input("z_a", value=1.0, key="z_a_1")
228
+ y_1 = st.number_input("y", value=1.0, key="y_1")
229
+ z_min_1 = st.number_input("z_min", value=-10.0, key="z_min_1")
230
+ z_max_1 = st.number_input("z_max", value=10.0, key="z_max_1")
231
+
232
+ if st.button("Compute z vs. β Curves"):
233
+ with col2:
234
+ fig = generate_z_vs_beta_plot(z_a_1, y_1, z_min_1, z_max_1)
235
+ if fig is not None:
236
+ st.plotly_chart(fig, use_container_width=True)
237
+
238
+ with tab2:
239
+ st.header("Plot Imaginary Parts of Roots vs. z")
240
+
241
+ col1, col2 = st.columns([1, 2])
242
+
243
+ with col1:
244
+ beta = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0)
245
+ y_2 = st.number_input("y", value=1.0, key="y_2")
246
+ z_a_2 = st.number_input("z_a", value=1.0, key="z_a_2")
247
+ z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_2")
248
+ z_max_2 = st.number_input("z_max", value=10.0, key="z_max_2")
249
+
250
+ if st.button("Compute Im{s} vs. z"):
251
+ with col2:
252
+ fig = generate_ims_vs_z_plot(beta, y_2, z_a_2, z_min_2, z_max_2)
253
+ if fig is not None:
254
+ st.plotly_chart(fig, use_container_width=True)