euler314 commited on
Commit
45cee47
·
verified ·
1 Parent(s): dec1797

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +409 -63
app.py CHANGED
@@ -44,8 +44,11 @@ def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
44
  Scan z in [z_min, z_max] for sign changes in the discriminant,
45
  and return approximated roots (where the discriminant is zero).
46
  """
 
 
 
47
  z_grid = np.linspace(z_min, z_max, steps)
48
- disc_vals = discriminant_func(z_grid, beta, z_a, y)
49
  roots_found = []
50
  for i in range(len(z_grid) - 1):
51
  f1, f2 = disc_vals[i], disc_vals[i+1]
@@ -59,7 +62,7 @@ def find_z_at_discriminant_zero(z_a, y, beta, z_min, z_max, steps):
59
  zl, zr = z_grid[i], 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
@@ -174,6 +177,7 @@ def compute_high_y_curve(betas, z_a, y):
174
  numerator = -4*a*(a-1)*y_effective*betas - 2*a*y_effective - 2*a*(2*a-1)
175
  return numerator/denominator
176
 
 
177
  def compute_alternate_low_expr(betas, z_a, y):
178
  """
179
  Compute the alternate low expression:
@@ -413,7 +417,7 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
413
  # Add Low Expression only if selected
414
  if show_low_y and alt_low_expr is not None:
415
  fig.add_trace(go.Scatter(x=betas, y=alt_low_expr, mode="markers+lines",
416
- name="Low Expression", line=dict(color='green')))
417
 
418
  # Add the max/min curves if selected
419
  if show_max_k and max_k_curve is not None:
@@ -422,14 +426,14 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
422
 
423
  if show_min_t and min_t_curve is not None:
424
  fig.add_trace(go.Scatter(x=betas, y=min_t_curve, mode="lines",
425
- name="Min t Expression", line=dict(color='orange', width=2)))
426
 
427
  if custom_curve1 is not None:
428
  fig.add_trace(go.Scatter(x=betas, y=custom_curve1, mode="markers+lines",
429
- name="Custom 1 (s-based)", line=dict(color='purple')))
430
  if custom_curve2 is not None:
431
  fig.add_trace(go.Scatter(x=betas, y=custom_curve2, mode="markers+lines",
432
- name="Custom 2 (direct)", line=dict(color='magenta')))
433
 
434
  if show_derivatives:
435
  # First derivatives
@@ -444,9 +448,9 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
444
  curve_info.append(('alt_low', 'Alt Low', 'orange'))
445
 
446
  if custom_curve1 is not None:
447
- curve_info.append(('custom1', 'Custom 1', 'purple'))
448
  if custom_curve2 is not None:
449
- curve_info.append(('custom2', 'Custom 2', 'magenta'))
450
 
451
  for key, name, color in curve_info:
452
  if key in derivatives:
@@ -464,9 +468,9 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
464
 
465
  if show_min_t and min_t_curve is not None:
466
  fig.add_trace(go.Scatter(x=betas, y=min_t_derivatives[0], mode="lines",
467
- name="Min t d/dβ", line=dict(color='orange', dash='dash')))
468
  fig.add_trace(go.Scatter(x=betas, y=min_t_derivatives[1], mode="lines",
469
- name="Min t d²/dβ²", line=dict(color='orange', dash='dot')))
470
 
471
  fig.update_layout(
472
  title="Curves vs β: Eigenvalue Support Boundaries and Asymptotic Expressions" if use_eigenvalue_method
@@ -507,6 +511,9 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
507
  st.error("Invalid input parameters.")
508
  return None, None
509
 
 
 
 
510
  z_points = np.linspace(z_min, z_max, n_points)
511
  ims, res = [], []
512
  for z in z_points:
@@ -532,6 +539,129 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
532
  xaxis_title="z", yaxis_title="Re{s}", hovermode="x unified")
533
  return fig_im, fig_re
534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
  @st.cache_data
536
  def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
537
  """
@@ -591,13 +721,77 @@ def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
591
  showlegend=True
592
  )
593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  return fig
595
 
596
  # ----------------- Streamlit UI -----------------
597
  st.title("Cubic Root Analysis")
598
 
599
- # Define three tabs (removed "Curve Intersections")
600
- tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Im{s} vs. z", "Differential Analysis"])
601
 
602
  # ----- Tab 1: z*(β) Curves -----
603
  with tab1:
@@ -692,20 +886,20 @@ with tab1:
692
  - **High y Expression** (Green): Asymptotic approximation for high y values
693
  - **Low Expression** (Orange): Alternative asymptotic expression
694
  - **Max k Expression** (Red): $\\max_{k \\in (0,\\infty)} \\frac{y\\beta (a-1)k + \\bigl(ak+1\\bigr)\\bigl((y-1)k-1\\bigr)}{(ak+1)(k^2+k)}$
695
- - **Min t Expression** (Orange): $\\min_{t \\in \\left(-\\frac{1}{a},\\, 0\\right)} \\frac{y\\beta (a-1)t + \\bigl(at+1\\bigr)\\bigl((y-1)t-1\\bigr)}{(at+1)(t^2+t)}$
696
- - **Custom Expression 1** (Purple): Result from user-defined s substituted into the main formula
697
- - **Custom Expression 2** (Magenta): Direct z(β) expression
698
  """)
