NavyDevilDoc commited on
Commit
e87d551
Β·
verified Β·
1 Parent(s): 2374c13

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +132 -42
src/streamlit_app.py CHANGED
@@ -1,14 +1,38 @@
1
  import streamlit as st
2
  import numpy as np
3
  import matplotlib.pyplot as plt
 
 
 
4
  from kinematics_visualizer import Motion1D, Motion2D, KinematicsVisualizer
5
 
 
 
 
 
 
 
 
 
 
6
  def clear_comparison_state():
7
  """Clear any lingering comparison state"""
8
- keys_to_clear = [key for key in st.session_state.keys() if key.startswith('comp_')]
9
- for key in keys_to_clear:
10
- if key in st.session_state:
11
- del st.session_state[key]
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  # Configure Streamlit page
14
  st.set_page_config(
@@ -66,26 +90,47 @@ st.markdown("""
66
  """, unsafe_allow_html=True)
67
 
68
  def main():
 
 
 
69
  st.markdown('<div class="main-header">πŸš€ Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
70
 
 
 
 
 
71
  # Sidebar for navigation
72
  st.sidebar.title("πŸ“š Select Tutorial")
73
  tutorial_type = st.sidebar.radio(
74
  "Choose a physics concept:",
75
- ["1D Motion", "2D Projectile Motion", "Compare Motions"]
 
76
  )
77
 
 
 
 
 
 
 
 
 
78
  # Clear comparison state when not on comparison tab
79
  if tutorial_type != "Compare Motions":
80
  clear_comparison_state()
81
- plt.close('all') # Close any lingering plots
82
 
83
- if tutorial_type == "1D Motion":
84
- show_1d_motion()
85
- elif tutorial_type == "2D Projectile Motion":
86
- show_2d_motion()
87
- elif tutorial_type == "Compare Motions":
88
- show_motion_comparison()
 
 
 
 
 
89
 
90
  def show_1d_motion():
91
  st.markdown('<div class="section-header">πŸ“ One-Dimensional Motion</div>', unsafe_allow_html=True)
@@ -486,75 +531,114 @@ def show_motion_comparison():
486
  st.markdown("**Compare up to 3 different projectile motions side by side**")
487
 
488
  # Clear any previous matplotlib figures explicitly
489
- plt.clf()
490
- plt.close('all')
 
 
 
 
 
 
 
 
 
 
 
 
491
 
492
  # Create tabs for different trajectories
493
  tab1, tab2, tab3 = st.tabs(["πŸš€ Trajectory 1", "🎯 Trajectory 2", "⚽ Trajectory 3"])
494
 
495
  trajectories = []
496
 
497
- # Use containers to isolate each tab's content
498
  with tab1:
499
  with st.container():
 
500
  col1, col2 = st.columns(2)
501
  with col1:
502
- speed1 = st.slider("Speed 1 (m/s)", 5.0, 50.0, 20.0, key="comp_speed1")
503
- angle1 = st.slider("Angle 1 (Β°)", 0.0, 90.0, 30.0, key="comp_angle1")
504
  with col2:
505
- height1 = st.slider("Height 1 (m)", 0.0, 30.0, 0.0, key="comp_height1")
506
 
 
 
 
 
 
507
  motion1 = Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1)
508
  trajectories.append(("Trajectory 1", motion1, 'blue'))
509
 
 
510
  with tab2:
511
  with st.container():
 
512
  col1, col2 = st.columns(2)
513
  with col1:
514
- speed2 = st.slider("Speed 2 (m/s)", 5.0, 50.0, 25.0, key="comp_speed2")
515
- angle2 = st.slider("Angle 2 (Β°)", 0.0, 90.0, 45.0, key="comp_angle2")
516
  with col2:
517
- height2 = st.slider("Height 2 (m)", 0.0, 30.0, 0.0, key="comp_height2")
518
 
 
 
 
 
 
519
  motion2 = Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2)
520
  trajectories.append(("Trajectory 2", motion2, 'red'))
521
 
 
522
  with tab3:
523
  with st.container():
 
524
  col1, col2 = st.columns(2)
525
  with col1:
526
- speed3 = st.slider("Speed 3 (m/s)", 5.0, 50.0, 30.0, key="comp_speed3")
527
- angle3 = st.slider("Angle 3 (Β°)", 0.0, 90.0, 60.0, key="comp_angle3")
528
  with col2:
