NavyDevilDoc commited on
Commit
06ac5eb
·
verified ·
1 Parent(s): ec09121

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +323 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,325 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ from vector_physics import Vector2D, VectorPhysicsSimulator, plot_vectors, plot_trajectory, UnitConverter, QuizGenerator
4
 
5
+ # Configure Streamlit
6
+ st.set_page_config(page_title="Vector Physics Tutorial", layout="wide")
7
+
8
+ # Initialize simulator and quiz generator
9
+ simulator = VectorPhysicsSimulator()
10
+ quiz_gen = QuizGenerator()
11
+ converter = UnitConverter()
12
+
13
+ # Initialize session state
14
+ if 'quiz_question' not in st.session_state:
15
+ st.session_state.quiz_question = None
16
+ if 'quiz_answer' not in st.session_state:
17
+ st.session_state.quiz_answer = None
18
+ if 'quiz_submitted' not in st.session_state:
19
+ st.session_state.quiz_submitted = False
20
+ if 'show_explanation' not in st.session_state:
21
+ st.session_state.show_explanation = False
22
+
23
+ # Title and introduction
24
+ st.title("🚢 Vector Physics Interactive Tutorial")
25
+ st.markdown("""
26
+ Welcome to the Vector Physics Tutorial! Vectors are quantities that have both magnitude and direction.
27
+ Use the controls below to explore different vector scenarios and see how they behave in real-time.
28
+ """)
29
+
30
+ # Sidebar for navigation
31
+ st.sidebar.title("Select Physics Problem")
32
+ problem_type = st.sidebar.selectbox(
33
+ "Choose a problem type:",
34
+ ["Boat Crossing", "Projectile Motion", "Vector Addition", "Quiz Mode"]
35
+ )
36
+
37
+ # Unit selection
38
+ st.sidebar.markdown("---")
39
+ st.sidebar.subheader("🔧 Unit Settings")
40
+ speed_unit = st.sidebar.selectbox("Speed Units:", list(converter.speed_conversions().keys()))
41
+ distance_unit = st.sidebar.selectbox("Distance Units:", list(converter.distance_conversions().keys()))
42
+
43
+ # Reset button
44
+ if st.sidebar.button("🔄 Reset to Defaults", help="Reset all controls to default values"):
45
+ st.rerun()
46
+
47
+ if problem_type == "Boat Crossing":
48
+ st.header("🚢 Boat Crossing a River")
49
+ st.markdown("""
50
+ A boat is trying to cross a river with a current. The boat has its own velocity,
51
+ and the river current affects the boat's actual path.
52
+ """)
53
+
54
+ col1, col2 = st.columns([1, 2])
55
+
56
+ with col1:
57
+ st.subheader("Controls")
58
+ # Convert default values to selected units
59
+ default_boat_speed = converter.convert_speed(5.0, "m/s", speed_unit)
60
+ default_current_speed = converter.convert_speed(3.0, "m/s", speed_unit)
61
+
62
+ boat_speed_input = st.slider(f"Boat Speed ({speed_unit})", 0.1,
63
+ converter.convert_speed(10.0, "m/s", speed_unit),
64
+ default_boat_speed, 0.1)
65
+ boat_angle = st.slider("Boat Direction (degrees)", -90, 90, 45, 1)
66
+ current_speed_input = st.slider(f"Current Speed ({speed_unit})", 0.0,
67
+ converter.convert_speed(8.0, "m/s", speed_unit),
68
+ default_current_speed, 0.1)
69
+ current_angle = st.slider("Current Direction (degrees)", -180, 180, 180, 1)
70
+
71
+ # Convert back to m/s for calculations
72
+ boat_speed = converter.convert_speed(boat_speed_input, speed_unit, "m/s")
73
+ current_speed = converter.convert_speed(current_speed_input, speed_unit, "m/s")
74
+
75
+ # Calculate results
76
+ results = simulator.boat_crossing_problem(boat_speed, boat_angle, current_speed, current_angle)
77
+
78
+ st.subheader("Results")
79
+ st.write(f"**Boat Velocity:** {converter.convert_speed(results['boat_velocity'].magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {results['boat_velocity'].angle:.1f}°")
80
+ st.write(f"**Current Velocity:** {converter.convert_speed(results['current_velocity'].magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {results['current_velocity'].angle:.1f}°")
81
+ st.write(f"**Resultant Velocity:** {converter.convert_speed(results['resultant_velocity'].magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {results['resultant_velocity'].angle:.1f}��")
82
+
83
+ # Show heading difference
84
+ st.markdown("---")
85
+ st.subheader("📐 Navigation Analysis")
86
+ st.write(f"**Heading Difference:** {results['heading_difference']:.1f}°")
87
+ if results['heading_difference'] > 10:
88
+ st.warning(f"⚠️ The boat's actual path deviates {results['heading_difference']:.1f}° from intended direction!")
89
+ else:
90
+ st.success("✅ The boat is following close to its intended path.")
91
+
92
+ with col2:
93
+ # Create plots
94
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
95
+
96
+ # Vector diagram
97
+ vectors = [results['boat_velocity'], results['current_velocity'], results['resultant_velocity']]
98
+ labels = ['Boat Velocity (intended)', 'Current Velocity', 'Resultant Velocity (actual)']
99
+ colors = ['blue', 'red', 'green']
100
+ plot_vectors(ax1, vectors, labels, colors)
101
+ ax1.set_title("Vector Diagram")
102
+
103
+ # Trajectory plot
104
+ traj_x_converted = [converter.convert_distance(x, "meters", distance_unit) for x in results['trajectory_x'][:50]]
105
+ traj_y_converted = [converter.convert_distance(y, "meters", distance_unit) for y in results['trajectory_y'][:50]]
106
+ plot_trajectory(ax2, traj_x_converted, traj_y_converted, f"Boat Path ({distance_unit})")
107
+ ax2.set_xlabel(f'X Position ({distance_unit})')
108
+ ax2.set_ylabel(f'Y Position ({distance_unit})')
109
+
110
+ st.pyplot(fig)
111
+
112
+ elif problem_type == "Projectile Motion":
113
+ st.header("🎯 Projectile Motion")
114
+ st.markdown("""
115
+ A projectile is launched at an angle. Gravity acts downward while the horizontal
116
+ component of velocity remains constant. **Error analysis** shows how measurement
117
+ uncertainty affects the results.
118
+ """)
119
+
120
+ col1, col2 = st.columns([1, 2])
121
+
122
+ with col1:
123
+ st.subheader("Controls")
124
+ default_speed = converter.convert_speed(20.0, "m/s", speed_unit)
125
+ initial_speed_input = st.slider(f"Initial Speed ({speed_unit})", 1.0,
126
+ converter.convert_speed(50.0, "m/s", speed_unit),
127
+ default_speed, 1.0)
128
+ launch_angle = st.slider("Launch Angle (degrees)", 0, 90, 45, 1)
129
+ gravity = st.slider("Gravity (m/s²)", 1.0, 15.0, 9.81, 0.1)
130
+
131
+ show_error = st.checkbox("Show Error Analysis", value=True, help="Shows uncertainty range from measurement errors")
132
+
133
+ # Convert to m/s for calculations
134
+ initial_speed = converter.convert_speed(initial_speed_input, speed_unit, "m/s")
135
+
136
+ # Calculate results
137
+ results = simulator.projectile_motion(initial_speed, launch_angle, gravity)
138
+
139
+ st.subheader("Results")
140
+ st.write(f"**Initial Velocity:** {converter.convert_speed(results['initial_velocity'].magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {results['initial_velocity'].angle:.1f}°")
141
+ st.write(f"**Max Range:** {converter.convert_distance(results['max_range'], 'meters', distance_unit):.2f} {distance_unit}")
142
+ st.write(f"**Max Height:** {converter.convert_distance(results['max_height'], 'meters', distance_unit):.2f} {distance_unit}")
143
+ st.write(f"**Flight Time:** {results['time_points'][-1]:.2f} s")
144
+
145
+ if show_error:
146
+ st.markdown("---")
147
+ st.subheader("📊 Uncertainty Analysis")
148
+ st.write(f"**Speed Uncertainty:** ±{converter.convert_speed(results['speed_uncertainty'], 'm/s', speed_unit):.2f} {speed_unit}")
149
+ st.write(f"**Angle Uncertainty:** ±{results['angle_uncertainty']:.1f}°")
150
+ st.info("💡 Small measurement errors can lead to significant differences in trajectory!")
151
+
152
+ with col2:
153
+ # Create plots
154
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
155
+
156
+ # Vector diagram
157
+ initial_vel = results['initial_velocity']
158
+ velocity_x = Vector2D(initial_vel.x, 0)
159
+ velocity_y = Vector2D(0, initial_vel.y)
160
+
161
+ vectors = [velocity_x, velocity_y, initial_vel]
162
+ labels = ['Horizontal Component', 'Vertical Component', 'Initial Velocity']
163
+ colors = ['blue', 'red', 'green']
164
+ plot_vectors(ax1, vectors, labels, colors)
165
+ ax1.set_title("Velocity Components")
166
+
167
+ # Trajectory plot with error analysis
168
+ traj_x = [converter.convert_distance(x, "meters", distance_unit) for x in results['trajectory_x']]
169
+ traj_y = [converter.convert_distance(y, "meters", distance_unit) for y in results['trajectory_y']]
170
+
171
+ error_bounds = None
172
+ if show_error:
173
+ error_bounds = {
174
+ 'upper_x': [converter.convert_distance(x, "meters", distance_unit) for x in results['error_upper_x']],
175
+ 'upper_y': [converter.convert_distance(y, "meters", distance_unit) for y in results['error_upper_y']],
176
+ 'lower_x': [converter.convert_distance(x, "meters", distance_unit) for x in results['error_lower_x']],
177
+ 'lower_y': [converter.convert_distance(y, "meters", distance_unit) for y in results['error_lower_y']]
178
+ }
179
+
180
+ plot_trajectory(ax2, traj_x, traj_y, f"Projectile Path ({distance_unit})", error_bounds)
181
+ ax2.set_xlabel(f'X Position ({distance_unit})')
182
+ ax2.set_ylabel(f'Y Position ({distance_unit})')
183
+
184
+ st.pyplot(fig)
185
+
186
+ elif problem_type == "Vector Addition":
187
+ st.header("➕ Vector Addition Practice")
188
+ st.markdown("""
189
+ Practice adding vectors by adjusting their magnitudes and directions.
190
+ See how different combinations create different resultant vectors.
191
+ """)
192
+
193
+ col1, col2 = st.columns([1, 2])
194
+
195
+ with col1:
196
+ st.subheader("Vector A")
197
+ default_mag_a = converter.convert_speed(5.0, "m/s", speed_unit)
198
+ mag_a_input = st.slider(f"Magnitude A ({speed_unit})", 0.1,
199
+ converter.convert_speed(10.0, "m/s", speed_unit),
200
+ default_mag_a, 0.1, key="mag_a")
201
+ angle_a = st.slider("Angle A (degrees)", -180, 180, 30, 1, key="angle_a")
202
+
203
+ st.subheader("Vector B")
204
+ default_mag_b = converter.convert_speed(3.0, "m/s", speed_unit)
205
+ mag_b_input = st.slider(f"Magnitude B ({speed_unit})", 0.1,
206
+ converter.convert_speed(10.0, "m/s", speed_unit),
207
+ default_mag_b, 0.1, key="mag_b")
208
+ angle_b = st.slider("Angle B (degrees)", -180, 180, 120, 1, key="angle_b")
209
+
210
+ # Convert to m/s for calculations
211
+ mag_a = converter.convert_speed(mag_a_input, speed_unit, "m/s")
212
+ mag_b = converter.convert_speed(mag_b_input, speed_unit, "m/s")
213
+
214
+ # Create vectors
215
+ vector_a = Vector2D(magnitude=mag_a, angle=angle_a)
216
+ vector_b = Vector2D(magnitude=mag_b, angle=angle_b)
217
+ resultant = vector_a + vector_b
218
+
219
+ st.subheader("Results")
220
+ st.write(f"**Vector A:** {converter.convert_speed(vector_a.magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {vector_a.angle:.1f}°")
221
+ st.write(f"**Vector B:** {converter.convert_speed(vector_b.magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {vector_b.angle:.1f}°")
222
+ st.write(f"**Resultant:** {converter.convert_speed(resultant.magnitude, 'm/s', speed_unit):.2f} {speed_unit} at {resultant.angle:.1f}°")
223
+ st.write(f"**Dot Product A·B:** {vector_a.dot_product(vector_b):.2f}")
224
+ st.write(f"**Angle Between Vectors:** {vector_a.angle_between(vector_b):.1f}°")
225
+
226
+ with col2:
227
+ fig, ax = plt.subplots(figsize=(8, 8))
228
+
229
+ vectors = [vector_a, vector_b, resultant]
230
+ labels = ['Vector A', 'Vector B', 'Resultant A+B']
231
+ colors = ['blue', 'red', 'green']
232
+ plot_vectors(ax, vectors, labels, colors)
233
+ ax.set_title("Vector Addition")
234
+
235
+ st.pyplot(fig)
236
+
237
+ elif problem_type == "Quiz Mode":
238
+ st.header("🎓 Vector Physics Quiz")
239
+ st.markdown("""
240
+ Test your understanding of vector concepts! Click 'New Question' to get a random problem.
241
+ """)
242
+
243
+ col1, col2, col3 = st.columns([1, 1, 1])
244
+
245
+ with col1:
246
+ if st.button("📝 New Question", type="primary"):
247
+ st.session_state.quiz_question = quiz_gen.generate_question()
248
+ st.session_state.quiz_answer = None
249
+ st.session_state.quiz_submitted = False
250
+ st.session_state.show_explanation = False
251
+
252
+ with col2:
253
+ if st.button("💡 Show Explanation") and st.session_state.quiz_question:
254
+ st.session_state.show_explanation = True
255
+
256
+ with col3:
257
+ if st.button("🔄 Reset Quiz"):
258
+ st.session_state.quiz_question = None
259
+ st.session_state.quiz_answer = None
260
+ st.session_state.quiz_submitted = False
261
+ st.session_state.show_explanation = False
262
+
263
+ if st.session_state.quiz_question:
264
+ question = st.session_state.quiz_question
265
+
266
+ st.markdown("---")
267
+ st.subheader("Question:")
268
+ st.write(question["question"])
269
+
270
+ # Answer input
271
+ user_answer = st.number_input("Your answer:", value=0.0, format="%.2f", key="quiz_input")
272
+
273
+ if st.button("Submit Answer"):
274
+ st.session_state.quiz_answer = user_answer
275
+ st.session_state.quiz_submitted = True
276
+
277
+ # Check answer
278
+ if st.session_state.quiz_submitted and st.session_state.quiz_answer is not None:
279
+ correct_answer = question["answer"]
280
+ tolerance = question["tolerance"]
281
+
282
+ if abs(st.session_state.quiz_answer - correct_answer) <= tolerance:
283
+ st.success(f"✅ Correct! The answer is {correct_answer:.2f}")
284
+ st.balloons()
285
+ else:
286
+ st.error(f"❌ Not quite right. The correct answer is {correct_answer:.2f}")
287
+ st.write(f"Your answer: {st.session_state.quiz_answer:.2f}")
288
+
289
+ # Show explanation
290
+ if st.session_state.show_explanation:
291
+ st.markdown("---")
292
+ st.subheader("💡 Explanation:")
293
+ st.write(question["explanation"])
294
+
295
+ else:
296
+ st.info("Click 'New Question' to start the quiz!")
297
+
298
+ # Educational notes
299
+ st.sidebar.markdown("---")
300
+ st.sidebar.markdown("### 📚 Key Concepts")
301
+ st.sidebar.markdown("""
302
+ - **Magnitude**: Length of the vector
303
+ - **Direction**: Angle the vector makes
304
+ - **Components**: x and y parts of vector
305
+ - **Addition**: Tip-to-tail method
306
+ - **Resultant**: Sum of multiple vectors
307
+ """)
308
+
309
+ st.sidebar.markdown("### 💡 Try This")
310
+ st.sidebar.markdown("""
311
+ 1. Set boat angle to 90° to go straight across
312
+ 2. Increase current speed and watch the path change
313
+ 3. Try launch angles of 45° for maximum range
314
+ 4. Make two vectors perpendicular (90° apart)
315
+ 5. Test the quiz mode to check your understanding
316
+ 6. Change units to see the same physics in different measurements
317
+ """)
318
+
319
+ st.sidebar.markdown("### ⚙️ Features")
320
+ st.sidebar.markdown("""
321
+ - **Unit Conversion**: Change between m/s, km/h, mph, etc.
322
+ - **Error Analysis**: See how measurement uncertainty affects results
323
+ - **Quiz Mode**: Test your vector knowledge
324
+ - **Reset Button**: Restore default values
325
+ """)