699
  else:
700
  st.markdown("""
701
  - **Upper z*(β)** (Blue): Maximum z value where discriminant is zero
702
- - **Lower z*(β)** (Light Blue): Minimum z value where discriminant is zero
703
  - **High y Expression** (Green): Asymptotic approximation for high y values
704
  - **Low Expression** (Orange): Alternative asymptotic expression
705
  - **Max k Expression** (Red): $\\max_{k \\in (0,\\infty)} \\frac{y\\beta (a-1)k + \\bigl(ak+1\\bigr)\\bigl((y-1)k-1\\bigr)}{(ak+1)(k^2+k)}$
706
- - **Min t Expression** (Orange): $\\min_{t \\in \\left(-\\frac{1}{a},\\, 0\\right)} \\frac{y\\beta (a-1)t + \\bigl(at+1\\bigr)\\bigl((y-1)t-1\\bigr)}{(at+1)(t^2+t)}$
707
- - **Custom Expression 1** (Purple): Result from user-defined s substituted into the main formula
708
- - **Custom Expression 2** (Magenta): Direct z(β) expression
709
  """)
710
  if show_derivatives:
711
  st.markdown("""
@@ -714,48 +908,200 @@ with tab1:
714
  - Dotted lines: Second derivatives (d²/dβ²)
715
  """)
716
 
717
- # ----- Tab 2: Im{s} vs. z -----
718
  with tab2:
719
- st.header("Plot Complex Roots vs. z")
720
- col1, col2 = st.columns([1, 2])
721
- with col1:
722
- beta = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0, key="beta_tab2")
723
- y_2 = st.number_input("y", value=1.0, key="y_tab2")
724
- z_a_2 = st.number_input("z_a", value=1.0, key="z_a_tab2")
725
- z_min_2 = st.number_input("z_min", value=-10.0, key="z_min_tab2")
726
- z_max_2 = st.number_input("z_max", value=10.0, key="z_max_tab2")
727
- with st.expander("Resolution Settings", expanded=False):
728
- z_points = st.slider("z grid points", min_value=1000, max_value=10000, value=5000, step=500, key="z_points")
729
- if st.button("Compute Complex Roots vs. z", key="tab2_button"):
730
- with col2:
731
- fig_im, fig_re = generate_root_plots(beta, y_2, z_a_2, z_min_2, z_max_2, z_points)
732
- if fig_im is not None and fig_re is not None:
733
- st.plotly_chart(fig_im, use_container_width=True)
734
- st.plotly_chart(fig_re, use_container_width=True)
735
-
736
- # Add a separator
737
- st.markdown("---")
738
-
739
- # Add eigenvalue distribution section
740
- st.header("Eigenvalue Distribution for B_n = S_n T_n")
741
- with st.expander("Simulation Information", expanded=False):
742
- st.markdown("""
743
- This simulation generates the eigenvalue distribution of B_n as n→∞, where:
744
- - B_n = (1/n)XX* with X being a p×n matrix
745
- - p/n → y as n→∞
746
- - All elements of X are i.i.d with distribution β·δ(z_a) + (1-β)·δ(1)
747
- """)
748
-
749
- col_eigen1, col_eigen2 = st.columns([1, 2])
750
- with col_eigen1:
751
- n_samples = st.slider("Number of samples (n)", min_value=100, max_value=2000, value=1000, step=100)
752
- sim_seed = st.number_input("Random seed", min_value=1, max_value=1000, value=42, step=1)
753
-
754
- if st.button("Generate Eigenvalue Distribution", key="tab2_eigen_button"):
755
- with col_eigen2:
756
- fig_eigen = generate_eigenvalue_distribution(beta, y_2, z_a_2, n=n_samples, seed=sim_seed)
757
- if fig_eigen is not None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  st.plotly_chart(fig_eigen, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
759
 
760
  # ----- Tab 3: Differential Analysis -----
761
  with tab3:
@@ -795,7 +1141,7 @@ with tab3:
795
  st.subheader("Curves to Analyze")
796
  analyze_upper_lower = st.checkbox("Upper-Lower Difference", value=True)
797
  analyze_high_y = st.checkbox("High y Expression", value=False)
798
- analyze_alt_low = st.checkbox("Alternate Low Expression", value=False)
799
 
800
  if st.button("Compute Differentials", key="tab3_button"):
801
  with col2:
@@ -843,11 +1189,11 @@ with tab3:
843
  d2 = np.gradient(d1, betas_diff)
844
 
845
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=alt_low_curve, mode="lines",
846
- name="Alt Low", line=dict(color="orange", width=2)))
847
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d1, mode="lines",
848
- name="Alt Low d/dβ", line=dict(color="orange", dash='dash')))
849
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d2, mode="lines",
850
- name="Alt Low d²/dβ²", line=dict(color="orange", dash='dot')))
851
 