529
- height3 = st.slider("Height 3 (m)", 0.0, 30.0, 5.0, key="comp_height3")
530
 
 
 
 
 
 
531
  motion3 = Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3)
532
  trajectories.append(("Trajectory 3", motion3, 'green'))
533
 
534
- # Create comparison plot in its own container
535
- with st.container():
 
 
 
 
536
  try:
537
- # Ensure we have a clean figure
538
- plt.close('all')
539
  fig, ax = plt.subplots(figsize=(12, 8))
540
 
541
  max_range = 0
 
 
542
  for name, motion, color in trajectories:
543
  try:
544
  data = motion.trajectory_data(motion.calculate_flight_time())
545
- if len(data['x']) > 0: # Check data exists
546
  ax.plot(data['x'], data['y'], linewidth=3, label=name, color=color)
547
 
548
  # Mark launch and landing points
549
  ax.plot(motion.launch_x, motion.launch_height, 'o', markersize=8, color=color, alpha=0.7)
550
- ax.plot(data['x'][-1], data['y'][-1], 's', markersize=8, color=color, alpha=0.7)
551
- max_range = max(max_range, data['x'][-1] if data['x'][-1] > 0 else 0)
 
 
552
  except Exception as e:
553
- st.error(f"Error plotting {name}: {str(e)}")
554
  continue
555
 
556
  # Only proceed if we have valid data
557
- if max_range > 0:
558
  ax.set_xlabel('Horizontal Position (m)', fontsize=12)
559
  ax.set_ylabel('Vertical Position (m)', fontsize=12)
560
  ax.set_title('Trajectory Comparison', fontsize=16, fontweight='bold')
@@ -563,21 +647,27 @@ def show_motion_comparison():
563
  ax.set_ylim(bottom=0)
564
  ax.set_xlim(0, max_range * 1.1)
565
 
566
- # Display plot
567
- st.pyplot(fig, clear_figure=True) # Important: clear_figure=True
568
  else:
569
  st.warning("No valid trajectory data to display")
570
 
571
  except Exception as e:
572
  st.error(f"Error creating comparison plot: {str(e)}")
573
  finally:
574
- plt.close(fig) # Ensure figure is always closed
575
- plt.close('all')
 
 
 
 
576
 
577
- # Comparison table in its own container
578
- with st.container():
579
- st.markdown("### πŸ“‹ Trajectory Comparison Table")
580
-
 
 
581
  try:
582
  comparison_data = []
583
  for name, motion, _ in trajectories:
@@ -593,7 +683,7 @@ def show_motion_comparison():
593
  "Max Height (m)": f"{info['max_height']:.1f}"
594
  })
595
  except Exception as e:
596
- st.error(f"Error getting info for {name}: {str(e)}")
597
  continue
598
 
599
  if comparison_data:
 
1
  import streamlit as st
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
+ import matplotlib
5
+ import os
6
+ import tempfile
7
  from kinematics_visualizer import Motion1D, Motion2D, KinematicsVisualizer
8
 
9
+ # Fix matplotlib configuration for HuggingFace Spaces
10
+ try:
11
+ # Set matplotlib to use a writable temporary directory
12
+ temp_dir = tempfile.mkdtemp()
13
+ os.environ['MPLCONFIGDIR'] = temp_dir
14
+ matplotlib.use('Agg') # Use non-interactive backend
15
+ except Exception as e:
16
+ st.warning(f"Matplotlib configuration warning (app will still work): {e}")
17
+
18
  def clear_comparison_state():
19
  """Clear any lingering comparison state"""
20
+ try:
21
+ keys_to_clear = [key for key in st.session_state.keys() if key.startswith('comp_')]
22
+ for key in keys_to_clear:
23
+ if key in st.session_state:
24
+ del st.session_state[key]
25
+ except Exception:
26
+ pass # Silently handle any state clearing issues
27
+
28
+ def clear_plots():
29
+ """Safely clear all matplotlib plots"""
30
+ try:
31
+ plt.clf()
32
+ plt.close('all')
33
+ plt.ioff() # Turn off interactive mode
34
+ except Exception:
35
+ pass
36
 
37
  # Configure Streamlit page
