euler314 commited on
Commit
000e941
·
verified ·
1 Parent(s): 3994d34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -86
app.py CHANGED
@@ -490,30 +490,56 @@ def generate_z_vs_beta_plot(z_a, y, z_min, z_max, beta_steps, z_steps,
490
 
491
  def compute_cubic_roots(z, beta, z_a, y):
492
  """
493
- Compute the roots of the cubic equation for given parameters with improved accuracy.
494
  """
495
  # Apply the condition for y
496
  y_effective = y if y > 1 else 1/y
497
 
 
 
 
 
 
 
498
  # Coefficients in the form as^3 + bs^2 + cs + d = 0
499
  a = z * z_a
500
  b = z * z_a + z + z_a - z_a*y_effective
501
  c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
502
  d = 1
503
 
504
- # Special handling for small leading coefficient
505
  if abs(a) < 1e-10:
506
  if abs(b) < 1e-10: # Linear case
507
- roots = np.array([-d/c, 0, 0])
508
  else: # Quadratic case
509
- quadratic_roots = np.roots([b, c, d])
510
- roots = np.append(quadratic_roots, 0)
511
- else:
512
- # Use high-precision computation for the cubic
513
- coeffs = [a, b, c, d]
514
- roots = np.roots(coeffs)
515
 
516
- return roots
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  def track_roots_consistently(z_values, all_roots):
519
  """
@@ -528,58 +554,52 @@ def track_roots_consistently(z_values, all_roots):
528
  prev_roots = tracked_roots[i-1]
529
  current_roots = all_roots[i]
530
 
531
- # For each current root, find the closest previous root
 
532
  assignments = np.zeros(n_roots, dtype=int)
 
533
  for j in range(n_roots):
534
  distances = np.abs(current_roots - prev_roots[j])
535
- best_match = np.argmin(distances)
536
 
537
- # If this best match is already assigned, compare distances
538
- if best_match in assignments:
539
- prev_idx = np.where(assignments == best_match)[0][0]
540
- if distances[best_match] < np.abs(current_roots[best_match] - prev_roots[prev_idx]):
541
- # This root is a better match
542
- assignments[j] = best_match
543
- assignments[prev_idx] = -1 # Mark for reassignment
544
- else:
545
- assignments[j] = best_match
546
-
547
- # Handle unassigned roots
548
- unassigned = np.where(assignments == -1)[0]
549
- available = np.setdiff1d(np.arange(n_roots), assignments[assignments >= 0])
550
-
551
- for j, ua in enumerate(unassigned):
552
- if j < len(available):
553
- assignments[ua] = available[j]
554
- else:
555
- # Find the least bad assignment
556
- all_distances = np.array([np.abs(current_roots[k] - prev_roots[ua]) for k in range(n_roots)])
557
- assigned_indices = assignments[assignments >= 0]
558
- mask = np.ones(n_roots, dtype=bool)
559
- mask[assigned_indices] = False
560
- if np.any(mask):
561
- assignments[ua] = np.where(mask)[0][0]
562
 
563
  # Reorder current roots based on assignments
564
- reordered_roots = np.zeros_like(current_roots)
565
- for j in range(n_roots):
566
- if assignments[j] >= 0:
567
- reordered_roots[j] = current_roots[assignments[j]]
568
- else:
569
- # Find any unassigned root
570
- used = np.sort([assignments[k] for k in range(n_roots) if assignments[k] >= 0])
571
- unused = np.setdiff1d(np.arange(n_roots), used)
572
- if len(unused) > 0:
573
- reordered_roots[j] = current_roots[unused[0]]
574
- assignments[j] = unused[0]
575
-
576
- tracked_roots[i] = reordered_roots
577
 
578
  return tracked_roots
579
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
581
  """
582
- Generate Im(s) and Re(s) vs. z plots with improved accuracy and tracking.
583
  """
584
  if z_a <= 0 or y <= 0 or z_min >= z_max:
585
  st.error("Invalid input parameters.")
@@ -592,35 +612,42 @@ def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
592
 
593
  # Collect all roots first
594
  all_roots = []
595
- for z in z_points:
 
 
 
 
 
 
 
 
 
 
 
596
  roots = compute_cubic_roots(z, beta, z_a, y)
597
- # Initial sorting to have some consistency
 
598
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
599
  all_roots.append(roots)
 
 
 
 
 
 
 
 
600
 
601
  all_roots = np.array(all_roots)
 
602
 
603
- # Track roots consistently
604
  tracked_roots = track_roots_consistently(z_points, all_roots)
605
 
606
  # Extract imaginary and real parts
607
  ims = np.imag(tracked_roots)
608
  res = np.real(tracked_roots)
609
 
610
- # Calculate discriminant for verification
611
- discriminants = []
612
- for z in z_points:
613
- a = z * z_a
614
- b = z * z_a + z + z_a - z_a*y_effective
615
- c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
616
- d = 1
617
-
618
- # Cubic discriminant: 18abcd - 27a²d² + b²c² - 2b³d - 9ac³
619
- disc = (18*a*b*c*d - 27*a*a*d*d + b*b*c*c - 2*b*b*b*d - 9*a*c*c*c)
620
- discriminants.append(disc)
621
-
622
- discriminants = np.array(discriminants)
623
-
624
  # Create figure for imaginary parts
625
  fig_im = go.Figure()
626
  for i in range(3):
@@ -702,7 +729,7 @@ def analyze_complex_root_structure(beta_values, z, z_a, y):
702
 
703
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
704
  """
705
- Generate Im(s) and Re(s) vs. β plots with improved accuracy.
706
  """
707
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
708
  st.error("Invalid input parameters.")
@@ -715,35 +742,42 @@ def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
715
 
716
  # Collect all roots first
717
  all_roots = []
718
- for beta in beta_points:
 
 
 
 
 
 
 
 
 
 
 
719
  roots = compute_cubic_roots(z, beta, z_a, y)
720
- # Initial sorting to have some consistency
 
721
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
722
  all_roots.append(roots)
 
 
 
 
 
 
 
 
723
 
724
  all_roots = np.array(all_roots)
 
725
 
726
- # Track roots consistently
727
  tracked_roots = track_roots_consistently(beta_points, all_roots)
728
 
729
  # Extract imaginary and real parts
730
  ims = np.imag(tracked_roots)
731
  res = np.real(tracked_roots)
732
 
733
- # Calculate discriminant for verification
734
- discriminants = []
735
- for beta in beta_points:
736
- a = z * z_a
737
- b = z * z_a + z + z_a - z_a*y_effective
738
- c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
739
- d = 1
740
-
741
- # Cubic discriminant
742
- disc = (18*a*b*c*d - 27*a*a*d*d + b*b*c*c - 2*b*b*b*d - 9*a*c*c*c)
743
- discriminants.append(disc)
744
-
745
- discriminants = np.array(discriminants)
746
-
747
  # Create figure for imaginary parts
748
  fig_im = go.Figure()
749
  for i in range(3):
 
490
 
491
  def compute_cubic_roots(z, beta, z_a, y):
492
  """
493
+ Compute the roots of the cubic equation for given parameters using SymPy for maximum accuracy.
494
  """
495
  # Apply the condition for y
496
  y_effective = y if y > 1 else 1/y
497
 
498
+ # Import SymPy functions
499
+ from sympy import symbols, solve, im, re, N, Poly
500
+
501
+ # Create a symbolic variable for the equation
502
+ s = symbols('s')
503
+
504
  # Coefficients in the form as^3 + bs^2 + cs + d = 0
505
  a = z * z_a
506
  b = z * z_a + z + z_a - z_a*y_effective
507
  c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
508
  d = 1
509
 
510
+ # Handle special cases
511
  if abs(a) < 1e-10:
512
  if abs(b) < 1e-10: # Linear case
513
+ roots = np.array([-d/c, 0, 0], dtype=complex)
514
  else: # Quadratic case
515
+ quad_roots = np.roots([b, c, d])
516
+ roots = np.append(quad_roots, 0).astype(complex)
517
+ return roots
 
 
 
518
 
519
+ try:
520
+ # Create the cubic polynomial
521
+ cubic_eq = Poly(a*s**3 + b*s**2 + c*s + d, s)
522
+
523
+ # Solve the equation symbolically
524
+ symbolic_roots = solve(cubic_eq, s)
525
+
526
+ # Convert symbolic roots to complex numbers with high precision
527
+ numerical_roots = []
528
+ for root in symbolic_roots:
529
+ # Use SymPy's N function with high precision
530
+ numerical_root = complex(N(root, 30))
531
+ numerical_roots.append(numerical_root)
532
+
533
+ # If we got fewer than 3 roots (due to multiplicity), pad with zeros
534
+ while len(numerical_roots) < 3:
535
+ numerical_roots.append(0j)
536
+
537
+ return np.array(numerical_roots, dtype=complex)
538
+
539
+ except Exception as e:
540
+ # Fallback to numpy if SymPy has issues
541
+ coeffs = [a, b, c, d]
542
+ return np.roots(coeffs)
543
 
544
  def track_roots_consistently(z_values, all_roots):
545
  """
 
554
  prev_roots = tracked_roots[i-1]
555
  current_roots = all_roots[i]
556
 
557
+ # For each previous root, find the closest current root
558
+ assigned = np.zeros(n_roots, dtype=bool)
559
  assignments = np.zeros(n_roots, dtype=int)
560
+
561
  for j in range(n_roots):
562
  distances = np.abs(current_roots - prev_roots[j])
 
563
 
564
+ # Find the closest unassigned root
565
+ while True:
566
+ best_idx = np.argmin(distances)
567
+ if not assigned[best_idx]:
568
+ assignments[j] = best_idx
569
+ assigned[best_idx] = True
570
+ break
571
+ else:
572
+ # Mark as infinite distance and try again
573
+ distances[best_idx] = np.inf
574
+
575
+ # Safety check if all are assigned (shouldn't happen)
576
+ if np.all(distances == np.inf):
577
+ assignments[j] = j # Default to same index
578
+ break
 
 
 
 
 
 
 
 
 
 
579
 
580
  # Reorder current roots based on assignments
581
+ tracked_roots[i] = current_roots[assignments]
 
 
 
 
 
 
 
 
 
 
 
 
582
 
583
  return tracked_roots
584
 
585
+ def generate_cubic_discriminant(z, beta, z_a, y_effective):
586
+ """
587
+ Calculate the cubic discriminant using the standard formula.
588
+ For a cubic ax^3 + bx^2 + cx + d:
589
+ Δ = 18abcd - 27a^2d^2 + b^2c^2 - 2b^3d - 9ac^3
590
+ """
591
+ a = z * z_a
592
+ b = z * z_a + z + z_a - z_a*y_effective
593
+ c = z + z_a + 1 - y_effective*(beta*z_a + 1 - beta)
594
+ d = 1
595
+
596
+ # Standard formula for cubic discriminant
597
+ discriminant = (18*a*b*c*d - 27*a**2*d**2 + b**2*c**2 - 2*b**3*d - 9*a*c**3)
598
+ return discriminant
599
+
600
  def generate_root_plots(beta, y, z_a, z_min, z_max, n_points):
601
  """
602
+ Generate Im(s) and Re(s) vs. z plots with improved accuracy using SymPy.
603
  """
604
  if z_a <= 0 or y <= 0 or z_min >= z_max:
605
  st.error("Invalid input parameters.")
 
612
 
613
  # Collect all roots first
614
  all_roots = []
615
+ discriminants = []
616
+
617
+ # Progress indicator
618
+ progress_bar = st.progress(0)
619
+ status_text = st.empty()
620
+
621
+ for i, z in enumerate(z_points):
622
+ # Update progress
623
+ progress_bar.progress((i + 1) / n_points)
624
+ status_text.text(f"Computing roots for z = {z:.3f} ({i+1}/{n_points})")
625
+
626
+ # Calculate roots using SymPy
627
  roots = compute_cubic_roots(z, beta, z_a, y)
628
+
629
+ # Initial sorting to help with tracking
630
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
631
  all_roots.append(roots)
632
+
633
+ # Calculate discriminant
634
+ disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
635
+ discriminants.append(disc)
636
+
637
+ # Clear progress indicators
638
+ progress_bar.empty()
639
+ status_text.empty()
640
 
641
  all_roots = np.array(all_roots)
642
+ discriminants = np.array(discriminants)
643
 
644
+ # Track roots consistently across z values
645
  tracked_roots = track_roots_consistently(z_points, all_roots)
646
 
647
  # Extract imaginary and real parts
648
  ims = np.imag(tracked_roots)
649
  res = np.real(tracked_roots)
650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  # Create figure for imaginary parts
652
  fig_im = go.Figure()
653
  for i in range(3):
 
729
 
730
  def generate_roots_vs_beta_plots(z, y, z_a, beta_min, beta_max, n_points):
731
  """
732
+ Generate Im(s) and Re(s) vs. β plots with improved accuracy using SymPy.
733
  """
734
  if z_a <= 0 or y <= 0 or beta_min >= beta_max:
735
  st.error("Invalid input parameters.")
 
742
 
743
  # Collect all roots first
744
  all_roots = []
745
+ discriminants = []
746
+
747
+ # Progress indicator
748
+ progress_bar = st.progress(0)
749
+ status_text = st.empty()
750
+
751
+ for i, beta in enumerate(beta_points):
752
+ # Update progress
753
+ progress_bar.progress((i + 1) / n_points)
754
+ status_text.text(f"Computing roots for β = {beta:.3f} ({i+1}/{n_points})")
755
+
756
+ # Calculate roots using SymPy
757
  roots = compute_cubic_roots(z, beta, z_a, y)
758
+
759
+ # Initial sorting to help with tracking
760
  roots = sorted(roots, key=lambda x: (abs(x.imag), x.real))
761
  all_roots.append(roots)
762
+
763
+ # Calculate discriminant
764
+ disc = generate_cubic_discriminant(z, beta, z_a, y_effective)
765
+ discriminants.append(disc)
766
+
767
+ # Clear progress indicators
768
+ progress_bar.empty()
769
+ status_text.empty()
770
 
771
  all_roots = np.array(all_roots)
772
+ discriminants = np.array(discriminants)
773
 
774
+ # Track roots consistently across beta values
775
  tracked_roots = track_roots_consistently(beta_points, all_roots)
776
 
777
  # Extract imaginary and real parts
778
  ims = np.imag(tracked_roots)
779
  res = np.real(tracked_roots)
780
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
  # Create figure for imaginary parts
782
  fig_im = go.Figure()
783
  for i in range(3):