852
  fig_diff.update_layout(
853
  title="Differential Analysis vs. β" +
 
44
  Scan z in [z_min, z_max] for sign changes in the discriminant,
45
  and return approximated roots (where the discriminant is zero).
46
  """
47
+ # Apply the condition for y
48
+ y_effective = y if y > 1 else 1/y
49
+
50
  z_grid = np.linspace(z_min, z_max, steps)
51
+ disc_vals = discriminant_func(z_grid, beta, z_a, y_effective)
52
  roots_found = []
53
  for i in range(len(z_grid) - 1):
54
  f1, f2 = disc_vals[i], disc_vals[i+1]
 
62
  zl, zr = z_grid[i], z_grid[i+1]
63
  for _ in range(50):
64
  mid = 0.5 * (zl + zr)
65
+ fm = discriminant_func(mid, beta, z_a, y_effective)
66
  if fm == 0:
67
  zl = zr = mid
68
  break
 
177
  numerator = -4*a*(a-1)*y_effective*betas - 2*a*y_effective - 2*a*(2*a-1)
178
  return numerator/denominator
179
 
180
+ @st.cache_data
181
  def compute_alternate_low_expr(betas, z_a, y):
182
  """
183
  Compute the alternate low expression:
 
417
  # Add Low Expression only if selected
418
  if show_low_y and alt_low_expr is not None:
419
  fig.add_trace(go.Scatter(x=betas, y=alt_low_expr, mode="markers+lines",
420
+ name="Low Expression", line=dict(color='orange')))
421
 
422
  # Add the max/min curves if selected
423
  if show_max_k and max_k_curve is not None:
 
426
 
427
  if show_min_t and min_t_curve is not None:
428
  fig.add_trace(go.Scatter(x=betas, y=min_t_curve, mode="lines",
429
+ name="Min t Expression", line=dict(color='purple', width=2)))
430
 
431
  if custom_curve1 is not None:
432
  fig.add_trace(go.Scatter(x=betas, y=custom_curve1, mode="markers+lines",
433
+ name="Custom 1 (s-based)", line=dict(color='magenta')))
434
  if custom_curve2 is not None:
435
  fig.add_trace(go.Scatter(x=betas, y=custom_curve2, mode="markers+lines",
436
+ name="Custom 2 (direct)", line=dict(color='brown')))
437
 
438
  if show_derivatives:
439
  # First derivatives
 
448
  curve_info.append(('alt_low', 'Alt Low', 'orange'))
449
 
450
  if custom_curve1 is not None:
451
+ curve_info.append(('custom1', 'Custom 1', 'magenta'))
452
  if custom_curve2 is not None:
453
+ curve_info.append(('custom2', 'Custom 2', 'brown'))
454
 
455
  for key, name, color in curve_info:
456
  if key in derivatives:
 
468
 
469
  if show_min_t and min_t_curve is not None:
470
  fig.add_trace(go.Scatter(x=betas, y=min_t_derivatives[0], mode="lines",
471
+ name="Min t d/dβ", line=dict(color='purple', dash='dash')))
472
  fig.add_trace(go.Scatter(x=betas, y=min_t_derivatives[1], mode="lines",
473
+ name="Min t d²/dβ²", line=dict(color='purple', dash='dot')))
474
 
475
  fig.update_layout(
476
  title="Curves vs β: Eigenvalue Support Boundaries and Asymptotic Expressions" if use_eigenvalue_method
 
511
  st.error("Invalid input parameters.")
512
  return None, None
513
 
514
+ # Apply the condition for y
515
+ y_effective = y if y > 1 else 1/y
516
+
517
  z_points = np.linspace(z_min, z_max, n_points)
518
  ims, res = [], []
519
  for z in z_points:
 
539
  xaxis_title="z", yaxis_title="Re{s}", hovermode="x unified")
540
  return fig_im, fig_re
541
 
542
+ def analyze_complex_root_structure(beta_values, z, z_a, y):
543
+ """
544
+ Analyze when the cubic equation switches between having all real roots
545
+ and having a complex conjugate pair plus one real root.
546
+
547
+ Returns:
548
+ - transition_points: beta values where the root structure changes
549
+ - structure_types: list indicating whether each interval has all real roots or complex roots
550
+ """
551
+ # Apply the condition for y
552
+ y_effective = y if y > 1 else 1/y
553
+
554
+ transition_points = []
555
+ structure_types = []
556
+
557
+ previous_type = None
558
+
559
+ for beta in beta_values:
560
+ roots = compute_cubic_roots(z, beta, z_a, y)
561
+
562
+ # Check if all roots are real (imaginary parts close to zero)
563
+ is_all_real = all(abs(root.imag) < 1e-10 for root in roots)
564
+
565
+ current_type = "real" if is_all_real else "complex"
566
+
567
+ if previous_type is not None and current_type != previous_type:
568
+ # Found a transition point
569
+ transition_points.append(beta)
570
+ structure_types.append(previous_type)
571
+
572
+ previous_type = current_type
573
+
574
+ # Add the final interval type
575
+ if previous_type is not None:
576
+ structure_types.append(previous_type)
577
+
578
+ return transition_points, structure_types
579
+
580
+ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
581
+ """
582
+ Generate Im(s) and Re(s) vs. β plots.
583
+ """
584
+ if z_a <= 0 or y <= 0 or beta_min >= beta_max:
585
+ st.error("Invalid input parameters.")
586
+ return None, None, None
587
+
588
+ # Apply the condition for y
589
+ y_effective = y if y > 1 else 1/y
590
+
591
+ beta_points = np.linspace(beta_min, beta_max, n_points)
592
+ ims, res = [], []
593
+ for beta in beta_points:
594
+ roots = compute_cubic_roots(z, beta, z_a, y)
595
+ roots = sorted(roots, key=lambda x: abs(x.imag))
596
+ ims.append([root.imag for root in roots])
597
+ res.append([root.real for root in roots])
598
+ ims = np.array(ims)
599
+ res = np.array(res)
600
+
601
+ # Find transition points in root structure
602
+ transition_points, structure_types = analyze_complex_root_structure(beta_points, z, z_a, y)
603
+
604
+ # Create traces for imaginary parts
605
+ fig_im = go.Figure()
606
+ for i in range(3):
607
+ fig_im.add_trace(go.Scatter(x=beta_points, y=ims[:, i], mode="lines", name=f"Im{{s{i+1}}}",
608
+ line=dict(width=2)))
609
+
610
+ # Add vertical lines at transition points
611
+ for beta in transition_points:
612
+ fig_im.add_vline(x=beta, line=dict(color="red", width=1, dash="dash"))
613
+
614
+ fig_im.update_layout(title=f"Im{{s}} vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
615
+ xaxis_title="β", yaxis_title="Im{s}", hovermode="x unified")
616
+
617
+ # Create traces for real parts
618
+ fig_re = go.Figure()
619
+ for i in range(3):
620
+ fig_re.add_trace(go.Scatter(x=beta_points, y=res[:, i], mode="lines", name=f"Re{{s{i+1}}}",
621
+ line=dict(width=2)))
622
+
623
+ # Add vertical lines at transition points
624
+ for beta in transition_points:
625
+ fig_re.add_vline(x=beta, line=dict(color="red", width=1, dash="dash"))
626
+
627
+ fig_re.update_layout(title=f"Re{{s}} vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
628
+ xaxis_title="β", yaxis_title="Re{s}", hovermode="x unified")
629
+
630
+ # Create a plot showing the discriminant
631
+ fig_disc = go.Figure()
632
+
633
+ # Calculate discriminant as a function of beta
634
+ discriminant_values = []
635
+ for beta in beta_points:
636
+ # For cubic ax^3 + bx^2 + cx + d, the discriminant is:
637
+ # Δ = 18abcd - 27a^2d^2 + b^2c^2 - 2b^3d - 9ac^3
638
+
639
+ a = z * z_a
640
+ b = z * z_a + z + z_a - z_a*y_effective
641
+ c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
642
+ d = 1
643
+
644
+ delta0 = b*b - 3*a*c
645
+ delta1 = 2*b*b*b - 9*a*b*c + 27*a*a*d
646
+ discriminant = delta1*delta1 - 4*delta0*delta0*delta0
647
+
648
+ discriminant_values.append(discriminant)
649
+
650
+ fig_disc.add_trace(go.Scatter(x=beta_points, y=discriminant_values, mode="lines",
651
+ name="Discriminant", line=dict(width=2, color="black")))
652
+
653
+ # Add horizontal line at y=0
654
+ fig_disc.add_hline(y=0, line=dict(color="red", width=1, dash="dash"))
655
+
656
+ # Add vertical lines at transition points
657
+ for beta in transition_points:
658
+ fig_disc.add_vline(x=beta, line=dict(color="red", width=1, dash="dash"))
659
+
660
+ fig_disc.update_layout(title=f"Cubic Discriminant vs. β (z={z:.3f}, y={y:.3f}, z_a={z_a:.3f})",
661
+ xaxis_title="β", yaxis_title="Discriminant", hovermode="x unified")
662
+
663
+ return fig_im, fig_re, fig_disc
664
+
665
  @st.cache_data
666
  def generate_eigenvalue_distribution(beta, y, z_a, n=1000, seed=42):
667
  """
 
721
  showlegend=True
722
  )
723
 
724
+ return fig, eigenvalues
725
+
726
+ def generate_phase_diagram(z_a, y, beta_min=0.0, beta_max=1.0, z_min=-10.0, z_max=10.0,
727
+ beta_steps=100, z_steps=100):
728
+ """
729
+ Generate a phase diagram showing regions of complex and real roots.
730
+
731
+ Returns a heatmap where:
732
+ - Value 1 (red): Region with all real roots
733
+ - Value -1 (blue): Region with complex roots
734
+ """
735
+ # Apply the condition for y
736
+ y_effective = y if y > 1 else 1/y
737
+
738
+ beta_values = np.linspace(beta_min, beta_max, beta_steps)
739
+ z_values = np.linspace(z_min, z_max, z_steps)
740
+
741
+ # Initialize phase map
742
+ phase_map = np.zeros((z_steps, beta_steps))
743
+
744
+ # Progress tracking
745
+ progress_bar = st.progress(0)
746
+ status_text = st.empty()
747
+
748
+ for i, z in enumerate(z_values):
749
+ # Update progress
750
+ progress_bar.progress((i + 1) / len(z_values))
751
+ status_text.text(f"Analyzing phase at z = {z:.2f} ({i+1}/{len(z_values)})")
752
+
753
+ for j, beta in enumerate(beta_values):
754
+ roots = compute_cubic_roots(z, beta, z_a, y)
755
+
756
+ # Check if all roots are real (imaginary parts close to zero)
757
+ is_all_real = all(abs(root.imag) < 1e-10 for root in roots)
758
+
759
+ phase_map[i, j] = 1 if is_all_real else -1
760
+
761
+ # Clear progress indicators
762
+ progress_bar.empty()
763
+ status_text.empty()
764
+
765
+ # Create heatmap
766
+ fig = go.Figure(data=go.Heatmap(
767
+ z=phase_map,
768
+ x=beta_values,
769
+ y=z_values,
770
+ colorscale=[[0, 'blue'], [0.5, 'white'], [1.0, 'red']],
771
+ zmin=-1,
772
+ zmax=1,
773
+ showscale=True,
774
+ colorbar=dict(
775
+ title="Root Type",
776
+ tickvals=[-1, 1],
777
+ ticktext=["Complex Roots", "All Real Roots"]
778
+ )
779
+ ))
780
+
781
+ fig.update_layout(
782
+ title=f"Phase Diagram: Root Structure (y={y:.3f}, z_a={z_a:.3f})",
783
+ xaxis_title="β",
784
+ yaxis_title="z",
785
+ hovermode="closest"
786
+ )
787
+
788
  return fig
789
 
790
  # ----------------- Streamlit UI -----------------
791
  st.title("Cubic Root Analysis")
792
 
793
+ # Define three tabs
794
+ tab1, tab2, tab3 = st.tabs(["z*(β) Curves", "Complex Root Analysis", "Differential Analysis"])
795
 
796
  # ----- Tab 1: z*(β) Curves -----
797
  with tab1:
 
886
  - **High y Expression** (Green): Asymptotic approximation for high y values
887
  - **Low Expression** (Orange): Alternative asymptotic expression
888
  - **Max k Expression** (Red): $\\max_{k \\in (0,\\infty)} \\frac{y\\beta (a-1)k + \\bigl(ak+1\\bigr)\\bigl((y-1)k-1\\bigr)}{(ak+1)(k^2+k)}$
889
+ - **Min t Expression** (Purple): $\\min_{t \\in \\left(-\\frac{1}{a},\\, 0\\right)} \\frac{y\\beta (a-1)t + \\bigl(at+1\\bigr)\\bigl((y-1)t-1\\bigr)}{(at+1)(t^2+t)}$
890
+ - **Custom Expression 1** (Magenta): Result from user-defined s substituted into the main formula
891
+ - **Custom Expression 2** (Brown): Direct z(β) expression
892
  """)
893
  else:
894
  st.markdown("""
895
  - **Upper z*(β)** (Blue): Maximum z value where discriminant is zero
896
+ - **Lower z*(β)** (Blue): Minimum z value where discriminant is zero
897
  - **High y Expression** (Green): Asymptotic approximation for high y values
898
  - **Low Expression** (Orange): Alternative asymptotic expression
899
  - **Max k Expression** (Red): $\\max_{k \\in (0,\\infty)} \\frac{y\\beta (a-1)k + \\bigl(ak+1\\bigr)\\bigl((y-1)k-1\\bigr)}{(ak+1)(k^2+k)}$
900
+ - **Min t Expression** (Purple): $\\min_{t \\in \\left(-\\frac{1}{a},\\, 0\\right)} \\frac{y\\beta (a-1)t + \\bigl(at+1\\bigr)\\bigl((y-1)t-1\\bigr)}{(at+1)(t^2+t)}$
901
+ - **Custom Expression 1** (Magenta): Result from user-defined s substituted into the main formula
902
+ - **Custom Expression 2** (Brown): Direct z(β) expression
903
  """)
904
  if show_derivatives:
905
  st.markdown("""
 
908
  - Dotted lines: Second derivatives (d²/dβ²)
909
  """)
910
 
911
+ # ----- Tab 2: Complex Root Analysis -----
912
  with tab2:
913
+ st.header("Complex Root Analysis")
914
+
915
+ # Create tabs within the page for different plots
916
+ plot_tabs = st.tabs(["Im{s} vs. z", "Im{s} vs. β", "Phase Diagram", "Eigenvalue Distribution"])
917
+
918
+ # Tab for Im{s} vs. z plot
919
+ with plot_tabs[0]:
920
+ col1, col2 = st.columns([1, 2])
921
+ with col1:
922
+ beta_z = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0, key="beta_tab2_z")
923
+ y_z = st.number_input("y", value=1.0, key="y_tab2_z")
924
+ z_a_z = st.number_input("z_a", value=1.0, key="z_a_tab2_z")
925
+ z_min_z = st.number_input("z_min", value=-10.0, key="z_min_tab2_z")
926
+ z_max_z = st.number_input("z_max", value=10.0, key="z_max_tab2_z")
927
+ with st.expander("Resolution Settings", expanded=False):
928
+ z_points = st.slider("z grid points", min_value=1000, max_value=10000, value=5000, step=500, key="z_points_z")
929
+ if st.button("Compute Complex Roots vs. z", key="tab2_button_z"):
930
+ with col2:
931
+ fig_im, fig_re = generate_root_plots(beta_z, y_z, z_a_z, z_min_z, z_max_z, z_points)
932
+ if fig_im is not None and fig_re is not None:
933
+ st.plotly_chart(fig_im, use_container_width=True)
934
+ st.plotly_chart(fig_re, use_container_width=True)
935
+
936
+ # New tab for Im{s} vs. β plot
937
+ with plot_tabs[1]:
938
+ col1, col2 = st.columns([1, 2])
939
+ with col1:
940
+ z_beta = st.number_input("z", value=1.0, key="z_tab2_beta")
941
+ y_beta = st.number_input("y", value=1.0, key="y_tab2_beta")
942
+ z_a_beta = st.number_input("z_a", value=1.0, key="z_a_tab2_beta")
943
+ beta_min = st.number_input("β_min", value=0.0, min_value=0.0, max_value=1.0, key="beta_min_tab2")
944
+ beta_max = st.number_input("β_max", value=1.0, min_value=0.0, max_value=1.0, key="beta_max_tab2")
945
+ with st.expander("Resolution Settings", expanded=False):
946
+ beta_points = st.slider("β grid points", min_value=100, max_value=1000, value=500, step=100, key="beta_points")
947
+ if st.button("Compute Complex Roots vs. β", key="tab2_button_beta"):
948
+ with col2:
949
+ fig_im_beta, fig_re_beta, fig_disc = generate_roots_vs_beta_plots(
950
+ z_beta, y_beta, z_a_beta, beta_min, beta_max, beta_points)
951
+
952
+ if fig_im_beta is not None and fig_re_beta is not None and fig_disc is not None:
953
+ st.plotly_chart(fig_im_beta, use_container_width=True)
954
+ st.plotly_chart(fig_re_beta, use_container_width=True)
955
+ st.plotly_chart(fig_disc, use_container_width=True)
956
+
957
+ # Add analysis of transition points
958
+ transition_points, structure_types = analyze_complex_root_structure(
959
+ np.linspace(beta_min, beta_max, beta_points), z_beta, z_a_beta, y_beta)
960
+
961
+ if transition_points:
962
+ st.subheader("Root Structure Transition Points")
963
+ for i, beta in enumerate(transition_points):
964
+ prev_type = structure_types[i]
965
+ next_type = structure_types[i+1] if i+1 < len(structure_types) else "unknown"
966
+ st.markdown(f"- At β = {beta:.6f}: Transition from {prev_type} roots to {next_type} roots")
967
+ else:
968
+ st.info("No transitions detected in root structure across this β range.")
969
+
970
+ # Explanation
971
+ with st.expander("Analysis Explanation", expanded=False):
972
+ st.markdown("""
973
+ ### Interpreting the Plots
974
+
975
+ - **Im{s} vs. β**: Shows how the imaginary parts of the roots change with β. When all curves are at Im{s}=0, all roots are real.
976
+ - **Re{s} vs. β**: Shows how the real parts of the roots change with β.
977
+ - **Discriminant Plot**: The cubic discriminant changes sign at points where the root structure changes.
978
+ - When discriminant < 0: The cubic has one real root and two complex conjugate roots.
979
+ - When discriminant > 0: The cubic has three distinct real roots.
980
+ - When discriminant = 0: The cubic has multiple roots (at least two roots are equal).
981
+
982
+ The vertical red dashed lines mark the transition points where the root structure changes.
983
+ """)
984
+
985
+ # Tab for Phase Diagram
986
+ with plot_tabs[2]:
987
+ col1, col2 = st.columns([1, 2])
988
+ with col1:
989
+ z_a_phase = st.number_input("z_a", value=1.0, key="z_a_phase")
990
+ y_phase = st.number_input("y", value=1.0, key="y_phase")
991
+ beta_min_phase = st.number_input("β_min", value=0.0, min_value=0.0, max_value=1.0, key="beta_min_phase")
992
+ beta_max_phase = st.number_input("β_max", value=1.0, min_value=0.0, max_value=1.0, key="beta_max_phase")
993
+ z_min_phase = st.number_input("z_min", value=-10.0, key="z_min_phase")
994
+ z_max_phase = st.number_input("z_max", value=10.0, key="z_max_phase")
995
+
996
+ with st.expander("Resolution Settings", expanded=False):
997
+ beta_steps_phase = st.slider("β grid points", min_value=20, max_value=200, value=100, step=20, key="beta_steps_phase")
998
+ z_steps_phase = st.slider("z grid points", min_value=20, max_value=200, value=100, step=20, key="z_steps_phase")
999
+
1000
+ if st.button("Generate Phase Diagram", key="tab2_button_phase"):
1001
+ with col2:
1002
+ st.info("Generating phase diagram. This may take a while depending on resolution...")
1003
+ fig_phase = generate_phase_diagram(
1004
+ z_a_phase, y_phase, beta_min_phase, beta_max_phase, z_min_phase, z_max_phase,
1005
+ beta_steps_phase, z_steps_phase)
1006
+
1007
+ if fig_phase is not None:
1008
+ st.plotly_chart(fig_phase, use_container_width=True)
1009
+
1010
+ with st.expander("Phase Diagram Explanation", expanded=False):
1011
+ st.markdown("""
1012
+ ### Understanding the Phase Diagram
1013
+
1014
+ This heatmap shows the regions in the (β, z) plane where:
1015
+
1016
+ - **Red Regions**: The cubic equation has all real roots
1017
+ - **Blue Regions**: The cubic equation has one real root and two complex conjugate roots
1018
+
1019
+ The boundaries between these regions represent values where the discriminant is zero,
1020
+ which are the exact same curves as the z*(β) boundaries in the first tab. This phase
1021
+ diagram provides a comprehensive view of the eigenvalue support structure.
1022
+ """)
1023
+
1024
+ # Eigenvalue distribution tab
1025
+ with plot_tabs[3]:
1026
+ st.subheader("Eigenvalue Distribution for B_n = S_n T_n")
1027
+ with st.expander("Simulation Information", expanded=False):
1028
+ st.markdown("""
1029
+ This simulation generates the eigenvalue distribution of B_n as n→∞, where:
1030
+ - B_n = (1/n)XX^T with X being a p×n matrix
1031
+ - p/n → y as n→∞
1032
+ - The diagonal entries of T_n follow distribution β·δ(z_a) + (1-β)·δ(1)
1033
+ """)
1034
+
1035
+ col_eigen1, col_eigen2 = st.columns([1, 2])
1036
+ with col_eigen1:
1037
+ beta_eigen = st.number_input("β", value=0.5, min_value=0.0, max_value=1.0, key="beta_eigen")
1038
+ y_eigen = st.number_input("y", value=1.0, key="y_eigen")
1039
+ z_a_eigen = st.number_input("z_a", value=1.0, key="z_a_eigen")
1040
+ n_samples = st.slider("Number of samples (n)", min_value=100, max_value=2000, value=1000, step=100)
1041
+ sim_seed = st.number_input("Random seed", min_value=1, max_value=1000, value=42, step=1)
1042
+
1043
+ # Add comparison option
1044
+ show_theoretical = st.checkbox("Show theoretical boundaries", value=True)
1045
+ show_empirical_stats = st.checkbox("Show empirical statistics", value=True)
1046
+
1047
+ if st.button("Generate Eigenvalue Distribution", key="tab2_eigen_button"):
1048
+ with col_eigen2:
1049
+ # Generate the eigenvalue distribution
1050
+ fig_eigen, eigenvalues = generate_eigenvalue_distribution(beta_eigen, y_eigen, z_a_eigen, n=n_samples, seed=sim_seed)
1051
+
1052
+ # If requested, compute and add theoretical boundaries
1053
+ if show_theoretical:
1054
+ # Calculate min and max eigenvalues using the support boundary functions
1055
+ betas = np.array([beta_eigen])
1056
+ min_eig, max_eig = compute_eigenvalue_support_boundaries(z_a_eigen, y_eigen, betas, n_samples=n_samples, seeds=5)
1057
+
1058
+ # Add vertical lines for boundaries
1059
+ fig_eigen.add_vline(
1060
+ x=min_eig[0],
1061
+ line=dict(color="red", width=2, dash="dash"),
1062
+ annotation_text="Min theoretical",
1063
+ annotation_position="top right"
1064
+ )
1065
+ fig_eigen.add_vline(
1066
+ x=max_eig[0],
1067
+ line=dict(color="red", width=2, dash="dash"),
1068
+ annotation_text="Max theoretical",
1069
+ annotation_position="top left"
1070
+ )
1071
+
1072
+ # Display the plot
1073
  st.plotly_chart(fig_eigen, use_container_width=True)
1074
+
1075
+ # Add comparison of empirical vs theoretical bounds
1076
+ if show_theoretical and show_empirical_stats:
1077
+ empirical_min = eigenvalues.min()
1078
+ empirical_max = eigenvalues.max()
1079
+
1080
+ st.markdown("### Comparison of Empirical vs Theoretical Bounds")
1081
+ col1, col2, col3 = st.columns(3)
1082
+ with col1:
1083
+ st.metric("Theoretical Min", f"{min_eig[0]:.4f}")
1084
+ st.metric("Theoretical Max", f"{max_eig[0]:.4f}")
1085
+ st.metric("Theoretical Width", f"{max_eig[0] - min_eig[0]:.4f}")
1086
+ with col2:
1087
+ st.metric("Empirical Min", f"{empirical_min:.4f}")
1088
+ st.metric("Empirical Max", f"{empirical_max:.4f}")
1089
+ st.metric("Empirical Width", f"{empirical_max - empirical_min:.4f}")
1090
+ with col3:
1091
+ st.metric("Min Difference", f"{empirical_min - min_eig[0]:.4f}")
1092
+ st.metric("Max Difference", f"{empirical_max - max_eig[0]:.4f}")
1093
+ st.metric("Width Difference", f"{(empirical_max - empirical_min) - (max_eig[0] - min_eig[0]):.4f}")
1094
+
1095
+ # Display additional statistics
1096
+ if show_empirical_stats:
1097
+ st.markdown("### Eigenvalue Statistics")
1098
+ col1, col2 = st.columns(2)
1099
+ with col1:
1100
+ st.metric("Mean", f"{np.mean(eigenvalues):.4f}")
1101
+ st.metric("Median", f"{np.median(eigenvalues):.4f}")
1102
+ with col2:
1103
+ st.metric("Standard Deviation", f"{np.std(eigenvalues):.4f}")
1104
+ st.metric("Interquartile Range", f"{np.percentile(eigenvalues, 75) - np.percentile(eigenvalues, 25):.4f}")
1105
 
1106
  # ----- Tab 3: Differential Analysis -----
1107
  with tab3:
 
1141
  st.subheader("Curves to Analyze")
1142
  analyze_upper_lower = st.checkbox("Upper-Lower Difference", value=True)
1143
  analyze_high_y = st.checkbox("High y Expression", value=False)
1144
+ analyze_alt_low = st.checkbox("Low y Expression", value=False)
1145
 
1146
  if st.button("Compute Differentials", key="tab3_button"):
1147
  with col2:
 
1189
  d2 = np.gradient(d1, betas_diff)
1190
 
1191
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=alt_low_curve, mode="lines",
1192
+ name="Low y", line=dict(color="orange", width=2)))
1193
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d1, mode="lines",
1194
+ name="Low y d/dβ", line=dict(color="orange", dash='dash')))
1195
  fig_diff.add_trace(go.Scatter(x=betas_diff, y=d2, mode="lines",
1196
+ name="Low y d²/dβ²", line=dict(color="orange", dash='dot')))
1197
 
1198
  fig_diff.update_layout(
1199
  title="Differential Analysis vs. β" +