38
  st.set_page_config(
 
90
  """, unsafe_allow_html=True)
91
 
92
  def main():
93
+ # Clear plots at the start
94
+ clear_plots()
95
+
96
  st.markdown('<div class="main-header">πŸš€ Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
97
 
98
+ # Initialize session state for tutorial selection if not exists
99
+ if 'tutorial_type' not in st.session_state:
100
+ st.session_state.tutorial_type = "1D Motion"
101
+
102
  # Sidebar for navigation
103
  st.sidebar.title("πŸ“š Select Tutorial")
104
  tutorial_type = st.sidebar.radio(
105
  "Choose a physics concept:",
106
+ ["1D Motion", "2D Projectile Motion", "Compare Motions"],
107
+ index=["1D Motion", "2D Projectile Motion", "Compare Motions"].index(st.session_state.tutorial_type)
108
  )
109
 
110
+ # Detect tutorial change and clear state
111
+ if tutorial_type != st.session_state.tutorial_type:
112
+ st.session_state.tutorial_type = tutorial_type
113
+ clear_comparison_state()
114
+ clear_plots()
115
+ # Force a clean rerun
116
+ st.rerun()
117
+
118
  # Clear comparison state when not on comparison tab
119
  if tutorial_type != "Compare Motions":
120
  clear_comparison_state()
121
+ clear_plots()
122
 
123
+ # Route to appropriate function
124
+ try:
125
+ if tutorial_type == "1D Motion":
126
+ show_1d_motion()
127
+ elif tutorial_type == "2D Projectile Motion":
128
+ show_2d_motion()
129
+ elif tutorial_type == "Compare Motions":
130
+ show_motion_comparison()
131
+ except Exception as e:
132
+ st.error(f"Error loading tutorial: {str(e)}")
133
+ st.info("Please refresh the page and try again.")
134
 
135
  def show_1d_motion():
136
  st.markdown('<div class="section-header">πŸ“ One-Dimensional Motion</div>', unsafe_allow_html=True)
 
531
  st.markdown("**Compare up to 3 different projectile motions side by side**")
532
 
533
  # Clear any previous matplotlib figures explicitly
534
+ clear_plots()
535
+
536
+ # Use session state to prevent oscillation
537
+ if 'comparison_initialized' not in st.session_state:
538
+ st.session_state.comparison_initialized = True
539
+ st.session_state.comp_speed1 = 20.0
540
+ st.session_state.comp_angle1 = 30.0
541
+ st.session_state.comp_height1 = 0.0
542
+ st.session_state.comp_speed2 = 25.0
543
+ st.session_state.comp_angle2 = 45.0
544
+ st.session_state.comp_height2 = 0.0
545
+ st.session_state.comp_speed3 = 30.0
546
+ st.session_state.comp_angle3 = 60.0
547
+ st.session_state.comp_height3 = 5.0
548
 
549
  # Create tabs for different trajectories
550
  tab1, tab2, tab3 = st.tabs(["πŸš€ Trajectory 1", "🎯 Trajectory 2", "⚽ Trajectory 3"])
551
 
552
  trajectories = []
553
 
554
+ # Tab 1
555
  with tab1:
556
  with st.container():
557
+ st.subheader("Configure Trajectory 1")
558
  col1, col2 = st.columns(2)
559
  with col1:
560
+ speed1 = st.slider("Speed 1 (m/s)", 5.0, 50.0, st.session_state.comp_speed1, key="slider_speed1")
561
+ angle1 = st.slider("Angle 1 (Β°)", 0.0, 90.0, st.session_state.comp_angle1, key="slider_angle1")
562
  with col2:
563
+ height1 = st.slider("Height 1 (m)", 0.0, 30.0, st.session_state.comp_height1, key="slider_height1")
564
 
565
+ # Update session state
566
+ st.session_state.comp_speed1 = speed1
567
+ st.session_state.comp_angle1 = angle1
568
+ st.session_state.comp_height1 = height1
569
+
570
  motion1 = Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1)
571
  trajectories.append(("Trajectory 1", motion1, 'blue'))
572
 
573
+ # Tab 2
574
  with tab2:
575
  with st.container():
576
+ st.subheader("Configure Trajectory 2")
577
  col1, col2 = st.columns(2)
578
  with col1:
579
+ speed2 = st.slider("Speed 2 (m/s)", 5.0, 50.0, st.session_state.comp_speed2, key="slider_speed2")
580
+ angle2 = st.slider("Angle 2 (Β°)", 0.0, 90.0, st.session_state.comp_angle2, key="slider_angle2")
581
  with col2:
582
+ height2 = st.slider("Height 2 (m)", 0.0, 30.0, st.session_state.comp_height2, key="slider_height2")
583
 
584
+ # Update session state
585
+ st.session_state.comp_speed2 = speed2
586
+ st.session_state.comp_angle2 = angle2
587
+ st.session_state.comp_height2 = height2
588
+
589
  motion2 = Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2)
590
  trajectories.append(("Trajectory 2", motion2, 'red'))
591
 
592
+ # Tab 3
593
  with tab3:
594
  with st.container():
595
+ st.subheader("Configure Trajectory 3")
596
  col1, col2 = st.columns(2)
597
  with col1:
598
+ speed3 = st.slider("Speed 3 (m/s)", 5.0, 50.0, st.session_state.comp_speed3, key="slider_speed3")
599
+ angle3 = st.slider("Angle 3 (Β°)", 0.0, 90.0, st.session_state.comp_angle3, key="slider_angle3")
600
  with col2:
601
+ height3 = st.slider("Height 3 (m)", 0.0, 30.0, st.session_state.comp_height3, key="slider_height3")
602
 
603
+ # Update session state
604
+ st.session_state.comp_speed3 = speed3
605
+ st.session_state.comp_angle3 = angle3
606
+ st.session_state.comp_height3 = height3
607
+
608
  motion3 = Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3)
609
  trajectories.append(("Trajectory 3", motion3, 'green'))
610
 
611
+ # Create comparison plot in a separate container
612
+ st.markdown("---")
613
+ st.markdown("### πŸ“ˆ Trajectory Comparison Plot")
614
+
615
+ plot_container = st.container()
616
+ with plot_container:
617
  try:
618
+ # Create figure with explicit cleanup
 
619
  fig, ax = plt.subplots(figsize=(12, 8))
620
 
621
  max_range = 0
622
+ valid_trajectories = 0
623
+
624
  for name, motion, color in trajectories:
625
  try:
626
  data = motion.trajectory_data(motion.calculate_flight_time())
627
+ if len(data['x']) > 0:
628
  ax.plot(data['x'], data['y'], linewidth=3, label=name, color=color)
629
 
630
  # Mark launch and landing points
631
  ax.plot(motion.launch_x, motion.launch_height, 'o', markersize=8, color=color, alpha=0.7)
632
+ if len(data['x']) > 0:
633
+ ax.plot(data['x'][-1], data['y'][-1], 's', markersize=8, color=color, alpha=0.7)
634
+ max_range = max(max_range, data['x'][-1] if data['x'][-1] > 0 else 0)
635
+ valid_trajectories += 1
636
  except Exception as e:
637
+ st.warning(f"Could not plot {name}: {str(e)}")
638
  continue
639
 
640
  # Only proceed if we have valid data
641
+ if valid_trajectories > 0 and max_range > 0:
642
  ax.set_xlabel('Horizontal Position (m)', fontsize=12)
643
  ax.set_ylabel('Vertical Position (m)', fontsize=12)
644
  ax.set_title('Trajectory Comparison', fontsize=16, fontweight='bold')
 
647
  ax.set_ylim(bottom=0)
648
  ax.set_xlim(0, max_range * 1.1)
649
 
650
+ # Display plot with explicit cleanup
651
+ st.pyplot(fig, clear_figure=True)
652
  else:
653
  st.warning("No valid trajectory data to display")
654
 
655
  except Exception as e:
656
  st.error(f"Error creating comparison plot: {str(e)}")
657
  finally:
658
+ # Always clean up
659
+ try:
660
+ plt.close(fig)
661
+ plt.close('all')
662
+ except:
663
+ pass
664
 
665
+ # Comparison table
666
+ st.markdown("---")
667
+ st.markdown("### πŸ“‹ Trajectory Comparison Table")
668
+
669
+ table_container = st.container()
670
+ with table_container:
671
  try:
672
  comparison_data = []
673
  for name, motion, _ in trajectories:
 
683
  "Max Height (m)": f"{info['max_height']:.1f}"
684
  })
685
  except Exception as e:
686
+ st.warning(f"Could not get data for {name}: {str(e)}")
687
  continue
688
 
689
  if comparison_data: