shikharyashmaurya commited on
Commit
dfc428f
·
1 Parent(s): cc90294

Added new folder

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. pages/ac-vs-dc.py +369 -0
  2. pages/air-resistance.py +159 -0
  3. pages/angular-momentum.py +114 -0
  4. pages/atomic-structure.py +148 -0
  5. pages/basic-circuit-analysis.py +388 -0
  6. pages/blackbody-radiation.py +85 -0
  7. pages/capacitance.py +65 -0
  8. pages/center-of-gravity.py +83 -0
  9. pages/center-of-mass.py +75 -0
  10. pages/centripetal-force.py +59 -0
  11. pages/chaos-theory.py +50 -0
  12. pages/conservation-of-angular-momentum.py +66 -0
  13. pages/conservation-of-energy.py +137 -0
  14. pages/conservation-of-momentum.py +110 -0
  15. pages/coulombs-law.py +61 -0
  16. pages/diffraction.py +56 -0
  17. pages/dimensional-analysis.py +110 -0
  18. pages/distance.py +142 -0
  19. pages/electic-charge.py +86 -0
  20. pages/electic-field.py +102 -0
  21. pages/electomagnetic-induction.py +298 -0
  22. pages/electric-potential.py +49 -0
  23. pages/electromagnetc-waves.py +42 -0
  24. pages/electromagnetic-spectrum.py +89 -0
  25. pages/energy.py +235 -0
  26. pages/farady-law.py +116 -0
  27. pages/field-concepts.py +131 -0
  28. pages/first-law-of-thermodynamics.py +52 -0
  29. pages/fluid-dynamics.py +46 -0
  30. pages/fluid-statics.py +45 -0
  31. pages/force.py +131 -0
  32. pages/free-fall.py +140 -0
  33. pages/friction.py +115 -0
  34. pages/general-relativity.py +56 -0
  35. pages/gravity.py +161 -0
  36. pages/heat.py +47 -0
  37. pages/ideal-gas-law.py +40 -0
  38. pages/impulse.py +122 -0
  39. pages/inertia.py +111 -0
  40. pages/interference.py +68 -0
  41. pages/kinetic-theory-of-gas.py +91 -0
  42. pages/lenz-law.py +112 -0
  43. pages/light-wave.py +70 -0
  44. pages/magnetic-field.py +258 -0
  45. pages/mass-energy-equivalence.py +42 -0
  46. pages/mass.py +122 -0
  47. pages/maxwells-equations.py +145 -0
  48. pages/mechanical-advantage.py +63 -0
  49. pages/moment-of-inertia.py +78 -0
  50. pages/momentum.py +151 -0
pages/ac-vs-dc.py ADDED
@@ -0,0 +1,369 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+ import io
6
+ from PIL import Image
7
+
8
+ # Set page config
9
+ st.set_page_config(page_title="AC vs DC Electricity Visualization", layout="wide")
10
+
11
+ # Title and description
12
+ st.title("AC vs DC Electricity Visualization")
13
+ st.write("""
14
+ This simulation demonstrates the key differences between Alternating Current (AC) and Direct Current (DC).
15
+ - **AC**: Current periodically changes direction, voltage oscillates between positive and negative values
16
+ - **DC**: Current flows in a single direction, voltage remains constant
17
+ """)
18
+
19
+ # Sidebar controls
20
+ st.sidebar.header("Simulation Controls")
21
+
22
+ # AC parameters
23
+ st.sidebar.subheader("AC Parameters")
24
+ ac_amplitude = st.sidebar.slider("AC Amplitude (Volts)", 1.0, 10.0, 5.0, 0.1)
25
+ ac_frequency = st.sidebar.slider("AC Frequency (Hz)", 0.1, 2.0, 1.0, 0.1)
26
+
27
+ # DC parameters
28
+ st.sidebar.subheader("DC Parameters")
29
+ dc_voltage = st.sidebar.slider("DC Voltage (Volts)", 0.0, 10.0, 5.0, 0.1)
30
+
31
+ # Animation speed
32
+ animation_speed = st.sidebar.slider("Animation Speed", 0.1, 2.0, 1.0, 0.1)
33
+
34
+ # Time parameters
35
+ duration = 5 # seconds to simulate
36
+ fps = 30 # frames per second
37
+
38
+ # Function to create the simulation animation
39
+ def create_animation(ac_amplitude, ac_frequency, dc_voltage, duration, fps, animation_speed):
40
+ # Create figure and axes
41
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 12))
42
+ plt.tight_layout(pad=4.0)
43
+
44
+ # Time array
45
+ t = np.linspace(0, duration, int(duration * fps))
46
+ dt = t[1] - t[0]
47
+
48
+ # Calculate the AC and DC signals
49
+ ac_signal = ac_amplitude * np.sin(2 * np.pi * ac_frequency * t)
50
+ dc_signal = np.ones_like(t) * dc_voltage
51
+
52
+ # Set up plots
53
+ # Voltage plots
54
+ ax1.set_xlim(0, duration)
55
+ ax1.set_ylim(-max(ac_amplitude, dc_voltage) * 1.2, max(ac_amplitude, dc_voltage) * 1.2)
56
+ ax1.set_ylabel('Voltage (V)')
57
+ ax1.set_title('Voltage vs Time')
58
+ ax1.grid(True)
59
+
60
+ ac_line, = ax1.plot([], [], 'r-', label='AC')
61
+ dc_line, = ax1.plot([], [], 'b-', label='DC')
62
+ ax1.legend(loc='upper right')
63
+
64
+ # AC electron flow
65
+ ax2.set_xlim(-1, 1)
66
+ ax2.set_ylim(-1, 1)
67
+ ax2.set_aspect('equal')
68
+ ax2.set_title('AC Electron Flow')
69
+ ax2.grid(True)
70
+ ax2.set_xticks([])
71
+ ax2.set_yticks([])
72
+
73
+ # Draw a wire for AC
74
+ wire_length = 1.8
75
+ wire_thickness = 0.1
76
+ wire_rect = plt.Rectangle((-wire_length/2, -wire_thickness/2), wire_length, wire_thickness,
77
+ fc='gray', ec='black')
78
+ ax2.add_patch(wire_rect)
79
+
80
+ # Electrons for AC
81
+ num_electrons = 20
82
+ ac_electrons = []
83
+ for i in range(num_electrons):
84
+ electron = plt.Circle((0, 0), 0.03, fc='blue', ec='black')
85
+ ax2.add_patch(electron)
86
+ ac_electrons.append(electron)
87
+
88
+ # DC electron flow
89
+ ax3.set_xlim(-1, 1)
90
+ ax3.set_ylim(-1, 1)
91
+ ax3.set_aspect('equal')
92
+ ax3.set_title('DC Electron Flow')
93
+ ax3.grid(True)
94
+ ax3.set_xticks([])
95
+ ax3.set_yticks([])
96
+
97
+ # Draw a wire for DC
98
+ wire_rect = plt.Rectangle((-wire_length/2, -wire_thickness/2), wire_length, wire_thickness,
99
+ fc='gray', ec='black')
100
+ ax3.add_patch(wire_rect)
101
+
102
+ # Electrons for DC
103
+ dc_electrons = []
104
+ for i in range(num_electrons):
105
+ electron = plt.Circle((0, 0), 0.03, fc='blue', ec='black')
106
+ ax3.add_patch(electron)
107
+ dc_electrons.append(electron)
108
+
109
+ # Initialization function for animation
110
+ def init():
111
+ ac_line.set_data([], [])
112
+ dc_line.set_data([], [])
113
+
114
+ # Initialize AC electrons positions
115
+ for i, electron in enumerate(ac_electrons):
116
+ pos = -wire_length/2 + i * wire_length / (num_electrons - 1)
117
+ electron.center = (pos, 0)
118
+
119
+ # Initialize DC electrons positions
120
+ for i, electron in enumerate(dc_electrons):
121
+ pos = -wire_length/2 + i * wire_length / (num_electrons - 1)
122
+ electron.center = (pos, 0)
123
+
124
+ return ac_line, dc_line, *ac_electrons, *dc_electrons
125
+
126
+ # Animation function
127
+ def animate(frame):
128
+ frame = int(frame * animation_speed) % len(t)
129
+ current_time = t[:frame+1]
130
+
131
+ # Update voltage plots
132
+ ac_line.set_data(current_time, ac_signal[:frame+1])
133
+ dc_line.set_data(current_time, dc_signal[:frame+1])
134
+
135
+ # Update AC electron positions
136
+ ac_current_value = ac_signal[frame]
137
+ ac_speed = ac_current_value / ac_amplitude # Normalized speed factor
138
+
139
+ for i, electron in enumerate(ac_electrons):
140
+ # Base position
141
+ base_pos = -wire_length/2 + i * wire_length / (num_electrons - 1)
142
+ # Add oscillation based on AC signal
143
+ oscillation = 0.05 * np.sin(2 * np.pi * ac_frequency * t[frame] - i * np.pi / 10)
144
+ electron.center = (base_pos + oscillation, 0)
145
+
146
+ # Change electron color based on direction
147
+ if ac_current_value > 0:
148
+ electron.set_facecolor('blue')
149
+ else:
150
+ electron.set_facecolor('red')
151
+
152
+ # Update DC electron positions
153
+ for i, electron in enumerate(dc_electrons):
154
+ # Base position plus movement based on time
155
+ pos = (-wire_length/2 + (i + frame/20 * animation_speed) % num_electrons * wire_length / num_electrons)
156
+ if pos > wire_length/2:
157
+ pos = -wire_length/2
158
+ electron.center = (pos, 0)
159
+
160
+ return ac_line, dc_line, *ac_electrons, *dc_electrons
161
+
162
+ # Create animation
163
+ anim = FuncAnimation(fig, animate, frames=len(t), init_func=init, blit=True)
164
+
165
+ plt.tight_layout()
166
+ return anim, fig
167
+
168
+ # Create separate plots for static visualization instead of animation
169
+ def create_static_plots():
170
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
171
+
172
+ # Time array
173
+ t = np.linspace(0, duration, 1000)
174
+
175
+ # Calculate the AC and DC signals
176
+ ac_signal = ac_amplitude * np.sin(2 * np.pi * ac_frequency * t)
177
+ dc_signal = np.ones_like(t) * dc_voltage
178
+
179
+ # AC plot
180
+ ax1.plot(t, ac_signal, 'r-', label=f'AC ({ac_frequency} Hz, {ac_amplitude}V)')
181
+ ax1.set_ylabel('Voltage (V)')
182
+ ax1.set_title('Alternating Current (AC)')
183
+ ax1.grid(True)
184
+ ax1.legend()
185
+
186
+ # DC plot
187
+ ax2.plot(t, dc_signal, 'b-', label=f'DC ({dc_voltage}V)')
188
+ ax2.set_xlabel('Time (s)')
189
+ ax2.set_ylabel('Voltage (V)')
190
+ ax2.set_title('Direct Current (DC)')
191
+ ax2.grid(True)
192
+ ax2.legend()
193
+
194
+ plt.tight_layout()
195
+ return fig
196
+
197
+ # Display static plots
198
+ st.header("Voltage Over Time")
199
+ static_fig = create_static_plots()
200
+ st.pyplot(static_fig)
201
+
202
+ # Visualize electron flow differences
203
+ st.header("Electron Flow Visualization")
204
+ st.write("""
205
+ The visualization below shows how electrons move in AC and DC circuits:
206
+ - In the AC circuit (top), electrons oscillate back and forth, changing direction periodically
207
+ - In the DC circuit (bottom), electrons flow continuously in one direction
208
+ """)
209
+
210
+ # Instead of animation, create a visual representation with multiple frames
211
+ st.write("**Animation visualization replaced with static representation**")
212
+
213
+ # Create frames to show electron movement
214
+ def create_electron_flow_images():
215
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
216
+
217
+ # Set up axes
218
+ for ax, title in zip([ax1, ax2], ['AC Electron Flow', 'DC Electron Flow']):
219
+ ax.set_xlim(-1, 1)
220
+ ax.set_ylim(-1, 1)
221
+ ax.set_aspect('equal')
222
+ ax.set_title(title)
223
+ ax.grid(True)
224
+ ax.set_xticks([])
225
+ ax.set_yticks([])
226
+
227
+ # Draw wires
228
+ wire_length = 1.8
229
+ wire_thickness = 0.1
230
+ wire_rect = plt.Rectangle((-wire_length/2, -wire_thickness/2), wire_length, wire_thickness,
231
+ fc='gray', ec='black')
232
+ ax.add_patch(wire_rect)
233
+
234
+ plt.tight_layout()
235
+ return fig
236
+
237
+ # Show different phases of electron movement
238
+ phases = ['Phase 1: Initial', 'Phase 2: Movement', 'Phase 3: Direction Change (AC only)']
239
+
240
+ for i, phase in enumerate(phases):
241
+ st.subheader(phase)
242
+
243
+ fig = create_electron_flow_images()
244
+ num_electrons = 10
245
+
246
+ # Get the axes from the figure
247
+ ax1, ax2 = fig.axes
248
+
249
+ # Draw electrons for AC
250
+ for j in range(num_electrons):
251
+ # Base position
252
+ base_pos = -0.9 + j * 1.8 / (num_electrons - 1)
253
+
254
+ # Different positions for different phases
255
+ if i == 0: # Initial phase
256
+ pos = base_pos
257
+ color = 'blue'
258
+ elif i == 1: # Movement phase
259
+ pos = base_pos + 0.1 # Slightly moved to the right
260
+ color = 'blue'
261
+ else: # Direction change (for AC)
262
+ pos = base_pos - 0.1 # Now moved to the left
263
+ color = 'red' # Changed color to indicate reversed direction
264
+
265
+ electron = plt.Circle((pos, 0), 0.03, fc=color, ec='black')
266
+ ax1.add_patch(electron)
267
+
268
+ # Draw electrons for DC (always moving in one direction)
269
+ for j in range(num_electrons):
270
+ base_pos = -0.9 + j * 1.8 / (num_electrons - 1)
271
+
272
+ # Different positions for different phases, but always moving right
273
+ if i == 0: # Initial phase
274
+ pos = base_pos
275
+ elif i == 1: # Movement phase
276
+ pos = base_pos + 0.1 # Moved to the right
277
+ else: # Continued movement
278
+ pos = base_pos + 0.2 # Moved further right
279
+
280
+ # If electron moves beyond wire, wrap around
281
+ if pos > 0.9:
282
+ pos = -0.9 + (pos - 0.9)
283
+
284
+ electron = plt.Circle((pos, 0), 0.03, fc='blue', ec='black')
285
+ ax2.add_patch(electron)
286
+
287
+ st.pyplot(fig)
288
+
289
+ # Explain each phase
290
+ if i == 0:
291
+ st.write("Initial state: Electrons are evenly distributed along both wires.")
292
+ elif i == 1:
293
+ st.write("Both AC and DC electrons begin moving to the right as current flows.")
294
+ else:
295
+ st.write("In AC, the current reverses direction periodically (red electrons now moving left). In DC, electrons continue flowing in the same direction (wrapping around when reaching the end).")
296
+
297
+ # Explanations
298
+ st.header("Key Differences Between AC and DC")
299
+
300
+ col1, col2 = st.columns(2)
301
+
302
+ with col1:
303
+ st.subheader("Alternating Current (AC)")
304
+ st.write("""
305
+ - **Direction**: Periodically changes direction
306
+ - **Waveform**: Typically sinusoidal
307
+ - **Voltage**: Oscillates between positive and negative values
308
+ - **Generation**: Produced by alternators and generators
309
+ - **Transmission**: Can be efficiently transmitted over long distances
310
+ - **Usage**: Power grid, household appliances, transformers
311
+ """)
312
+
313
+ with col2:
314
+ st.subheader("Direct Current (DC)")
315
+ st.write("""
316
+ - **Direction**: Flows in one direction only
317
+ - **Waveform**: Constant (or with minimal variation)
318
+ - **Voltage**: Maintains consistent polarity
319
+ - **Generation**: Produced by batteries, solar cells, and power supplies
320
+ - **Transmission**: Loses power over long distances
321
+ - **Usage**: Electronics, batteries, LED lighting, computers
322
+ """)
323
+
324
+ st.header("Real-World Applications")
325
+ st.write("""
326
+ - **AC** is used for power transmission and distribution systems because its voltage can be easily changed using transformers, allowing for efficient transmission over long distances.
327
+ - **DC** is used in electronic devices, mobile phones, computers, and electric vehicles because these devices require a stable, consistent current.
328
+ - Many devices convert AC to DC using adapters or power supplies. Look at your laptop charger - it converts the AC from the wall outlet to the DC your computer needs!
329
+ """)
330
+
331
+ # Advantages and disadvantages
332
+ st.header("Advantages and Disadvantages")
333
+
334
+ adv_col1, adv_col2 = st.columns(2)
335
+
336
+ with adv_col1:
337
+ st.subheader("AC Advantages")
338
+ st.write("""
339
+ - Easy to transform voltage levels
340
+ - Less energy loss in transmission
341
+ - Easy to generate with rotating machines
342
+ - Self-extinguishing arcs (helpful for circuit breakers)
343
+ """)
344
+
345
+ st.subheader("AC Disadvantages")
346
+ st.write("""
347
+ - More complex circuits needed
348
+ - Skin effect in conductors
349
+ - Inductive and capacitive losses
350
+ - Not suitable for sensitive electronics without conversion
351
+ """)
352
+
353
+ with adv_col2:
354
+ st.subheader("DC Advantages")
355
+ st.write("""
356
+ - Simple to understand and use
357
+ - Steady power delivery
358
+ - No frequency considerations
359
+ - Better for electronic components
360
+ - Less dangerous at equal voltages
361
+ """)
362
+
363
+ st.subheader("DC Disadvantages")
364
+ st.write("""
365
+ - Difficult to change voltage levels
366
+ - Higher transmission losses over distance
367
+ - Cannot use transformers directly
368
+ - Difficult to interrupt (arc issues)
369
+ """)
pages/air-resistance.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ class FallingObject:
6
+ def __init__(self, mass, drag_coefficient, cross_sectional_area,
7
+ initial_height, initial_velocity=0, air_density=1.225):
8
+ """
9
+ Simulate a falling object with air resistance
10
+
11
+ Parameters:
12
+ - mass: mass of the object (kg)
13
+ - drag_coefficient: coefficient of drag
14
+ - cross_sectional_area: frontal area of the object (m²)
15
+ - initial_height: starting height of the object (m)
16
+ - initial_velocity: initial velocity (m/s), default is 0
17
+ - air_density: density of air (kg/m³), default is sea level air density
18
+ """
19
+ self.mass = mass
20
+ self.drag_coefficient = drag_coefficient
21
+ self.cross_sectional_area = cross_sectional_area
22
+ self.height = initial_height
23
+ self.velocity = initial_velocity
24
+ self.air_density = air_density
25
+ self.gravity = 9.81 # m/s²
26
+
27
+ def calculate_trajectory(self, time_step=0.01, max_time=10):
28
+ """
29
+ Calculate the trajectory of the falling object
30
+
31
+ Returns:
32
+ - times: list of time points
33
+ - heights: list of corresponding heights
34
+ - velocities: list of corresponding velocities
35
+ """
36
+ times = [0]
37
+ heights = [self.height]
38
+ velocities = [self.velocity]
39
+
40
+ current_time = 0
41
+ current_height = self.height
42
+ current_velocity = self.velocity
43
+
44
+ while current_height > 0 and current_time < max_time:
45
+ # Calculate drag force
46
+ drag_force = 0.5 * self.drag_coefficient * self.air_density * \
47
+ self.cross_sectional_area * abs(current_velocity)**2
48
+
49
+ # Determine drag direction (opposite to velocity)
50
+ drag_direction = 1 if current_velocity > 0 else -1
51
+
52
+ # Calculate net force
53
+ net_force = self.mass * self.gravity - drag_direction * drag_force
54
+
55
+ # Calculate acceleration
56
+ acceleration = net_force / self.mass
57
+
58
+ # Update velocity
59
+ current_velocity += acceleration * time_step
60
+
61
+ # Update height (now decreasing)
62
+ current_height -= current_velocity * time_step
63
+
64
+ # Update time
65
+ current_time += time_step
66
+
67
+ # Store results
68
+ times.append(current_time)
69
+ heights.append(max(0, current_height))
70
+ velocities.append(current_velocity)
71
+
72
+ return times, heights, velocities
73
+
74
+ def plot_trajectory(times, heights, velocities):
75
+ """
76
+ Create a matplotlib figure with two subplots
77
+ """
78
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
79
+
80
+ # Height vs Time plot
81
+ ax1.plot(times, heights, label='Height')
82
+ ax1.set_xlabel('Time (s)')
83
+ ax1.set_ylabel('Height (m)')
84
+ ax1.set_title('Object Height over Time')
85
+ ax1.invert_yaxis() # Invert y-axis to show falling
86
+ ax1.legend()
87
+ ax1.grid(True)
88
+
89
+ # Velocity vs Time plot
90
+ ax2.plot(times, velocities, label='Velocity', color='r')
91
+ ax2.set_xlabel('Time (s)')
92
+ ax2.set_ylabel('Velocity (m/s)')
93
+ ax2.set_title('Velocity over Time')
94
+ ax2.legend()
95
+ ax2.grid(True)
96
+
97
+ return fig
98
+
99
+ def main():
100
+ st.title('Air Resistance Simulation')
101
+
102
+ # Sidebar for input parameters
103
+ st.sidebar.header('Object Properties')
104
+
105
+ # Input sliders
106
+ mass = st.sidebar.slider('Mass (kg)', min_value=0.1, max_value=100.0, value=1.0, step=0.1)
107
+ initial_height = st.sidebar.slider('Initial Height (m)', min_value=1, max_value=500, value=100)
108
+ drag_coefficient = st.sidebar.slider('Drag Coefficient', min_value=0.1, max_value=2.0, value=0.47, step=0.01)
109
+ cross_sectional_area = st.sidebar.slider('Cross-Sectional Area (m²)',
110
+ min_value=0.01, max_value=10.0, value=0.1, step=0.01)
111
+
112
+ # Create falling object
113
+ falling_object = FallingObject(
114
+ mass=mass,
115
+ drag_coefficient=drag_coefficient,
116
+ cross_sectional_area=cross_sectional_area,
117
+ initial_height=initial_height
118
+ )
119
+
120
+ # Calculate trajectory
121
+ times, heights, velocities = falling_object.calculate_trajectory()
122
+
123
+ # Plot results
124
+ fig = plot_trajectory(times, heights, velocities)
125
+ st.pyplot(fig)
126
+
127
+ # Display some key metrics
128
+ st.subheader('Simulation Results')
129
+ col1, col2, col3 = st.columns(3)
130
+
131
+ with col1:
132
+ st.metric('Fall Time', f'{times[-1]:.2f} s')
133
+
134
+ with col2:
135
+ st.metric('Max Velocity', f'{max(abs(v) for v in velocities):.2f} m/s')
136
+
137
+ with col3:
138
+ terminal_velocity = np.sqrt((2 * mass * 9.81) / (drag_coefficient * 1.225 * cross_sectional_area))
139
+ st.metric('Terminal Velocity', f'{terminal_velocity:.2f} m/s')
140
+
141
+ # Explanation
142
+ st.markdown("""
143
+ ### How Air Resistance Works
144
+
145
+ This simulation demonstrates how air resistance affects a falling object:
146
+
147
+ - **Drag Force**: Opposes the motion of the object through the air
148
+ - **Factors Affecting Drag**:
149
+ * Object's mass
150
+ * Drag coefficient
151
+ * Cross-sectional area
152
+ * Air density
153
+
154
+ As the object falls, air resistance increases with velocity, eventually
155
+ leading to terminal velocity where gravitational force equals air resistance.
156
+ """)
157
+
158
+ if __name__ == '__main__':
159
+ main()
pages/angular-momentum.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Title of the app
6
+ st.title("Angular Momentum Visualization")
7
+
8
+ # Let the user choose the type of motion
9
+ motion_type = st.selectbox("Select Motion Type", ["Linear Motion", "Circular Motion"])
10
+
11
+ # Scenario 1: Linear Motion
12
+ if motion_type == "Linear Motion":
13
+ st.subheader("Linear Motion")
14
+ st.write("A particle moves in a straight line. Adjust the initial position and velocity to see how Angular Momentum behaves.")
15
+
16
+ # Input sliders for initial position and velocity
17
+ x0 = st.slider("Initial x-position (x₀)", -5.0, 5.0, 0.0)
18
+ y0 = st.slider("Initial y-position (y₀)", -5.0, 5.0, 0.0)
19
+ v_x = st.slider("Velocity x-component (vₓ)", -2.0, 2.0, 1.0)
20
+ v_y = st.slider("Velocity y-component (vᵧ)", -2.0, 2.0, 0.0)
21
+ t = st.slider("Time (t)", 0.0, 10.0, 0.0)
22
+ m = 1.0 # Mass set to 1 for simplicity
23
+
24
+ # Calculate position at time t
25
+ x = x0 + v_x * t
26
+ y = y0 + v_y * t
27
+
28
+ # Calculate Angular Momentum (z-component)
29
+ L_z = m * (x * v_y - y * v_x)
30
+
31
+ # Create the plot
32
+ fig, ax = plt.subplots()
33
+ ax.set_aspect('equal') # Equal scaling for x and y axes
34
+ ax.set_xlim(-10, 10)
35
+ ax.set_ylim(-10, 10)
36
+ ax.grid(True)
37
+
38
+ # Plot the origin
39
+ ax.plot(0, 0, 'ko', label='Origin')
40
+
41
+ # Plot the particle's path (straight line)
42
+ if v_x != 0 or v_y != 0:
43
+ # Define the line extending beyond plot limits
44
+ s = 100
45
+ x1 = x0 + v_x * (-s)
46
+ y1 = y0 + v_y * (-s)
47
+ x2 = x0 + v_x * s
48
+ y2 = y0 + v_y * s
49
+ ax.plot([x1, x2], [y1, y2], 'g--', label='Path')
50
+
51
+ # Plot position vector (blue)
52
+ ax.arrow(0, 0, x, y, head_width=0.5, head_length=0.5, fc='blue', ec='blue', label='Position Vector')
53
+ # Plot velocity vector (red)
54
+ ax.arrow(x, y, v_x, v_y, head_width=0.5, head_length=0.5, fc='red', ec='red', label='Velocity Vector')
55
+ # Plot particle position
56
+ ax.plot(x, y, 'ro', label='Particle')
57
+
58
+ # Display Angular Momentum
59
+ ax.text(-9, 9, f'L_z = {L_z:.2f}', fontsize=12)
60
+
61
+ # Add legend
62
+ ax.legend(loc='upper right')
63
+ st.pyplot(fig)
64
+
65
+ # Scenario 2: Circular Motion
66
+ elif motion_type == "Circular Motion":
67
+ st.subheader("Circular Motion")
68
+ st.write("A particle moves in a circle around the origin. Adjust the radius and angular velocity to observe Angular Momentum.")
69
+
70
+ # Input sliders for radius and angular velocity
71
+ r = st.slider("Radius (r)", 0.5, 5.0, 2.0)
72
+ omega = st.slider("Angular Velocity (ω)", 0.1, 2.0, 1.0)
73
+ t = st.slider("Time (t)", 0.0, 10.0, 0.0)
74
+ m = 1.0 # Mass set to 1
75
+
76
+ # Calculate position and velocity using circular motion equations
77
+ theta = omega * t
78
+ x = r * np.cos(theta)
79
+ y = r * np.sin(theta)
80
+ v_x = -r * omega * np.sin(theta)
81
+ v_y = r * omega * np.cos(theta)
82
+
83
+ # Calculate Angular Momentum (z-component)
84
+ L_z = m * (x * v_y - y * v_x)
85
+
86
+ # Create the plot
87
+ fig, ax = plt.subplots()
88
+ ax.set_aspect('equal')
89
+ ax.set_xlim(-10, 10)
90
+ ax.set_ylim(-10, 10)
91
+ ax.grid(True)
92
+
93
+ # Plot the origin
94
+ ax.plot(0, 0, 'ko', label='Origin')
95
+
96
+ # Plot the circular path
97
+ theta_vals = np.linspace(0, 2 * np.pi, 100)
98
+ x_path = r * np.cos(theta_vals)
99
+ y_path = r * np.sin(theta_vals)
100
+ ax.plot(x_path, y_path, 'g--', label='Path')
101
+
102
+ # Plot position vector (blue)
103
+ ax.arrow(0, 0, x, y, head_width=0.5, head_length=0.5, fc='blue', ec='blue', label='Position Vector')
104
+ # Plot velocity vector (red)
105
+ ax.arrow(x, y, v_x, v_y, head_width=0.5, head_length=0.5, fc='red', ec='red', label='Velocity Vector')
106
+ # Plot particle position
107
+ ax.plot(x, y, 'ro', label='Particle')
108
+
109
+ # Display Angular Momentum
110
+ ax.text(-9, 9, f'L_z = {L_z:.2f}', fontsize=12)
111
+
112
+ # Add legend
113
+ ax.legend(loc='upper right')
114
+ st.pyplot(fig)
pages/atomic-structure.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import plotly.graph_objects as go
3
+ import numpy as np
4
+
5
+ # Dictionary of elements (atomic number: (name, symbol))
6
+ elements = {
7
+ 1: ("Hydrogen", "H"),
8
+ 2: ("Helium", "He"),
9
+ 3: ("Lithium", "Li"),
10
+ 4: ("Beryllium", "Be"),
11
+ 5: ("Boron", "B"),
12
+ 6: ("Carbon", "C"),
13
+ 7: ("Nitrogen", "N"),
14
+ 8: ("Oxygen", "O"),
15
+ 9: ("Fluorine", "F"),
16
+ 10: ("Neon", "Ne"),
17
+ # Add more elements as needed
18
+ }
19
+
20
+ # Function to calculate electron shells
21
+ def get_electron_shells(electrons):
22
+ shells = []
23
+ n = 1
24
+ while electrons > 0:
25
+ max_electrons = 2 * n**2
26
+ if electrons >= max_electrons:
27
+ shells.append(max_electrons)
28
+ electrons -= max_electrons
29
+ else:
30
+ shells.append(electrons)
31
+ electrons = 0
32
+ n += 1
33
+ return shells
34
+
35
+ # Streamlit app
36
+ st.title("Atomic Structure Visualizer")
37
+
38
+ # User inputs
39
+ col1, col2, col3 = st.columns(3)
40
+ with col1:
41
+ Z = st.number_input("Number of protons (Z)", min_value=1, max_value=118, value=6)
42
+ with col2:
43
+ N = st.number_input("Number of neutrons (N)", min_value=0, value=6)
44
+ with col3:
45
+ E = st.number_input("Number of electrons (E)", min_value=0, value=Z)
46
+
47
+ # Calculate atomic properties
48
+ A = Z + N # Mass number
49
+ charge = Z - E # Charge of the ion
50
+ name, symbol = elements.get(Z, ("Unknown", "?"))
51
+
52
+ # Display atomic information
53
+ st.subheader("Atomic Information")
54
+ st.write(f"**Element**: {name} ({symbol})")
55
+ st.write(f"**Atomic Number**: {Z}")
56
+ st.write(f"**Mass Number**: {A}")
57
+ st.write(f"**Number of Electrons**: {E}")
58
+ st.write(f"**Charge**: {'+' if charge > 0 else ''}{charge}" if charge != 0 else "Neutral atom")
59
+
60
+ # Calculate electron shells
61
+ shells = get_electron_shells(E)
62
+
63
+ # Nucleus parameters
64
+ nucleus_radius = 0.1
65
+
66
+ # Generate proton positions
67
+ proton_theta = np.random.uniform(0, 2 * np.pi, Z)
68
+ proton_r = np.random.uniform(0, nucleus_radius, Z)
69
+ proton_x = proton_r * np.cos(proton_theta)
70
+ proton_y = proton_r * np.sin(proton_theta)
71
+
72
+ # Generate neutron positions
73
+ neutron_theta = np.random.uniform(0, 2 * np.pi, N)
74
+ neutron_r = np.random.uniform(0, nucleus_radius, N)
75
+ neutron_x = neutron_r * np.cos(neutron_theta)
76
+ neutron_y = neutron_r * np.sin(neutron_theta)
77
+
78
+ # Generate electron positions
79
+ electron_x = []
80
+ electron_y = []
81
+ r_shell = 1.0 # Radius increment per shell
82
+ for n, k in enumerate(shells, start=1):
83
+ r = n * r_shell
84
+ angles = np.linspace(0, 2 * np.pi, k, endpoint=False)
85
+ for angle in angles:
86
+ electron_x.append(r * np.cos(angle))
87
+ electron_y.append(r * np.sin(angle))
88
+
89
+ # Create Plotly figure
90
+ fig = go.Figure()
91
+
92
+ # Add protons
93
+ fig.add_trace(go.Scatter(
94
+ x=proton_x, y=proton_y,
95
+ mode='markers',
96
+ name='Protons',
97
+ marker=dict(color='red', size=8)
98
+ ))
99
+
100
+ # Add neutrons
101
+ fig.add_trace(go.Scatter(
102
+ x=neutron_x, y=neutron_y,
103
+ mode='markers',
104
+ name='Neutrons',
105
+ marker=dict(color='blue', size=8)
106
+ ))
107
+
108
+ # Add electrons
109
+ fig.add_trace(go.Scatter(
110
+ x=electron_x, y=electron_y,
111
+ mode='markers',
112
+ name='Electrons',
113
+ marker=dict(color='green', size=10)
114
+ ))
115
+
116
+ # Add shell circles
117
+ for n in range(1, len(shells) + 1):
118
+ fig.add_shape(
119
+ type="circle",
120
+ x0=-n * r_shell, y0=-n * r_shell,
121
+ x1=n * r_shell, y1=n * r_shell,
122
+ line=dict(color="gray", width=1, dash="dash")
123
+ )
124
+
125
+ # Set plot limits and aspect ratio
126
+ max_r = max(0.5, len(shells) * r_shell)
127
+ fig.update_xaxes(range=[-max_r, max_r])
128
+ fig.update_yaxes(range=[-max_r, max_r], scaleanchor="x", scaleratio=1)
129
+
130
+ # Update layout
131
+ fig.update_layout(
132
+ title="Atomic Structure",
133
+ width=600,
134
+ height=600,
135
+ showlegend=True
136
+ )
137
+
138
+ # Display plot in Streamlit
139
+ st.plotly_chart(fig)
140
+
141
+ # Instructions
142
+ st.markdown("""
143
+ ### How to Use
144
+ - **Protons (Z)**: Defines the element (e.g., Z=6 for Carbon).
145
+ - **Neutrons (N)**: Affects the mass number (A = Z + N).
146
+ - **Electrons (E)**: Determines if it's an ion (Charge = Z - E).
147
+ - Adjust the inputs to visualize different atoms or ions!
148
+ """)
pages/basic-circuit-analysis.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.patches import Rectangle, Circle, FancyArrowPatch
5
+
6
+ st.set_page_config(page_title="Circuit Analysis Simulator", layout="wide")
7
+
8
+ st.title("Interactive Circuit Analysis Simulator")
9
+ st.write("Explore basic circuit concepts including Ohm's Law, series and parallel circuits.")
10
+
11
+ # Sidebar for navigation
12
+ page = st.sidebar.radio("Choose a Simulation",
13
+ ["Ohm's Law", "Series Circuit", "Parallel Circuit"])
14
+
15
+ if page == "Ohm's Law":
16
+ st.header("Ohm's Law: V = I × R")
17
+ st.write("""
18
+ Ohm's Law describes the relationship between voltage (V), current (I), and resistance (R).
19
+ Adjust the sliders to see how changing one parameter affects the others.
20
+ """)
21
+
22
+ col1, col2 = st.columns(2)
23
+
24
+ with col1:
25
+ # Let user control two of the variables and calculate the third
26
+ calculation_mode = st.radio(
27
+ "What do you want to calculate?",
28
+ ["Voltage", "Current", "Resistance"]
29
+ )
30
+
31
+ if calculation_mode == "Voltage":
32
+ current = st.slider("Current (A)", 0.1, 10.0, 1.0, 0.1)
33
+ resistance = st.slider("Resistance (Ω)", 1.0, 100.0, 10.0, 1.0)
34
+ voltage = current * resistance
35
+ st.success(f"Calculated Voltage: {voltage:.2f} V")
36
+
37
+ elif calculation_mode == "Current":
38
+ voltage = st.slider("Voltage (V)", 1.0, 220.0, 12.0, 1.0)
39
+ resistance = st.slider("Resistance (Ω)", 1.0, 100.0, 10.0, 1.0)
40
+ current = voltage / resistance if resistance != 0 else float('inf')
41
+ st.success(f"Calculated Current: {current:.2f} A")
42
+
43
+ else: # Resistance
44
+ voltage = st.slider("Voltage (V)", 1.0, 220.0, 12.0, 1.0)
45
+ current = st.slider("Current (A)", 0.1, 10.0, 1.0, 0.1)
46
+ resistance = voltage / current if current != 0 else float('inf')
47
+ st.success(f"Calculated Resistance: {resistance:.2f} Ω")
48
+
49
+ with col2:
50
+ # Create visualization
51
+ fig, ax = plt.subplots(figsize=(8, 6))
52
+
53
+ # Draw battery
54
+ battery_height = 1.5
55
+ battery_x = 1
56
+ battery_y = 3
57
+
58
+ # Long line
59
+ ax.plot([battery_x, battery_x + 6], [battery_y + battery_height, battery_y + battery_height], 'k-', lw=2)
60
+ # Bottom line
61
+ ax.plot([battery_x, battery_x + 6], [battery_y, battery_y], 'k-', lw=2)
62
+
63
+ # Battery
64
+ ax.plot([battery_x, battery_x], [battery_y, battery_y + battery_height], 'k-', lw=2)
65
+ ax.plot([battery_x + 0.2, battery_x + 0.2], [battery_y + 0.25, battery_y + battery_height - 0.25], 'k-', lw=4)
66
+ ax.plot([battery_x + 0.5, battery_x + 0.5], [battery_y + 0.5, battery_y + battery_height - 0.5], 'k-', lw=2)
67
+
68
+ # Resistor (zigzag)
69
+ res_x = battery_x + 3
70
+ res_y = battery_y + battery_height
71
+ zigzag_width = 1.5
72
+ zigzag_height = 0.5
73
+ zigzag_pts = 7
74
+ x_pts = np.linspace(res_x, res_x + zigzag_width, zigzag_pts)
75
+ y_pts = np.array([0, 1, 0, 1, 0, 1, 0]) * zigzag_height + res_y
76
+ ax.plot(x_pts, y_pts, 'k-', lw=2)
77
+
78
+ # Add current direction arrow
79
+ if calculation_mode == "Current" or calculation_mode == "Resistance":
80
+ current_value = current
81
+ else:
82
+ current_value = current
83
+
84
+ # Scale arrow size based on current
85
+ arrow_scale = min(1, current_value / 5)
86
+ arrow_width = 0.15 * (0.5 + arrow_scale)
87
+ arrow = FancyArrowPatch((battery_x + 4.5, battery_y + 0.5),
88
+ (battery_x + 3, battery_y + 0.5),
89
+ arrowstyle='->',
90
+ color='blue',
91
+ lw=2,
92
+ mutation_scale=20)
93
+ ax.add_patch(arrow)
94
+ ax.text(battery_x + 3.8, battery_y + 0.3, f"{current:.1f} A", color='blue')
95
+
96
+ # Add voltage label
97
+ if calculation_mode == "Voltage" or calculation_mode == "Resistance":
98
+ voltage_value = voltage
99
+ else:
100
+ voltage_value = voltage
101
+
102
+ ax.text(battery_x + 0.7, battery_y + 0.75, f"{voltage:.1f} V", color='red')
103
+
104
+ # Add resistance label
105
+ if calculation_mode == "Resistance" or calculation_mode == "Voltage":
106
+ resistance_value = resistance
107
+ else:
108
+ resistance_value = resistance
109
+
110
+ ax.text(res_x + 0.2, res_y + 0.75, f"{resistance:.1f} Ω", color='green')
111
+
112
+ ax.set_xlim(0, 8)
113
+ ax.set_ylim(1, 6)
114
+ ax.axis('equal')
115
+ ax.axis('off')
116
+
117
+ st.pyplot(fig)
118
+
119
+ # Show the formula and calculation
120
+ st.write("### Ohm's Law Formula")
121
+ if calculation_mode == "Voltage":
122
+ st.latex(r"V = I \times R")
123
+ st.latex(f"V = {current:.2f} \, A \times {resistance:.2f} \, \Omega = {voltage:.2f} \, V")
124
+ elif calculation_mode == "Current":
125
+ st.latex(r"I = \frac{V}{R}")
126
+ st.latex(f"I = \\frac{{{voltage:.2f} \, V}}{{{resistance:.2f} \, \Omega}} = {current:.2f} \, A")
127
+ else: # Resistance
128
+ st.latex(r"R = \frac{V}{I}")
129
+ st.latex(f"R = \\frac{{{voltage:.2f} \, V}}{{{current:.2f} \, A}} = {resistance:.2f} \, \Omega")
130
+
131
+ elif page == "Series Circuit":
132
+ st.header("Series Circuit Analysis")
133
+ st.write("""
134
+ In a series circuit, the same current flows through each component, and the total voltage is the sum of the voltages across each component.
135
+ """)
136
+
137
+ col1, col2 = st.columns(2)
138
+
139
+ with col1:
140
+ # User inputs
141
+ voltage_source = st.slider("Voltage Source (V)", 1.0, 24.0, 12.0, 0.1)
142
+
143
+ st.subheader("Resistors in Series")
144
+ num_resistors = st.slider("Number of Resistors", 1, 5, 3)
145
+
146
+ resistances = []
147
+ for i in range(num_resistors):
148
+ r = st.slider(f"R{i+1} (Ω)", 1.0, 100.0, 10.0 * (i+1), 1.0, key=f"series_r{i}")
149
+ resistances.append(r)
150
+
151
+ # Calculations
152
+ total_resistance = sum(resistances)
153
+ current = voltage_source / total_resistance if total_resistance > 0 else 0
154
+ voltage_drops = [current * r for r in resistances]
155
+ power_dissipations = [current**2 * r for r in resistances]
156
+
157
+ st.subheader("Circuit Analysis Results")
158
+ st.write(f"Total Resistance: {total_resistance:.2f} Ω")
159
+ st.write(f"Current: {current:.2f} A")
160
+
161
+ # Display voltage drops and power for each resistor
162
+ for i in range(num_resistors):
163
+ st.write(f"R{i+1}: Voltage Drop = {voltage_drops[i]:.2f} V, Power = {power_dissipations[i]:.2f} W")
164
+
165
+ with col2:
166
+ # Create visualization of series circuit
167
+ fig, ax = plt.subplots(figsize=(8, 6))
168
+
169
+ # Draw circuit
170
+ start_x, start_y = 1, 5
171
+
172
+ # Draw battery
173
+ battery_height = 1.5
174
+ battery_x = start_x
175
+ battery_y = start_y - battery_height/2
176
+
177
+ # Battery
178
+ ax.plot([battery_x, battery_x], [battery_y, battery_y + battery_height], 'k-', lw=2)
179
+ ax.plot([battery_x + 0.2, battery_x + 0.2], [battery_y + 0.25, battery_y + battery_height - 0.25], 'k-', lw=4)
180
+ ax.plot([battery_x + 0.5, battery_x + 0.5], [battery_y + 0.5, battery_y + battery_height - 0.5], 'k-', lw=2)
181
+
182
+ # Label voltage source
183
+ ax.text(battery_x + 0.7, battery_y + 0.75, f"{voltage_source:.1f} V", color='red')
184
+
185
+ # Draw wires and resistors in series
186
+ line_length = 10 / (num_resistors + 1)
187
+
188
+ # Top line from battery to first resistor
189
+ ax.plot([battery_x + 0.5, battery_x + line_length], [battery_y + battery_height - 0.5, battery_y + battery_height - 0.5], 'k-', lw=2)
190
+
191
+ # Draw resistors in series
192
+ for i in range(num_resistors):
193
+ res_x = battery_x + line_length * (i + 1)
194
+ res_y = battery_y + battery_height - 0.5
195
+
196
+ # Draw resistor (zigzag)
197
+ zigzag_width = 1.0
198
+ zigzag_height = 0.5
199
+ zigzag_pts = 7
200
+ x_pts = np.linspace(res_x, res_x + zigzag_width, zigzag_pts)
201
+ y_pts = np.array([0, 1, 0, 1, 0, 1, 0]) * zigzag_height + res_y
202
+ ax.plot(x_pts, y_pts, 'k-', lw=2)
203
+
204
+ # Label resistor
205
+ ax.text(res_x + 0.2, res_y + 0.8, f"R{i+1}\n{resistances[i]:.1f}Ω\n{voltage_drops[i]:.1f}V", fontsize=8)
206
+
207
+ # Connect to next resistor if not the last one
208
+ if i < num_resistors - 1:
209
+ ax.plot([res_x + zigzag_width, res_x + line_length], [res_y, res_y], 'k-', lw=2)
210
+
211
+ # Bottom line back to battery
212
+ ax.plot([battery_x + line_length * (num_resistors + 1) - line_length + zigzag_width, battery_x + 0.5],
213
+ [res_y, battery_y + 0.5], 'k-', lw=2)
214
+
215
+ # Add current direction arrow
216
+ arrow = FancyArrowPatch((battery_x + 0.5, battery_y + 0.5),
217
+ (battery_x + line_length/2, battery_y + 0.5),
218
+ arrowstyle='->',
219
+ color='blue',
220
+ lw=2,
221
+ mutation_scale=15)
222
+ ax.add_patch(arrow)
223
+ ax.text(battery_x + line_length/4, battery_y + 0.2, f"{current:.1f}A", color='blue', fontsize=9)
224
+
225
+ ax.set_xlim(0, 12)
226
+ ax.set_ylim(2, 8)
227
+ ax.axis('equal')
228
+ ax.axis('off')
229
+
230
+ st.pyplot(fig)
231
+
232
+ # Show formulas
233
+ st.write("### Series Circuit Formulas")
234
+ st.latex(r"R_{total} = R_1 + R_2 + ... + R_n")
235
+ st.latex(f"R_{{total}} = {' + '.join([f'{r:.1f}' for r in resistances])} = {total_resistance:.2f} \, \Omega")
236
+ st.latex(r"I = \frac{V_{source}}{R_{total}}")
237
+ st.latex(f"I = \\frac{{{voltage_source:.2f}}}{{{total_resistance:.2f}}} = {current:.2f} \, A")
238
+ st.latex(r"V_n = I \times R_n")
239
+
240
+ elif page == "Parallel Circuit":
241
+ st.header("Parallel Circuit Analysis")
242
+ st.write("""
243
+ In a parallel circuit, each component has the same voltage across it, and the total current is the sum of the currents through each path.
244
+ """)
245
+
246
+ col1, col2 = st.columns(2)
247
+
248
+ with col1:
249
+ # User inputs
250
+ voltage_source = st.slider("Voltage Source (V)", 1.0, 24.0, 12.0, 0.1)
251
+
252
+ st.subheader("Resistors in Parallel")
253
+ num_resistors = st.slider("Number of Resistors", 1, 5, 3)
254
+
255
+ resistances = []
256
+ for i in range(num_resistors):
257
+ r = st.slider(f"R{i+1} (Ω)", 1.0, 100.0, float(10.0 * (i+1)), 1.0, key=f"parallel_r{i}")
258
+ resistances.append(r)
259
+
260
+ # Calculations
261
+ inverse_resistance_sum = sum(1/r for r in resistances) if resistances else 0
262
+ total_resistance = 1/inverse_resistance_sum if inverse_resistance_sum > 0 else float('inf')
263
+
264
+ branch_currents = [voltage_source / r for r in resistances]
265
+ total_current = sum(branch_currents)
266
+ power_dissipations = [(voltage_source ** 2) / r for r in resistances]
267
+ total_power = voltage_source * total_current
268
+
269
+ st.subheader("Circuit Analysis Results")
270
+ st.write(f"Total Resistance: {total_resistance:.2f} Ω")
271
+ st.write(f"Total Current: {total_current:.2f} A")
272
+
273
+ # Display current and power for each resistor
274
+ for i in range(num_resistors):
275
+ st.write(f"R{i+1}: Current = {branch_currents[i]:.2f} A, Power = {power_dissipations[i]:.2f} W")
276
+
277
+ with col2:
278
+ # Create visualization of parallel circuit
279
+ fig, ax = plt.subplots(figsize=(8, 6))
280
+
281
+ # Draw circuit
282
+ start_x, start_y = 1, 5
283
+ end_x = 9
284
+
285
+ # Draw battery
286
+ battery_height = 1.5
287
+ battery_x = start_x
288
+ battery_y = start_y - battery_height/2
289
+
290
+ # Battery
291
+ ax.plot([battery_x, battery_x], [battery_y, battery_y + battery_height], 'k-', lw=2)
292
+ ax.plot([battery_x + 0.2, battery_x + 0.2], [battery_y + 0.25, battery_y + battery_height - 0.25], 'k-', lw=4)
293
+ ax.plot([battery_x + 0.5, battery_x + 0.5], [battery_y + 0.5, battery_y + battery_height - 0.5], 'k-', lw=2)
294
+
295
+ # Label voltage source
296
+ ax.text(battery_x + 0.7, battery_y + 0.75, f"{voltage_source:.1f} V", color='red')
297
+
298
+ # Top horizontal line
299
+ ax.plot([battery_x + 0.5, end_x], [battery_y + battery_height - 0.5, battery_y + battery_height - 0.5], 'k-', lw=2)
300
+
301
+ # Bottom horizontal line
302
+ ax.plot([battery_x + 0.5, end_x], [battery_y + 0.5, battery_y + 0.5], 'k-', lw=2)
303
+
304
+ # Add main current arrow
305
+ arrow = FancyArrowPatch((battery_x + 1.5, battery_y + battery_height - 0.5),
306
+ (battery_x + 2.5, battery_y + battery_height - 0.5),
307
+ arrowstyle='->',
308
+ color='blue',
309
+ lw=2,
310
+ mutation_scale=15)
311
+ ax.add_patch(arrow)
312
+ ax.text(battery_x + 1.5, battery_y + battery_height - 0.8, f"{total_current:.1f}A", color='blue')
313
+
314
+ # Draw resistors in parallel
315
+ spacing = 5.0 / (num_resistors + 1)
316
+ for i in range(num_resistors):
317
+ res_x = battery_x + 2.5 + i * spacing
318
+ res_y_top = battery_y + battery_height - 0.5
319
+ res_y_bottom = battery_y + 0.5
320
+
321
+ # Vertical lines to resistor
322
+ ax.plot([res_x, res_x], [res_y_top, res_y_top - 1], 'k-', lw=2)
323
+ ax.plot([res_x, res_x], [res_y_bottom, res_y_bottom + 1], 'k-', lw=2)
324
+
325
+ # Draw resistor (zigzag horizontal)
326
+ zigzag_height = 1.0
327
+ zigzag_width = 0.5
328
+ zigzag_pts = 7
329
+ y_pts = np.linspace(res_y_top - 1, res_y_bottom + 1, zigzag_pts)
330
+ x_pts = np.array([0, 1, 0, 1, 0, 1, 0]) * zigzag_width + res_x
331
+ ax.plot(x_pts, y_pts, 'k-', lw=2)
332
+
333
+ # Branch current arrow
334
+ branch_arrow = FancyArrowPatch((res_x - 0.3, res_y_top - 0.5),
335
+ (res_x - 0.3, res_y_top - 1.5),
336
+ arrowstyle='->',
337
+ color='blue',
338
+ lw=2,
339
+ mutation_scale=15)
340
+ ax.add_patch(branch_arrow)
341
+
342
+ # Label resistor and current
343
+ ax.text(res_x + 0.6, res_y_top - 1.5, f"R{i+1}\n{resistances[i]:.1f}Ω\n{branch_currents[i]:.2f}A", fontsize=8)
344
+
345
+ ax.set_xlim(0, 12)
346
+ ax.set_ylim(2, 8)
347
+ ax.axis('equal')
348
+ ax.axis('off')
349
+
350
+ st.pyplot(fig)
351
+
352
+ # Show formulas
353
+ st.write("### Parallel Circuit Formulas")
354
+ st.latex(r"\frac{1}{R_{total}} = \frac{1}{R_1} + \frac{1}{R_2} + ... + \frac{1}{R_n}")
355
+
356
+ # Create the fractions part without f-strings
357
+ fractions_parts = []
358
+ for r in resistances:
359
+ fractions_parts.append(r"\frac{1}{" + f"{r:.1f}" + "}")
360
+ fractions_text = " + ".join(fractions_parts)
361
+
362
+ st.latex(r"\frac{1}{R_{total}} = " + fractions_text + f" = {inverse_resistance_sum:.4f}")
363
+ st.latex(r"R_{total} = \frac{1}{" + f"{inverse_resistance_sum:.4f}" + r"} = " + f"{total_resistance:.2f} \, \Omega")
364
+ st.latex(r"I_{total} = I_1 + I_2 + ... + I_n")
365
+ st.latex(f"I_{{total}} = {' + '.join([f'{i:.2f}' for i in branch_currents])} = {total_current:.2f} \, A")
366
+ st.latex(r"I_n = \frac{V}{R_n}")
367
+
368
+ st.sidebar.markdown('''File "D:\projects\physics-simulations\pages\basic-circuit-analysis.py", line 359
369
+ fractions_parts.append(r""\frac{1}{""" + f"{r:.1f}" + r""}""")
370
+ ^
371
+ SyntaxError: unexpected character after line continuation character
372
+ ### Basic Circuit Concepts
373
+
374
+ **Ohm's Law:** V = IR
375
+ - V: Voltage (Volts)
376
+ - I: Current (Amperes)
377
+ - R: Resistance (Ohms)
378
+
379
+ **Series Circuit Properties:**
380
+ - Same current through all components
381
+ - Rtotal = R1 + R2 + ... + Rn
382
+ - Vtotal = V1 + V2 + ... + Vn
383
+
384
+ **Parallel Circuit Properties:**
385
+ - Same voltage across all components
386
+ - 1/Rtotal = 1/R1 + 1/R2 + ... + 1/Rn
387
+ - Itotal = I1 + I2 + ... + In
388
+ ''')
pages/blackbody-radiation.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Physical constants
6
+ h = 6.62607015e-34 # Planck's constant (J·s)
7
+ c = 299792458 # Speed of light (m/s)
8
+ k_B = 1.380649e-23 # Boltzmann constant (J/K)
9
+ b = 2.897e-3 # Wien's displacement constant (m·K)
10
+
11
+ # Planck's law function: Spectral radiance as a function of wavelength (in meters) and temperature
12
+ def planck(lambda_m, T):
13
+ """Calculate spectral radiance B(lambda, T) in W/m²/sr/m."""
14
+ return (2 * h * c**2 / lambda_m**5) / (np.exp(h * c / (lambda_m * k_B * T)) - 1)
15
+
16
+ # Streamlit app
17
+ st.title("Blackbody Radiation Simulation")
18
+
19
+ # Educational explanation
20
+ st.write("""
21
+ ### Understanding Blackbody Radiation
22
+ A **blackbody** is an idealized object that absorbs all incoming radiation and re-emits it based solely on its temperature. The emitted radiation's intensity and wavelength distribution are described by **Planck's law**:
23
+
24
+ \[ B(\lambda, T) = \frac{2hc^2}{\lambda^5} \cdot \frac{1}{e^{\frac{hc}{\lambda k_B T}} - 1} \]
25
+
26
+ - \( \lambda \): Wavelength (m)
27
+ - \( T \): Temperature (K)
28
+ - \( h \): Planck's constant
29
+ - \( c \): Speed of light
30
+ - \( k_B \): Boltzmann constant
31
+
32
+ The **peak wavelength** (\( \lambda_{\text{peak}} \)) shifts with temperature according to **Wien's displacement law**:
33
+
34
+ \[ \lambda_{\text{peak}} = \frac{b}{T} \]
35
+
36
+ where \( b \approx 2.897 \times 10^{-3} \, \text{m·K} \). This simulation plots the spectrum and marks the peak.
37
+ """)
38
+
39
+ # User input
40
+ T = st.slider("Temperature (K)", min_value=300, max_value=10000, value=5000, step=100,
41
+ help="Adjust the temperature to see how the radiation spectrum changes.")
42
+ normalize = st.checkbox("Normalize to maximum", value=True,
43
+ help="Normalize the curve to its peak value for shape comparison.")
44
+
45
+ # Wavelength array (in meters, from 50 nm to 50,000 nm)
46
+ lambda_m = np.linspace(5e-8, 5e-5, 1000)
47
+ # Calculate spectral radiance in W/m²/sr/m
48
+ B_m = planck(lambda_m, T)
49
+ # Convert to nanometers for plotting
50
+ lambda_nm = lambda_m * 1e9
51
+ # Convert spectral radiance to W/m²/sr/nm
52
+ B_nm = B_m / 1e9
53
+
54
+ # Prepare data for plotting
55
+ if normalize:
56
+ B_plot = B_nm / B_nm.max()
57
+ y_label = "Normalized Spectral Radiance"
58
+ else:
59
+ B_plot = B_nm
60
+ y_label = "Spectral Radiance (W/m²/sr/nm)"
61
+
62
+ # Create the plot
63
+ fig, ax = plt.subplots(figsize=(10, 6))
64
+ ax.plot(lambda_nm, B_plot, label=f"T = {T} K")
65
+ ax.set_xlabel("Wavelength (nm)")
66
+ ax.set_ylabel(y_label)
67
+ ax.set_title(f"Blackbody Radiation Spectrum at T = {T} K")
68
+ ax.grid(True)
69
+
70
+ # Highlight visible spectrum (400 nm to 700 nm)
71
+ ax.axvspan(400, 700, color='yellow', alpha=0.3, label="Visible Range")
72
+
73
+ # Calculate and mark peak wavelength
74
+ lambda_peak_m = b / T
75
+ lambda_peak_nm = lambda_peak_m * 1e9
76
+ ax.axvline(lambda_peak_nm, color='red', linestyle='--', label=f"Peak: {lambda_peak_nm:.2f} nm")
77
+
78
+ # Add legend
79
+ ax.legend()
80
+
81
+ # Display the plot in Streamlit
82
+ st.pyplot(fig)
83
+
84
+ # Display peak wavelength
85
+ st.write(f"**Peak wavelength**: {lambda_peak_nm:.2f} nm")
pages/capacitance.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Constants
6
+ epsilon_0 = 8.85e-12 # Permittivity of free space in F/m
7
+
8
+ # Streamlit UI
9
+ st.title("Capacitance Simulation")
10
+ st.markdown("""
11
+ This simulation demonstrates the capacitance of a parallel plate capacitor.
12
+ - **Capacitance (C)** is calculated as \( C = \epsilon_0 \frac{A}{d} \), where \(\epsilon_0 = 8.85 \times 10^{-12} \, \text{F/m}\).
13
+ - **Charge (Q)** is calculated as \( Q = C \times V \).
14
+ Adjust the sliders below to explore how \(A\), \(d\), and \(V\) affect \(C\) and \(Q\).
15
+ """)
16
+
17
+ # Inputs
18
+ A = st.slider("Plate Area (m²)", 0.01, 1.0, 0.1, step=0.01)
19
+ d = st.slider("Plate Separation (m)", 0.001, 0.1, 0.01, step=0.001)
20
+ V = st.slider("Voltage (V)", 0.0, 10.0, 5.0, step=0.1)
21
+
22
+ # Calculations
23
+ C = epsilon_0 * A / d
24
+ Q = C * V
25
+
26
+ # Display results
27
+ st.write(f"**Capacitance (C):** {C:.2e} F")
28
+ st.write(f"**Charge (Q):** {Q:.2e} C")
29
+
30
+ # Visualizations
31
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
32
+
33
+ # Capacitor diagram
34
+ h = 0.05 # Fixed height of plates
35
+ # Scale width based on sqrt(A) for visual representation
36
+ w = 0.1 + 0.4 * (np.sqrt(A) - np.sqrt(0.01)) / (np.sqrt(1) - np.sqrt(0.01))
37
+ # Scale separation based on d
38
+ d_scaled = 0.1 + 0.4 * (d - 0.001) / (0.1 - 0.001)
39
+
40
+ # Draw bottom plate
41
+ ax1.add_patch(plt.Rectangle((0.5 - w/2, 0), w, h, color='blue'))
42
+ # Draw top plate
43
+ ax1.add_patch(plt.Rectangle((0.5 - w/2, d_scaled), w, h, color='red'))
44
+ ax1.set_xlim(0, 1)
45
+ ax1.set_ylim(0, 1)
46
+ ax1.set_aspect('equal')
47
+ ax1.set_title("Parallel Plate Capacitor")
48
+ ax1.axis('off')
49
+ # Add labels
50
+ ax1.text(0.5, -0.1, f'A = {A:.2f} m²', ha='center')
51
+ ax1.text(0.5, d_scaled/2, f'd = {d:.3f} m', ha='center', va='center')
52
+
53
+ # Q vs V plot
54
+ V_range = np.linspace(0, 10, 100)
55
+ Q_range = C * V_range
56
+ ax2.plot(V_range, Q_range, label=f'C = {C:.2e} F')
57
+ ax2.plot(V, Q, 'ro', label='Current point')
58
+ ax2.set_xlabel('Voltage (V)')
59
+ ax2.set_ylabel('Charge (C)')
60
+ ax2.set_title('Q vs V')
61
+ ax2.legend()
62
+ ax2.grid(True)
63
+
64
+ # Display the plot
65
+ st.pyplot(fig)
pages/center-of-gravity.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ from shapely.geometry import Polygon
4
+ import numpy as np
5
+
6
+ # App title and introduction
7
+ st.title("Center of Gravity Visualization")
8
+ st.markdown("""
9
+ The **center of gravity** is the point where the weight of an object is evenly distributed in all directions.
10
+ This simulation lets you explore the center of gravity for different shapes by inputting their parameters.
11
+ """)
12
+
13
+ # Sidebar for shape selection
14
+ st.sidebar.title("Shape Selection")
15
+ shape = st.sidebar.selectbox("Select a shape", ["Rectangle", "Triangle", "Circle", "Custom Polygon"])
16
+
17
+ # Input fields and shape definition based on selection
18
+ if shape == "Rectangle":
19
+ st.markdown("### Rectangle")
20
+ width = st.number_input("Width", min_value=0.0, value=1.0, step=0.1)
21
+ height = st.number_input("Height", min_value=0.0, value=1.0, step=0.1)
22
+ # Define rectangle points starting at (0,0)
23
+ points = [(0, 0), (width, 0), (width, height), (0, height)]
24
+
25
+ elif shape == "Triangle":
26
+ st.markdown("### Triangle")
27
+ st.markdown("Enter the coordinates of the three vertices:")
28
+ x1 = st.number_input("X1", value=0.0, step=0.1)
29
+ y1 = st.number_input("Y1", value=0.0, step=0.1)
30
+ x2 = st.number_input("X2", value=1.0, step=0.1)
31
+ y2 = st.number_input("Y2", value=0.0, step=0.1)
32
+ x3 = st.number_input("X3", value=0.5, step=0.1)
33
+ y3 = st.number_input("Y3", value=1.0, step=0.1)
34
+ points = [(x1, y1), (x2, y2), (x3, y3)]
35
+
36
+ elif shape == "Circle":
37
+ st.markdown("### Circle")
38
+ radius = st.number_input("Radius", min_value=0.0, value=1.0, step=0.1)
39
+ # Approximate circle as a polygon with 100 points
40
+ num_points = 100
41
+ points = [(radius * np.cos(theta), radius * np.sin(theta))
42
+ for theta in np.linspace(0, 2 * np.pi, num_points)]
43
+
44
+ elif shape == "Custom Polygon":
45
+ st.markdown("### Custom Polygon")
46
+ st.markdown("Enter the coordinates of the vertices (one per line, e.g., 'x y') in order:")
47
+ points_str = st.text_area("Points", value="0 0\n1 0\n1 1\n0 1")
48
+ points = []
49
+ for line in points_str.split('\n'):
50
+ if line.strip():
51
+ try:
52
+ x, y = map(float, line.split())
53
+ points.append((x, y))
54
+ except ValueError:
55
+ st.error("Invalid input: Please enter numbers separated by a space (e.g., '1 2').")
56
+ st.stop()
57
+ if len(points) < 3:
58
+ st.error("Need at least 3 points to form a polygon.")
59
+ st.stop()
60
+
61
+ # Create Polygon object and compute centroid
62
+ poly = Polygon(points)
63
+ centroid = poly.centroid
64
+
65
+ # Plotting
66
+ st.markdown(f"### {shape} and its Center of Gravity")
67
+ fig, ax = plt.subplots()
68
+ poly_patch = plt.Polygon(points, fill=False, edgecolor='blue')
69
+ ax.add_artist(poly_patch)
70
+ ax.plot(centroid.x, centroid.y, 'ro', label='Center of Gravity')
71
+ ax.set_aspect('equal') # Ensure the shape isn't distorted
72
+ # Set plot limits with some padding
73
+ xs = [p[0] for p in points]
74
+ ys = [p[1] for p in points]
75
+ min_x, max_x = min(xs), max(xs)
76
+ min_y, max_y = min(ys), max(ys)
77
+ ax.set_xlim(min_x - 1, max_x + 1)
78
+ ax.set_ylim(min_y - 1, max_y + 1)
79
+ ax.legend()
80
+ st.pyplot(fig)
81
+
82
+ # Display centroid coordinates
83
+ st.write(f"**Center of Gravity:** ({centroid.x:.2f}, {centroid.y:.2f})")
pages/center-of-mass.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Sidebar for user input
6
+ st.sidebar.title("Input Particles")
7
+ instructions = """
8
+ Enter particles in the format: mass,x,y
9
+ One particle per line.
10
+ For example:
11
+ 1,0,0
12
+ 2,3,4
13
+ 1.5,2,1
14
+ """
15
+ st.sidebar.markdown(instructions)
16
+
17
+ # Prefill with an example for immediate visualization
18
+ example_input = "1,0,0\n2,3,4\n1.5,2,1"
19
+ input_text = st.sidebar.text_area("Particles", value=example_input, height=200)
20
+
21
+ # Parse the input
22
+ particles = []
23
+ lines = input_text.strip().split('\n')
24
+ for line in lines:
25
+ if line.strip() == '': # Skip empty lines
26
+ continue
27
+ try:
28
+ m, x, y = map(float, line.split(','))
29
+ particles.append((m, x, y))
30
+ except ValueError:
31
+ st.error(f"Invalid input: {line}")
32
+ particles = [] # Reset on error
33
+ break
34
+
35
+ # Process and visualize if there are valid particles
36
+ if particles:
37
+ # Extract masses and coordinates
38
+ masses = np.array([p[0] for p in particles])
39
+ x_coords = np.array([p[1] for p in particles])
40
+ y_coords = np.array([p[2] for p in particles])
41
+ total_mass = np.sum(masses)
42
+
43
+ if total_mass == 0:
44
+ st.write("Total mass is zero. Cannot compute Center of Mass.")
45
+ else:
46
+ # Calculate Center of Mass
47
+ x_com = np.sum(masses * x_coords) / total_mass
48
+ y_com = np.sum(masses * y_coords) / total_mass
49
+
50
+ # Create the plot
51
+ fig, ax = plt.subplots()
52
+ ax.plot(x_coords, y_coords, 'bo', label='Particles') # Blue dots for particles
53
+ ax.plot(x_com, y_com, 'r*', markersize=15, label='Center of Mass') # Red star for COM
54
+ ax.set_xlabel('X')
55
+ ax.set_ylabel('Y')
56
+ ax.set_title("Center of Mass Visualization")
57
+ ax.legend()
58
+ ax.grid(True) # Add grid for readability
59
+ ax.set_aspect('equal') # Equal aspect ratio for accurate distances
60
+
61
+ # Set plot limits dynamically
62
+ if len(particles) > 1:
63
+ x_min, x_max = np.min(x_coords), np.max(x_coords)
64
+ y_min, y_max = np.min(y_coords), np.max(y_coords)
65
+ ax.set_xlim(x_min - 1, x_max + 1)
66
+ ax.set_ylim(y_min - 1, y_max + 1)
67
+ else:
68
+ ax.set_xlim(-5, 5)
69
+ ax.set_ylim(-5, 5)
70
+
71
+ # Display the plot in Streamlit
72
+ st.pyplot(fig)
73
+ st.write(f"Center of Mass: ({x_com:.2f}, {y_com:.2f})")
74
+ else:
75
+ st.write("Please enter at least one particle.")
pages/centripetal-force.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import time
5
+
6
+ st.title("Centripetal Force Simulation")
7
+
8
+ # Sidebar parameters for tuning the simulation
9
+ mass = st.sidebar.slider("Mass (kg)", 0.1, 10.0, 1.0, 0.1)
10
+ speed = st.sidebar.slider("Speed (m/s)", 0.1, 10.0, 5.0, 0.1)
11
+ radius = st.sidebar.slider("Radius (m)", 0.5, 10.0, 3.0, 0.1)
12
+
13
+ # Calculate centripetal force: F = m * v^2 / r
14
+ centripetal_force = mass * (speed ** 2) / radius
15
+ st.sidebar.write("Centripetal Force (N):", round(centripetal_force, 2))
16
+
17
+ st.write("""
18
+ This simulation visualizes an object in uniform circular motion.
19
+ The object follows a circular path and the green arrow indicates the centripetal force,
20
+ pointing inward toward the center of the circle.
21
+ """)
22
+
23
+ if st.button("Start Simulation"):
24
+ plot_area = st.empty() # placeholder for updating plot
25
+
26
+ # Time parameters for simulation
27
+ t = 0.0
28
+ dt = 0.05 # time step in seconds
29
+ simulation_duration = 20 # seconds
30
+
31
+ while t < simulation_duration:
32
+ # Compute angular position (theta = omega * t, where omega = speed/radius)
33
+ theta = (speed / radius) * t
34
+ x = radius * np.cos(theta)
35
+ y = radius * np.sin(theta)
36
+
37
+ # Set up the plot
38
+ fig, ax = plt.subplots(figsize=(6,6))
39
+ # Draw the circular path
40
+ circle = plt.Circle((0, 0), radius, color='blue', fill=False, linestyle='--')
41
+ ax.add_artist(circle)
42
+ # Plot the moving object as a red dot
43
+ ax.plot(x, y, 'ro', markersize=10)
44
+ # Draw the centripetal force arrow (from the object pointing to the center)
45
+ ax.arrow(x, y, -x, -y, head_width=0.2, head_length=0.3, fc='green', ec='green')
46
+
47
+ # Set plot limits and formatting
48
+ ax.set_xlim(-radius - 1, radius + 1)
49
+ ax.set_ylim(-radius - 1, radius + 1)
50
+ ax.set_aspect('equal', 'box')
51
+ ax.set_title("Centripetal Force Simulation")
52
+ ax.set_xlabel("x (m)")
53
+ ax.set_ylabel("y (m)")
54
+
55
+ # Update the plot in the Streamlit app
56
+ plot_area.pyplot(fig)
57
+
58
+ t += dt
59
+ time.sleep(0.05)
pages/chaos-theory.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ from scipy.integrate import odeint
4
+ import matplotlib.pyplot as plt
5
+
6
+ # Define the Lorenz system
7
+ def lorenz(state, t, sigma, beta, rho):
8
+ x, y, z = state
9
+ dx = sigma * (y - x)
10
+ dy = x * (rho - z) - y
11
+ dz = x * y - beta * z
12
+ return [dx, dy, dz]
13
+
14
+ # Title and description
15
+ st.title("Lorenz Attractor - Chaos Theory Visualization")
16
+ st.write(
17
+ "This simulation visualizes the Lorenz attractor, a system that exhibits chaotic behavior. "
18
+ "Adjust the parameters using the sidebar to see how the system's trajectory changes."
19
+ )
20
+
21
+ # Sidebar for interactive parameter controls
22
+ st.sidebar.header("Simulation Parameters")
23
+ sigma = st.sidebar.slider("Sigma (σ)", 0.1, 20.0, 10.0)
24
+ beta = st.sidebar.slider("Beta (β)", 0.1, 10.0, 8.0/3.0, step=0.1)
25
+ rho = st.sidebar.slider("Rho (ρ)", 0.1, 50.0, 28.0, step=0.1)
26
+
27
+ initial_x = st.sidebar.number_input("Initial x", value=1.0)
28
+ initial_y = st.sidebar.number_input("Initial y", value=1.0)
29
+ initial_z = st.sidebar.number_input("Initial z", value=1.0)
30
+
31
+ t_max = st.sidebar.number_input("Simulation Time", value=40.0, min_value=1.0)
32
+ num_points = st.sidebar.number_input("Number of Points", value=10000, min_value=100, step=100)
33
+
34
+ # Time array for simulation
35
+ t = np.linspace(0, t_max, num_points)
36
+
37
+ # Initial state and integration of the Lorenz equations
38
+ state0 = [initial_x, initial_y, initial_z]
39
+ states = odeint(lorenz, state0, t, args=(sigma, beta, rho))
40
+
41
+ # Plotting the Lorenz attractor
42
+ fig = plt.figure(figsize=(10, 6))
43
+ ax = fig.add_subplot(111, projection='3d')
44
+ ax.plot(states[:, 0], states[:, 1], states[:, 2], lw=0.5)
45
+ ax.set_xlabel("X")
46
+ ax.set_ylabel("Y")
47
+ ax.set_zlabel("Z")
48
+ ax.set_title("Lorenz Attractor")
49
+
50
+ st.pyplot(fig)
pages/conservation-of-angular-momentum.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import time
5
+
6
+ # Set initial parameters
7
+ m = 1.0 # mass (kg)
8
+ r0 = 1.0 # initial radius (m)
9
+ ω0 = 1.0 # initial angular velocity (rad/s)
10
+ L = m * r0**2 * ω0 # angular momentum (kg·m²/s, constant)
11
+
12
+ # Streamlit app title and explanation
13
+ st.title("Conservation of Angular Momentum Simulation")
14
+ st.write("""
15
+ This simulation illustrates the conservation of angular momentum. Angular momentum (L) is conserved in the absence of external torque. For a point mass rotating around an axis, \( L = I \cdot \omega \), where \( I = m \cdot r^2 \) is the moment of inertia, \( m \) is the mass, \( r \) is the radius, and \( \omega \) is the angular velocity. Adjust the radius (\( r \)) using the slider below to see how \( \omega \) changes to keep \( L \) constant, similar to an ice skater spinning faster when pulling their arms in.
16
+ """)
17
+
18
+ # Slider for radius r
19
+ r = st.slider("Radius (r)", min_value=0.5, max_value=2.0, value=1.0, step=0.1)
20
+
21
+ # Compute moment of inertia I and angular velocity ω
22
+ I = m * r**2
23
+ ω = L / I
24
+
25
+ # Display current values
26
+ st.write(f"**Mass (m):** {m} kg")
27
+ st.write(f"**Initial radius (r₀):** {r0} m")
28
+ st.write(f"**Initial angular velocity (ω₀):** {ω0} rad/s")
29
+ st.write(f"**Angular momentum (L):** {L} kg·m²/s")
30
+ st.write(f"**Current radius (r):** {r} m")
31
+ st.write(f"**Current moment of inertia (I):** {I} kg·m²")
32
+ st.write(f"**Current angular velocity (ω):** {ω:.2f} rad/s")
33
+ st.write(f"**Current L = I · ω:** {I * ω:.2f} kg·m²/s")
34
+
35
+ # Animation setup
36
+ placeholder = st.empty()
37
+ dt = 0.1 # time step for animation (s)
38
+ num_frames = 100 # number of frames
39
+
40
+ for frame in range(num_frames):
41
+ t = frame * dt
42
+ θ = (ω * t) % (2 * np.pi) # current angle, wrapped to 0-2π
43
+ x = r * np.cos(θ)
44
+ y = r * np.sin(θ)
45
+
46
+ # Create figure
47
+ fig, ax = plt.subplots()
48
+ # Plot the circular path
49
+ circle = plt.Circle((0, 0), r, color='blue', fill=False)
50
+ ax.add_artist(circle)
51
+ # Plot the mass
52
+ ax.plot(x, y, 'ro', markersize=10)
53
+ # Plot a line from center to mass
54
+ ax.plot([0, x], [0, y], 'r-')
55
+ # Set axis limits and aspect
56
+ ax.set_xlim(-2.5, 2.5)
57
+ ax.set_ylim(-2.5, 2.5)
58
+ ax.set_aspect('equal')
59
+ ax.set_title(f"ω = {ω:.2f} rad/s")
60
+
61
+ # Update the placeholder with the new figure
62
+ placeholder.pyplot(fig)
63
+ plt.close(fig) # Free memory
64
+
65
+ # Control animation speed
66
+ time.sleep(0.05)
pages/conservation-of-energy.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.animation as animation
5
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
6
+
7
+ class EnergyConservationSimulation:
8
+ def __init__(self, mass=1.0, gravity=9.8, initial_height=10.0):
9
+ """
10
+ Initialize the simulation parameters
11
+
12
+ :param mass: Mass of the object (kg)
13
+ :param gravity: Acceleration due to gravity (m/s^2)
14
+ :param initial_height: Starting height of the object (m)
15
+ """
16
+ self.mass = mass
17
+ self.gravity = gravity
18
+ self.initial_height = initial_height
19
+
20
+ def calculate_energies(self):
21
+ """
22
+ Calculate potential and kinetic energies at different heights
23
+
24
+ :return: Tuple of lists (heights, potential_energies, kinetic_energies, total_energies)
25
+ """
26
+ # Create array of heights from initial height to 0
27
+ heights = np.linspace(0, self.initial_height, 100)
28
+
29
+ # Calculate potential energy at each height
30
+ potential_energies = self.mass * self.gravity * heights
31
+
32
+ # Calculate kinetic energy at each height (using conservation of energy principle)
33
+ kinetic_energies = self.mass * self.gravity * self.initial_height - potential_energies
34
+
35
+ # Total energy remains constant
36
+ total_energies = np.full_like(heights, self.mass * self.gravity * self.initial_height)
37
+
38
+ return heights, potential_energies, kinetic_energies, total_energies
39
+
40
+ def create_energy_plot(self):
41
+ """
42
+ Create a matplotlib figure showing energy transformations
43
+
44
+ :return: matplotlib figure
45
+ """
46
+ # Calculate energies
47
+ heights, pe, ke, te = self.calculate_energies()
48
+
49
+ # Create the plot
50
+ fig, ax = plt.subplots(figsize=(10, 6))
51
+
52
+ # Plot the energy curves
53
+ ax.plot(heights, pe, label='Potential Energy', color='blue')
54
+ ax.plot(heights, ke, label='Kinetic Energy', color='red')
55
+ ax.plot(heights, te, label='Total Energy', color='green', linestyle='--')
56
+
57
+ # Customize the plot
58
+ ax.set_title('Conservation of Energy: Energy vs Height', fontsize=15)
59
+ ax.set_xlabel('Height (m)', fontsize=12)
60
+ ax.set_ylabel('Energy (J)', fontsize=12)
61
+ ax.legend()
62
+ ax.grid(True, linestyle=':', alpha=0.7)
63
+
64
+ return fig
65
+
66
+ def main():
67
+ # Streamlit app title and description
68
+ st.title('Conservation of Energy Simulation')
69
+ st.write("""
70
+ ## Exploring Energy Transformations
71
+
72
+ This interactive simulation demonstrates the principle of Conservation of Energy
73
+ for a falling object. As the object falls:
74
+ - Potential Energy decreases
75
+ - Kinetic Energy increases
76
+ - Total Energy remains constant
77
+ """)
78
+
79
+ # Sidebar for simulation parameters
80
+ st.sidebar.header('Simulation Parameters')
81
+
82
+ # Mass input
83
+ mass = st.sidebar.slider('Mass of Object (kg)',
84
+ min_value=0.1,
85
+ max_value=10.0,
86
+ value=1.0,
87
+ step=0.1)
88
+
89
+ # Initial height input
90
+ initial_height = st.sidebar.slider('Initial Height (m)',
91
+ min_value=1.0,
92
+ max_value=20.0,
93
+ value=10.0,
94
+ step=0.5)
95
+
96
+ # Gravity (with option to modify)
97
+ gravity = st.sidebar.number_input('Gravity (m/s²)',
98
+ min_value=0.1,
99
+ max_value=20.0,
100
+ value=9.8,
101
+ step=0.1)
102
+
103
+ # Create simulation instance
104
+ sim = EnergyConservationSimulation(
105
+ mass=mass,
106
+ gravity=gravity,
107
+ initial_height=initial_height
108
+ )
109
+
110
+ # Generate and display the plot
111
+ fig = sim.create_energy_plot()
112
+ st.pyplot(fig)
113
+
114
+ # Additional information section
115
+ st.markdown("### Key Insights")
116
+ st.markdown("""
117
+ - 🔵 Blue Line: Potential Energy - Decreases as height decreases
118
+ - 🔴 Red Line: Kinetic Energy - Increases as height decreases
119
+ - 🟢 Green Dashed Line: Total Energy - Remains constant
120
+
121
+ The simulation shows how energy is transformed from potential to kinetic
122
+ energy while maintaining a constant total energy.
123
+ """)
124
+
125
+ # Calculate and display some specific values
126
+ heights, pe, ke, te = sim.calculate_energies()
127
+ col1, col2, col3 = st.columns(3)
128
+
129
+ with col1:
130
+ st.metric("Initial Potential Energy", f"{pe[0]:.2f} J")
131
+ with col2:
132
+ st.metric("Final Kinetic Energy", f"{ke[-1]:.2f} J")
133
+ with col3:
134
+ st.metric("Total Energy", f"{te[0]:.2f} J")
135
+
136
+ if __name__ == "__main__":
137
+ main()
pages/conservation-of-momentum.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import time
5
+
6
+ # Title of the app
7
+ st.title("Conservation of Momentum Simulation")
8
+
9
+ # Sidebar for user inputs
10
+ st.sidebar.title("Parameters")
11
+ m1 = st.sidebar.slider("Mass of Object 1 (kg)", 0.1, 10.0, 1.0, step=0.1)
12
+ m2 = st.sidebar.slider("Mass of Object 2 (kg)", 0.1, 10.0, 1.0, step=0.1)
13
+ u1 = st.sidebar.slider("Initial Velocity of Object 1 (m/s)", -10.0, 10.0, 5.0, step=0.1)
14
+ u2 = st.sidebar.slider("Initial Velocity of Object 2 (m/s)", -10.0, 10.0, -3.0, step=0.1)
15
+ initial_distance = st.sidebar.slider("Initial Distance Between Objects (m)", 1.0, 20.0, 10.0, step=0.1)
16
+
17
+ # Initial positions
18
+ x1 = 0.0 # Object 1 starts at origin
19
+ x2 = initial_distance # Object 2 starts at the initial distance
20
+
21
+ # Determine if and when a collision occurs
22
+ # For x1 < x2, collision happens if u1 > u2 (Object 1 catches up to Object 2)
23
+ if u1 > u2:
24
+ t_collision = (x2 - x1) / (u1 - u2)
25
+ collision_message = f"Time to collision: {t_collision:.2f} s"
26
+ else:
27
+ t_collision = float('inf')
28
+ collision_message = "Objects will not collide with these velocities."
29
+
30
+ st.write(collision_message)
31
+
32
+ # Calculate final velocities after elastic collision (if it occurs)
33
+ if t_collision < float('inf'):
34
+ v1 = (u1 * (m1 - m2) + 2 * m2 * u2) / (m1 + m2)
35
+ v2 = (u2 * (m2 - m1) + 2 * m1 * u1) / (m1 + m2)
36
+ else:
37
+ v1 = u1 # No collision, velocities remain unchanged
38
+ v2 = u2
39
+
40
+ # Simulation parameters
41
+ dt = 0.1 # Time step (s)
42
+ total_time = 10.0 # Total simulation time (s)
43
+
44
+ # Initialize simulation variables
45
+ pos1 = x1
46
+ pos2 = x2
47
+ vel1 = u1
48
+ vel2 = u2
49
+ collided = False
50
+ times = []
51
+ momenta1 = []
52
+ momenta2 = []
53
+ total_momenta = []
54
+
55
+ # Placeholder for the dynamic plot
56
+ placeholder = st.empty()
57
+
58
+ # Run the simulation
59
+ t = 0.0
60
+ while t < total_time:
61
+ # Check for collision
62
+ if not collided and t >= t_collision:
63
+ vel1 = v1
64
+ vel2 = v2
65
+ collided = True
66
+
67
+ # Update positions
68
+ pos1 += vel1 * dt
69
+ pos2 += vel2 * dt
70
+
71
+ # Calculate momenta
72
+ momentum1 = m1 * vel1
73
+ momentum2 = m2 * vel2
74
+ total_momentum = momentum1 + momentum2
75
+
76
+ # Store data for plotting
77
+ times.append(t)
78
+ momenta1.append(momentum1)
79
+ momenta2.append(momentum2)
80
+ total_momenta.append(total_momentum)
81
+
82
+ # Create the visualization
83
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
84
+
85
+ # Top plot: Positions of the objects
86
+ ax1.plot([pos1], [0], 'ro', markersize=10, label='Object 1 (Red)')
87
+ ax1.plot([pos2], [0], 'bo', markersize=10, label='Object 2 (Blue)')
88
+ ax1.set_xlim(-5, initial_distance + 5)
89
+ ax1.set_ylim(-1, 1)
90
+ ax1.set_xlabel('Position (m)')
91
+ ax1.set_title(f'Time: {t:.1f} s')
92
+ ax1.legend()
93
+ ax1.grid(True)
94
+
95
+ # Bottom plot: Momentum over time
96
+ ax2.plot(times, momenta1, 'b-', label='Momentum of Object 1')
97
+ ax2.plot(times, momenta2, 'g-', label='Momentum of Object 2')
98
+ ax2.plot(times, total_momenta, 'r-', label='Total Momentum')
99
+ ax2.set_xlabel('Time (s)')
100
+ ax2.set_ylabel('Momentum (kg m/s)')
101
+ ax2.legend()
102
+ ax2.grid(True)
103
+
104
+ # Update the plot in Streamlit
105
+ placeholder.pyplot(fig)
106
+ plt.close(fig)
107
+
108
+ # Increment time and add a delay for animation
109
+ t += dt
110
+ time.sleep(0.1)
pages/coulombs-law.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+
4
+ # Set up the Streamlit app
5
+ st.title("Coulomb's Law Visualization")
6
+ st.write("Adjust the charges Q1, Q2, and the distance r to see the force between them.")
7
+
8
+ # Input sliders for Q1, Q2, and r
9
+ Q1 = st.slider("Charge Q1", min_value=-10.0, max_value=10.0, value=1.0, step=0.1)
10
+ Q2 = st.slider("Charge Q2", min_value=-10.0, max_value=10.0, value=1.0, step=0.1)
11
+ r = st.slider("Distance r", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
12
+
13
+ # Calculate the force (k=1 for simplicity)
14
+ F = (Q1 * Q2) / r**2
15
+
16
+ # Function to determine color based on charge sign
17
+ def get_color(Q):
18
+ if Q > 0:
19
+ return 'blue' # Positive charge
20
+ elif Q < 0:
21
+ return 'red' # Negative charge
22
+ else:
23
+ return 'gray' # Zero charge
24
+
25
+ # Assign colors to charges
26
+ colors = [get_color(Q1), get_color(Q2)]
27
+
28
+ # Create the plot
29
+ fig, ax = plt.subplots()
30
+ ax.set_xlim(-1, r + 1) # Adjust x-axis based on distance r
31
+ ax.set_ylim(-1, 1) # Fixed y-axis for a 1D representation
32
+
33
+ # Plot the charges as points
34
+ ax.scatter([0, r], [0, 0], c=colors, s=100)
35
+ ax.text(-0.5, 0.5, f'Q1 = {Q1:.1f}') # Label for Q1
36
+ ax.text(r + 0.5, 0.5, f'Q2 = {Q2:.1f}') # Label for Q2
37
+
38
+ # Draw an arrow to represent the force direction on Q1
39
+ if F != 0:
40
+ # If Q1*Q2 > 0 (like charges), force is repulsive (to the left)
41
+ # If Q1*Q2 < 0 (unlike charges), force is attractive (to the right)
42
+ dx = -0.5 if Q1 * Q2 > 0 else 0.5
43
+ ax.quiver(0, 0, dx, 0, scale=1, scale_units='xy', angles='xy', color='green')
44
+
45
+ # Customize the plot
46
+ ax.set_xlabel('Distance')
47
+ ax.set_title("Coulomb's Law")
48
+
49
+ # Display the plot in Streamlit
50
+ st.pyplot(fig)
51
+
52
+ # Display force magnitude and direction
53
+ st.write(f"**Force magnitude:** {abs(F):.2f} units")
54
+ if Q1 * Q2 > 0:
55
+ direction = "to the left (repulsive)"
56
+ elif Q1 * Q2 < 0:
57
+ direction = "to the right (attractive)"
58
+ else:
59
+ direction = "zero"
60
+ st.write(f"**Force direction on Q1:** {direction}")
61
+ st.write("**Note:** Coulomb's constant k is set to 1 for simplicity.")
pages/diffraction.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Title and description
6
+ st.title("Single-Slit Diffraction Simulation")
7
+ st.write("Adjust the parameters below to explore how the diffraction pattern changes.")
8
+
9
+ # Theory section
10
+ st.markdown("### Theory")
11
+ st.write("In single-slit diffraction, light passing through a slit of width \(a\) produces an intensity pattern on a screen at distance \(D\). The intensity as a function of position \(y\) on the screen is given by:")
12
+ st.latex(r"I(y) = I_0 \left( \frac{\sin \beta}{\beta} \right)^2")
13
+ st.write("where")
14
+ st.latex(r"\beta = \frac{\pi a y}{\lambda D}")
15
+ st.write("- \(I_0\): maximum intensity at the center (set to 1 for relative intensity)")
16
+ st.write("- \(\lambda\): wavelength of the light")
17
+ st.write("- \(a\): slit width")
18
+ st.write("- \(D\): distance from slit to screen")
19
+ st.write("- \(y\): position on the screen")
20
+ st.write("The minima (dark bands) occur at:")
21
+ st.latex(r"y = \pm \frac{m \lambda D}{a}, \quad m = 1, 2, 3, \dots")
22
+ st.write("This simulation plots \(I(y)\) vs. \(y\), showing how the pattern spreads or narrows based on your inputs.")
23
+
24
+ # Parameter sliders
25
+ lambda_nm = st.slider("Wavelength λ (nm)", 300, 800, 500, help="Wavelength of light in nanometers (visible range: 400-700 nm)")
26
+ a_um = st.slider("Slit width a (μm)", 10, 1000, 100, help="Width of the slit in micrometers")
27
+ D_m = st.slider("Distance to screen D (m)", 0.5, 5.0, 1.0, help="Distance from slit to screen in meters")
28
+
29
+ # Convert units to meters
30
+ lambda_m = lambda_nm * 1e-9 # nm to m
31
+ a_m = a_um * 1e-6 # μm to m
32
+ D_m = D_m # already in m
33
+
34
+ # Generate position array (y) in meters
35
+ y_m = np.linspace(-0.02, 0.02, 1000) # -20 mm to 20 mm in meters
36
+
37
+ # Compute the diffraction parameter β and intensity I(y)
38
+ z = (a_m * y_m) / (lambda_m * D_m)
39
+ I = (np.sinc(z))**2 # np.sinc(x) = sin(πx)/(πx), and I = [sin(β)/β]^2
40
+
41
+ # Convert y to millimeters for plotting
42
+ y_mm = y_m * 1e3 # m to mm
43
+
44
+ # Create the plot
45
+ fig, ax = plt.subplots(figsize=(10, 6))
46
+ ax.plot(y_mm, I, color='blue')
47
+ ax.set_xlabel("Position on Screen (mm)")
48
+ ax.set_ylabel("Relative Intensity")
49
+ ax.set_ylim(0, 1.05) # Intensity from 0 to just above 1
50
+ ax.grid(True)
51
+ st.pyplot(fig)
52
+
53
+ # Calculate and display the position of the first minimum
54
+ y_min_m = lambda_m * D_m / a_m # in meters
55
+ y_min_mm = y_min_m * 1e3 # in millimeters
56
+ st.write(f"First minimum at y = ± {y_min_mm:.2f} mm")
pages/dimensional-analysis.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pint
3
+ import math
4
+
5
+ # Initialize the unit registry from Pint
6
+ ureg = pint.UnitRegistry()
7
+
8
+ st.title("Dimensional Analysis Visualizer")
9
+ st.markdown("""
10
+ This interactive simulation demonstrates how dimensional analysis works for different physics equations.
11
+ Select an example from the sidebar, adjust the parameters, and see how the units come together to verify that the equation is dimensionally consistent.
12
+ """)
13
+
14
+ # Sidebar to select the example
15
+ option = st.sidebar.selectbox("Select a physics concept",
16
+ ["Gravitational Force", "Pendulum Period", "Kinetic Energy"])
17
+
18
+ if option == "Gravitational Force":
19
+ st.header("Gravitational Force: F = G * m₁ * m₂ / r²")
20
+ st.markdown("""
21
+ **Concept:** The gravitational force between two masses is given by Newton’s law of gravitation.
22
+
23
+ **Units Check:**
24
+ - **G (Gravitational constant):** m³/(kg·s²)
25
+ - **m₁ and m₂ (Masses):** kg
26
+ - **r (Distance):** m
27
+ When you combine these, the result should have the units of force (Newton): kg·m/s².
28
+ """)
29
+ # User inputs for masses and distance
30
+ m1_val = st.number_input("Mass 1 (kg)", value=5.0, min_value=0.0)
31
+ m2_val = st.number_input("Mass 2 (kg)", value=5.0, min_value=0.0)
32
+ r_val = st.number_input("Distance (m)", value=1.0, min_value=0.1)
33
+
34
+ # Define constants and variables with units
35
+ G_val = 6.67430e-11 # Gravitational constant (SI units)
36
+ G = G_val * ureg("meter**3/(kilogram*second**2)")
37
+ m1 = m1_val * ureg.kilogram
38
+ m2 = m2_val * ureg.kilogram
39
+ r = r_val * ureg.meter
40
+
41
+ # Compute gravitational force using dimensional quantities
42
+ F = G * m1 * m2 / (r**2)
43
+
44
+ st.write("**Computed Gravitational Force:**", F)
45
+ st.write("**Dimensionality of F:**", F.dimensionality)
46
+ # Expected dimension for force (Newton)
47
+ expected = ureg.newton
48
+ st.write("**Expected Dimensionality (Newton):**", expected.dimensionality)
49
+ if F.dimensionality == expected.dimensionality:
50
+ st.success("Dimensional analysis checks out!")
51
+ else:
52
+ st.error("There is an inconsistency in the dimensions.")
53
+
54
+ elif option == "Pendulum Period":
55
+ st.header("Pendulum Period: T = 2π √(L/g)")
56
+ st.markdown("""
57
+ **Concept:** The period of a simple pendulum (for small oscillations) depends on its length L and the gravitational acceleration g.
58
+
59
+ **Units Check:**
60
+ - **L (Length):** m
61
+ - **g (Gravitational acceleration):** m/s²
62
+ When you take the square root of (L/g), the result has units of time (s), and multiplying by 2π (a dimensionless constant) keeps the dimension unchanged.
63
+ """)
64
+ # User inputs for pendulum length and gravitational acceleration
65
+ L_val = st.number_input("Length L (m)", value=1.0, min_value=0.0)
66
+ g_val = st.number_input("Gravitational acceleration g (m/s²)", value=9.8, min_value=0.1)
67
+
68
+ L = L_val * ureg.meter
69
+ g = g_val * ureg("meter/second**2")
70
+
71
+ # Calculate the period numerically (2π is dimensionless)
72
+ T_numeric = 2 * math.pi * math.sqrt(L_val / g_val)
73
+ st.write("**Computed Pendulum Period T:**", f"{T_numeric:.3f} seconds")
74
+
75
+ # Dimensional analysis using Pint: sqrt(L/g) must have the dimension of time
76
+ time_dim = (L / g)**0.5
77
+ st.write("**Dimensionality of √(L/g):**", time_dim.dimensionality)
78
+ expected = ureg.second
79
+ st.write("**Expected Dimensionality (second):**", expected.dimensionality)
80
+ if time_dim.dimensionality == expected.dimensionality:
81
+ st.success("Dimensional analysis checks out!")
82
+ else:
83
+ st.error("There is an inconsistency in the dimensions.")
84
+
85
+ elif option == "Kinetic Energy":
86
+ st.header("Kinetic Energy: E = ½ m v²")
87
+ st.markdown("""
88
+ **Concept:** The kinetic energy of a moving object is given by one-half the product of its mass and the square of its velocity.
89
+
90
+ **Units Check:**
91
+ - **m (Mass):** kg
92
+ - **v (Velocity):** m/s
93
+ Therefore, m·v² has units kg·(m²/s²), which are the units of energy (Joule).
94
+ """)
95
+ # User inputs for mass and velocity
96
+ m_val = st.number_input("Mass m (kg)", value=1.0, min_value=0.0)
97
+ v_val = st.number_input("Velocity v (m/s)", value=1.0, min_value=0.0)
98
+
99
+ m = m_val * ureg.kilogram
100
+ v = v_val * ureg("meter/second")
101
+ E = 0.5 * m * (v**2)
102
+
103
+ st.write("**Computed Kinetic Energy E:**", E)
104
+ st.write("**Dimensionality of E:**", E.dimensionality)
105
+ expected = ureg.joule
106
+ st.write("**Expected Dimensionality (Joule):**", expected.dimensionality)
107
+ if E.dimensionality == expected.dimensionality:
108
+ st.success("Dimensional analysis checks out!")
109
+ else:
110
+ st.error("There is an inconsistency in the dimensions.")
pages/distance.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import math
5
+
6
+ def calculate_euclidean_distance(x1, y1, x2, y2):
7
+ """Calculate Euclidean distance between two points."""
8
+ return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
9
+
10
+ def create_distance_plot(x1, y1, x2, y2):
11
+ """Create a matplotlib plot showing distance between two points."""
12
+ fig, ax = plt.subplots(figsize=(8, 6))
13
+ ax.plot(x1, y1, 'ro', label='Point 1') # Red dot for first point
14
+ ax.plot(x2, y2, 'bo', label='Point 2') # Blue dot for second point
15
+ ax.plot([x1, x2], [y1, y2], 'g--', label='Distance') # Green dashed line
16
+ ax.set_title('Distance Between Two Points')
17
+ ax.set_xlabel('X-axis')
18
+ ax.set_ylabel('Y-axis')
19
+ ax.grid(True)
20
+ ax.legend()
21
+ return fig
22
+
23
+ def create_manhattan_distance_plot(x1, y1, x2, y2):
24
+ """Create a matplotlib plot showing Manhattan distance."""
25
+ fig, ax = plt.subplots(figsize=(8, 6))
26
+ ax.plot(x1, y1, 'ro', label='Point 1') # Red dot for first point
27
+ ax.plot(x2, y2, 'bo', label='Point 2') # Blue dot for second point
28
+
29
+ # Draw Manhattan distance path
30
+ ax.plot([x1, x2], [y1, y1], 'g--') # Horizontal path
31
+ ax.plot([x2, x2], [y1, y2], 'g--') # Vertical path
32
+
33
+ ax.set_title('Manhattan Distance Visualization')
34
+ ax.set_xlabel('X-axis')
35
+ ax.set_ylabel('Y-axis')
36
+ ax.grid(True)
37
+ ax.legend()
38
+ return fig
39
+
40
+ def create_distance_over_time_plot(velocity, time):
41
+ """Create a matplotlib plot showing distance over time."""
42
+ fig, ax = plt.subplots(figsize=(10, 6))
43
+
44
+ # Distance vs Time plot
45
+ t = np.linspace(0, time, 100)
46
+ d = velocity * t
47
+
48
+ ax.plot(t, d, 'b-', label='Distance')
49
+ ax.set_title('Distance Traveled vs Time')
50
+ ax.set_xlabel('Time (seconds)')
51
+ ax.set_ylabel('Distance (units)')
52
+ ax.grid(True)
53
+ ax.legend()
54
+
55
+ # Annotate final distance
56
+ distance = velocity * time
57
+ ax.annotate(f'Final Distance: {distance} units',
58
+ xy=(time, distance),
59
+ xytext=(time/2, distance/2),
60
+ arrowprops=dict(facecolor='black', shrink=0.05))
61
+
62
+ return fig
63
+
64
+ def main():
65
+ st.title('Distance Visualization')
66
+
67
+ # Sidebar for navigation
68
+ app_mode = st.sidebar.selectbox('Choose Visualization Mode',
69
+ ['Euclidean Distance', 'Manhattan Distance', 'Distance Over Time'])
70
+
71
+ if app_mode == 'Euclidean Distance':
72
+ st.header('Euclidean Distance Calculator')
73
+
74
+ # Input for coordinates
75
+ col1, col2 = st.columns(2)
76
+ with col1:
77
+ x1 = st.number_input('X1 Coordinate', value=0.0, key='x1_euc')
78
+ y1 = st.number_input('Y1 Coordinate', value=0.0, key='y1_euc')
79
+
80
+ with col2:
81
+ x2 = st.number_input('X2 Coordinate', value=3.0, key='x2_euc')
82
+ y2 = st.number_input('Y2 Coordinate', value=4.0, key='y2_euc')
83
+
84
+ # Calculate distance
85
+ distance = calculate_euclidean_distance(x1, y1, x2, y2)
86
+
87
+ # Display results
88
+ st.write(f'Euclidean Distance: {distance:.2f} units')
89
+
90
+ # Create and display plot
91
+ fig = create_distance_plot(x1, y1, x2, y2)
92
+ st.pyplot(fig)
93
+ plt.close(fig)
94
+
95
+ elif app_mode == 'Manhattan Distance':
96
+ st.header('Manhattan Distance Calculator')
97
+
98
+ # Input for coordinates
99
+ col1, col2 = st.columns(2)
100
+ with col1:
101
+ x1 = st.number_input('X1 Coordinate', value=0.0, key='x1_man')
102
+ y1 = st.number_input('Y1 Coordinate', value=0.0, key='y1_man')
103
+
104
+ with col2:
105
+ x2 = st.number_input('X2 Coordinate', value=3.0, key='x2_man')
106
+ y2 = st.number_input('Y2 Coordinate', value=4.0, key='y2_man')
107
+
108
+ # Calculate Manhattan distance
109
+ manhattan_distance = abs(x2 - x1) + abs(y2 - y1)
110
+
111
+ # Display results
112
+ st.write(f'Manhattan Distance: {manhattan_distance:.2f} units')
113
+
114
+ # Create Manhattan distance visualization
115
+ fig = create_manhattan_distance_plot(x1, y1, x2, y2)
116
+ st.pyplot(fig)
117
+ plt.close(fig)
118
+
119
+ elif app_mode == 'Distance Over Time':
120
+ st.header('Distance Traveled Over Time')
121
+
122
+ # Velocity input
123
+ velocity = st.slider('Velocity (units/second)', 1, 20, 5)
124
+
125
+ # Time input
126
+ time = st.slider('Time (seconds)', 1, 60, 10)
127
+
128
+ # Calculate distance
129
+ distance = velocity * time
130
+
131
+ # Visualization
132
+ fig = create_distance_over_time_plot(velocity, time)
133
+ st.pyplot(fig)
134
+ plt.close(fig)
135
+
136
+ # Display numerical results
137
+ st.write(f'Velocity: {velocity} units/second')
138
+ st.write(f'Time: {time} seconds')
139
+ st.write(f'Total Distance: {distance} units')
140
+
141
+ if __name__ == '__main__':
142
+ main()
pages/electic-charge.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import plotly.graph_objects as go
3
+ import numpy as np
4
+
5
+ # Initialize session state to store charges
6
+ if 'charges' not in st.session_state:
7
+ st.session_state.charges = []
8
+
9
+ # User interface
10
+ st.title("Electric Charge Simulation")
11
+ st.write("Add charges and visualize the forces between them.")
12
+
13
+ # Input fields for adding a charge
14
+ x = st.number_input("X position", min_value=-10.0, max_value=10.0, value=0.0)
15
+ y = st.number_input("Y position", min_value=-10.0, max_value=10.0, value=0.0)
16
+ q = st.selectbox("Charge", [1, -1])
17
+
18
+ # Button to add a charge
19
+ if st.button("Add Charge"):
20
+ st.session_state.charges.append({'x': x, 'y': y, 'q': q})
21
+
22
+ # Button to clear all charges
23
+ if st.button("Clear All Charges"):
24
+ st.session_state.charges = []
25
+
26
+ # Slider to adjust force arrow scale
27
+ force_scale = st.slider("Force scale", min_value=0.1, max_value=10.0, value=1.0)
28
+
29
+ # Create Plotly figure
30
+ fig = go.Figure()
31
+
32
+ # Plot all charges
33
+ for charge in st.session_state.charges:
34
+ fig.add_trace(go.Scatter(
35
+ x=[charge['x']],
36
+ y=[charge['y']],
37
+ mode='markers+text',
38
+ text=[str(charge['q'])],
39
+ textposition='top right',
40
+ marker=dict(size=10, color='red' if charge['q'] > 0 else 'blue')
41
+ ))
42
+
43
+ # Calculate and plot forces
44
+ k = 1.0 # Coulomb's constant (simplified for visualization)
45
+ for charge in st.session_state.charges:
46
+ Fx, Fy = 0, 0
47
+ for other in st.session_state.charges:
48
+ if other != charge: # Skip self-interaction
49
+ dx = other['x'] - charge['x']
50
+ dy = other['y'] - charge['y']
51
+ r = np.sqrt(dx**2 + dy**2)
52
+ if r > 0: # Avoid division by zero
53
+ # Force magnitude: F = k * q1 * q2 / r^2
54
+ F = k * charge['q'] * other['q'] / (r**2)
55
+ # Force components along unit vector from other to charge
56
+ Fx += F * (-dx / r)
57
+ Fy += F * (-dy / r)
58
+ # Draw force arrow
59
+ arrow_dx = Fx * force_scale
60
+ arrow_dy = Fy * force_scale
61
+ if np.sqrt(arrow_dx**2 + arrow_dy**2) > 0: # Only draw if force exists
62
+ fig.add_annotation(
63
+ x=charge['x'] + arrow_dx,
64
+ y=charge['y'] + arrow_dy,
65
+ ax=charge['x'],
66
+ ay=charge['y'],
67
+ xref='x', yref='y', axref='x', ayref='y',
68
+ showarrow=True,
69
+ arrowhead=2,
70
+ arrowsize=1,
71
+ arrowwidth=2,
72
+ arrowcolor='green'
73
+ )
74
+
75
+ # Configure plot layout
76
+ fig.update_layout(
77
+ xaxis_range=[-10, 10],
78
+ yaxis_range=[-10, 10],
79
+ width=600,
80
+ height=600,
81
+ showlegend=False,
82
+ title="Charge Interactions"
83
+ )
84
+
85
+ # Display the plot in Streamlit
86
+ st.plotly_chart(fig)
pages/electic-field.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Title of the Streamlit app
6
+ st.title("Electric Field Visualization")
7
+ st.write("Add charges to see how the electric field changes! Positive charges are red, negative charges are blue.")
8
+
9
+ # Initialize session state to store charges
10
+ if 'charges' not in st.session_state:
11
+ st.session_state.charges = []
12
+
13
+ # Function to calculate the electric field on a grid
14
+ def calculate_electric_field(charges, X, Y, k=1, epsilon=1e-6):
15
+ """
16
+ Calculate the electric field components (E_x, E_y) at each point on the grid.
17
+
18
+ Parameters:
19
+ - charges: List of dictionaries with 'x', 'y', and 'q' for each charge
20
+ - X, Y: 2D arrays from np.meshgrid representing grid coordinates
21
+ - k: Coulomb's constant (set to 1 for simplicity)
22
+ - epsilon: Small value to avoid division by zero at charge locations
23
+
24
+ Returns:
25
+ - E_x, E_y: Electric field components as 2D arrays
26
+ """
27
+ E_x_total = np.zeros_like(X)
28
+ E_y_total = np.zeros_like(Y)
29
+ for charge in charges:
30
+ dx = X - charge['x']
31
+ dy = Y - charge['y']
32
+ r = np.sqrt(dx**2 + dy**2 + epsilon) # Distance with epsilon to avoid singularity
33
+ # E = k * q * r̂ / r^2, where r̂ is the unit vector (dx/r, dy/r)
34
+ # So, E_x = k * q * dx / r^3, E_y = k * q * dy / r^3
35
+ E_x_charge = k * charge['q'] * dx / r**3
36
+ E_y_charge = k * charge['q'] * dy / r**3
37
+ E_x_total += E_x_charge
38
+ E_y_total += E_y_charge
39
+ return E_x_total, E_y_total
40
+
41
+ # UI to add a charge
42
+ st.subheader("Add a Charge")
43
+ x = st.slider("X position", -5.0, 5.0, 0.0, key="x_add")
44
+ y = st.slider("Y position", -5.0, 5.0, 0.0, key="y_add")
45
+ q = st.selectbox("Charge", [1, -1], format_func=lambda val: "+1" if val > 0 else "-1", key="q_add")
46
+ if st.button("Add Charge", key="add_charge"):
47
+ st.session_state.charges.append({'x': x, 'y': y, 'q': q})
48
+ st.success(f"Added charge q={q} at ({x}, {y})")
49
+
50
+ # UI to display and remove charges
51
+ st.subheader("Current Charges")
52
+ if st.session_state.charges:
53
+ for i, charge in enumerate(st.session_state.charges):
54
+ st.write(f"Charge {i}: x={charge['x']}, y={charge['y']}, q={'+' if charge['q'] > 0 else '-'}1")
55
+
56
+ # Select and remove a charge
57
+ charge_to_remove = st.selectbox(
58
+ "Select charge to remove",
59
+ options=range(len(st.session_state.charges)),
60
+ format_func=lambda i: f"Charge {i}: x={st.session_state.charges[i]['x']}, y={st.session_state.charges[i]['y']}, q={'+' if st.session_state.charges[i]['q'] > 0 else '-'}1",
61
+ key="charge_to_remove"
62
+ )
63
+ if st.button("Remove Selected Charge", key="remove_charge"):
64
+ del st.session_state.charges[charge_to_remove]
65
+ st.success("Charge removed!")
66
+ else:
67
+ st.write("No charges added yet. Add some to see the electric field.")
68
+
69
+ # Create the plot if there are charges
70
+ if st.session_state.charges:
71
+ # Define the grid for calculation
72
+ x_grid = np.linspace(-5, 5, 20)
73
+ y_grid = np.linspace(-5, 5, 20)
74
+ X, Y = np.meshgrid(x_grid, y_grid)
75
+
76
+ # Calculate the electric field
77
+ E_x, E_y = calculate_electric_field(st.session_state.charges, X, Y)
78
+
79
+ # Create the plot
80
+ fig, ax = plt.subplots(figsize=(8, 8))
81
+ ax.streamplot(X, Y, E_x, E_y, color='black', linewidth=1, density=1.5)
82
+
83
+ # Plot each charge
84
+ for charge in st.session_state.charges:
85
+ color = 'red' if charge['q'] > 0 else 'blue'
86
+ ax.plot(charge['x'], charge['y'], 'o', color=color, markersize=10, label=f"q={charge['q']}")
87
+
88
+ # Set plot limits and labels
89
+ ax.set_xlim(-5, 5)
90
+ ax.set_ylim(-5, 5)
91
+ ax.set_xlabel("X")
92
+ ax.set_ylabel("Y")
93
+ ax.set_title("Electric Field Lines")
94
+ ax.grid(True)
95
+
96
+ # Display the plot in Streamlit
97
+ st.pyplot(fig)
98
+ else:
99
+ st.info("Add some charges above to visualize the electric field!")
100
+
101
+ # Footer
102
+ st.write("Note: The field lines point away from positive charges and toward negative charges, following the direction a positive test charge would move.")
pages/electomagnetic-induction.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+ import matplotlib.patches as patches
6
+ from matplotlib.collections import PatchCollection
7
+ import matplotlib.gridspec as gridspec
8
+ from io import BytesIO
9
+ import base64
10
+
11
+ # Set page config
12
+ st.set_page_config(
13
+ page_title="Electromagnetic Induction Simulator",
14
+ layout="wide"
15
+ )
16
+
17
+ # Title and description
18
+ st.title("Electromagnetic Induction Simulator")
19
+ st.markdown("""
20
+ This simulation demonstrates the principle of electromagnetic induction, where a changing magnetic field
21
+ induces an electric current in a conductor. You can adjust various parameters to see how they affect the induced voltage.
22
+ """)
23
+
24
+ # Create two columns for controls and visualization
25
+ col1, col2 = st.columns([1, 3])
26
+
27
+ # Control parameters
28
+ with col1:
29
+ st.subheader("Controls")
30
+
31
+ # Magnet properties
32
+ st.markdown("### Magnet Properties")
33
+ magnet_strength = st.slider("Magnetic Field Strength (T)", 0.1, 2.0, 1.0, 0.1)
34
+ magnet_speed = st.slider("Magnet Speed", 0.5, 3.0, 1.5, 0.1)
35
+ magnet_direction = st.radio("Magnet Motion", ["Moving In", "Moving Out", "Oscillating"])
36
+
37
+ # Coil properties
38
+ st.markdown("### Coil Properties")
39
+ coil_turns = st.slider("Number of Coil Turns", 5, 50, 20, 5)
40
+ coil_radius = st.slider("Coil Radius (cm)", 2.0, 8.0, 5.0, 0.5)
41
+ coil_resistance = st.slider("Coil Resistance (Ω)", 1.0, 20.0, 10.0, 1.0)
42
+
43
+ # Main visualization
44
+ with col2:
45
+ st.markdown("### Simulation")
46
+
47
+ # Create a placeholder for our animation
48
+ plot_placeholder = st.empty()
49
+
50
+ # Create a static visualization that updates with parameters
51
+ fig = plt.figure(figsize=(10, 8))
52
+ gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])
53
+
54
+ # Top plot: Physical setup
55
+ ax1 = fig.add_subplot(gs[0])
56
+ ax1.set_xlim(-10, 10)
57
+ ax1.set_ylim(-10, 10)
58
+ ax1.set_aspect('equal')
59
+ ax1.set_title('Magnet and Coil Configuration')
60
+ ax1.set_xlabel('Position (cm)')
61
+ ax1.set_ylabel('Position (cm)')
62
+
63
+ # Draw the coil (circle)
64
+ coil = plt.Circle((0, 0), coil_radius, fill=False, color='black', linewidth=2)
65
+ ax1.add_artist(coil)
66
+
67
+ # Add coil turns visualization
68
+ theta = np.linspace(0, 2*np.pi, coil_turns+1)[:-1]
69
+ coil_points_x = coil_radius * np.cos(theta)
70
+ coil_points_y = coil_radius * np.sin(theta)
71
+
72
+ for i in range(coil_turns):
73
+ ax1.plot(coil_points_x[i], coil_points_y[i], 'ko', markersize=3)
74
+
75
+ # Initial magnet position depends on the selected motion
76
+ if magnet_direction == "Moving In":
77
+ magnet_pos = -9 # Start far left
78
+ elif magnet_direction == "Moving Out":
79
+ magnet_pos = 0 # Start at center
80
+ else: # Oscillating
81
+ magnet_pos = -5 # Start slightly to the left
82
+
83
+ # Draw the magnet (rectangle)
84
+ magnet_width, magnet_height = 2, 4
85
+ magnet = patches.Rectangle((magnet_pos-magnet_width/2, -magnet_height/2),
86
+ magnet_width, magnet_height,
87
+ linewidth=1, edgecolor='r', facecolor='r', alpha=0.7)
88
+ ax1.add_patch(magnet)
89
+
90
+ # Add N and S labels to the magnet
91
+ ax1.text(magnet_pos, 1, "N", fontsize=12, ha='center', color='white', fontweight='bold')
92
+ ax1.text(magnet_pos, -1, "S", fontsize=12, ha='center', color='white', fontweight='bold')
93
+
94
+ # Add magnetic field lines
95
+ def plot_magnetic_field(pos_x, strength):
96
+ # Clear previous field lines
97
+ for line in ax1.lines[:]:
98
+ if line not in coil_points:
99
+ ax1.lines.remove(line)
100
+
101
+ # Field strength indicator by number and spread of field lines
102
+ num_field_lines = int(strength * 5)
103
+
104
+ # Field around the magnet
105
+ for i in range(-num_field_lines, num_field_lines+1, 2):
106
+ y_offset = i * 0.3
107
+
108
+ # Field lines from N to S pole (outside the magnet)
109
+ if abs(y_offset) > magnet_height/2:
110
+ x = np.linspace(pos_x, pos_x, 20)
111
+ y = np.linspace(magnet_height/2, -magnet_height/2, 20)
112
+ ax1.plot(x, y + y_offset, 'b-', linewidth=0.7, alpha=0.6)
113
+
114
+ # Field lines extending outward (further out based on strength)
115
+ extent = 2 + strength * 3
116
+
117
+ # Top field lines (from North pole)
118
+ x_top = np.linspace(pos_x - extent, pos_x + extent, 20)
119
+ y_top = np.zeros_like(x_top)
120
+ y_curvature = 4 * strength * (1 - ((x_top - pos_x) / extent) ** 2)
121
+ ax1.plot(x_top, magnet_height/2 + y_curvature, 'b-', linewidth=0.7, alpha=0.6)
122
+
123
+ # Bottom field lines (from South pole)
124
+ x_bottom = np.linspace(pos_x - extent, pos_x + extent, 20)
125
+ y_bottom = np.zeros_like(x_bottom)
126
+ y_curvature = 4 * strength * (1 - ((x_bottom - pos_x) / extent) ** 2)
127
+ ax1.plot(x_bottom, -magnet_height/2 - y_curvature, 'b-', linewidth=0.7, alpha=0.6)
128
+
129
+ # Store coil points reference
130
+ coil_points = ax1.lines.copy()
131
+
132
+ # Initialize magnetic field lines
133
+ plot_magnetic_field(magnet_pos, magnet_strength)
134
+
135
+ # Add a current indicator around the coil
136
+ current_indicator = ax1.text(0, coil_radius + 1.5, "Current: 0 mA",
137
+ ha='center', fontsize=10, bbox=dict(facecolor='white', alpha=0.5))
138
+
139
+ # Bottom plot: Induced voltage over time
140
+ ax2 = fig.add_subplot(gs[1])
141
+ ax2.set_xlim(0, 100)
142
+ ax2.set_ylim(-10, 10)
143
+ ax2.set_title('Induced Voltage vs. Time')
144
+ ax2.set_xlabel('Time')
145
+ ax2.set_ylabel('Voltage (mV)')
146
+ ax2.grid(True)
147
+
148
+ # Initialize voltage data
149
+ voltage_data = np.zeros(100)
150
+ time_data = np.arange(100)
151
+ voltage_line, = ax2.plot(time_data, voltage_data, 'g-')
152
+
153
+ # Function to calculate induced voltage based on parameters
154
+ def calculate_induced_voltage(pos, prev_pos, strength, turns, radius):
155
+ # Flux change calculation based on position change
156
+ area = np.pi * (radius ** 2)
157
+
158
+ # Distance from coil center affects field strength (inverse square law approximation)
159
+ distance_factor = max(0.1, 1 / (1 + abs(pos) ** 2))
160
+
161
+ # Flux through coil
162
+ current_flux = strength * area * distance_factor
163
+
164
+ # Flux from previous position
165
+ prev_distance_factor = max(0.1, 1 / (1 + abs(prev_pos) ** 2))
166
+ previous_flux = strength * area * prev_distance_factor
167
+
168
+ # Change in flux
169
+ d_flux = current_flux - previous_flux
170
+
171
+ # Induced voltage (Faraday's law: E = -N * dΦ/dt)
172
+ # We approximate dt as 1 time unit
173
+ induced_voltage = -turns * d_flux
174
+
175
+ # Add sign based on approach/retreat
176
+ if magnet_direction == "Moving In":
177
+ if pos < 0: # Approaching from left
178
+ induced_voltage *= -1
179
+ elif magnet_direction == "Moving Out":
180
+ if pos > 0: # Moving away to the right
181
+ induced_voltage *= -1
182
+
183
+ return induced_voltage
184
+
185
+ # Function to simulate current direction
186
+ def show_current_direction(voltage, ax, coil_r):
187
+ current = voltage / coil_resistance # I = V/R
188
+
189
+ # Update current text
190
+ current_indicator.set_text(f"Current: {current*1000:.2f} mA")
191
+
192
+ # Remove any existing current direction indicators
193
+ for artist in ax.artists:
194
+ if hasattr(artist, 'current_indicator'):
195
+ artist.remove()
196
+
197
+ # If current is significant, show direction
198
+ if abs(current) > 0.01:
199
+ # Define arrow properties based on current direction
200
+ if current > 0:
201
+ color = 'g'
202
+ rotation = 90 # Clockwise
203
+ else:
204
+ color = 'r'
205
+ rotation = -90 # Counter-clockwise
206
+
207
+ # Current magnitude affects arrow size
208
+ arrow_size = min(1.5, max(0.5, abs(current) * 10))
209
+
210
+ # Add arrow around the coil
211
+ arrow = patches.FancyArrowPatch((-coil_r, 0), (coil_r, 0),
212
+ connectionstyle=f"arc3,rad={rotation/180}",
213
+ arrowstyle=f"fancy,head_width={arrow_size},head_length={arrow_size*1.5}",
214
+ fc=color, ec=color, alpha=0.7)
215
+ arrow.current_indicator = True
216
+ arrow.set_transform(ax.transData)
217
+
218
+ ax.add_patch(arrow)
219
+
220
+ # Animation state variables
221
+ animation_state = {
222
+ 'magnet_pos': magnet_pos,
223
+ 'previous_pos': magnet_pos,
224
+ 'frame_count': 0
225
+ }
226
+
227
+ def update():
228
+ magnet_pos = animation_state['magnet_pos']
229
+ previous_pos = animation_state['previous_pos']
230
+ frame_count = animation_state['frame_count']
231
+
232
+ # Update magnet position based on selected motion
233
+ if magnet_direction == "Moving In":
234
+ magnet_pos = min(0, magnet_pos + magnet_speed * 0.2)
235
+ elif magnet_direction == "Moving Out":
236
+ magnet_pos = min(9, magnet_pos + magnet_speed * 0.2)
237
+ else: # Oscillating
238
+ magnet_pos = 5 * np.sin(frame_count * 0.1 * magnet_speed)
239
+
240
+ # Update magnet position
241
+ magnet.set_x(magnet_pos - magnet_width/2)
242
+
243
+ # Update magnetic field
244
+ plot_magnetic_field(magnet_pos, magnet_strength)
245
+
246
+ # Calculate induced voltage
247
+ induced_voltage = calculate_induced_voltage(
248
+ magnet_pos, previous_pos, magnet_strength, coil_turns, coil_radius/10) # Convert cm to dm
249
+
250
+ # Scale for display
251
+ display_voltage = induced_voltage * 5
252
+
253
+ # Update voltage data
254
+ voltage_data[:-1] = voltage_data[1:]
255
+ voltage_data[-1] = display_voltage
256
+ voltage_line.set_ydata(voltage_data)
257
+
258
+ # Update current indicator
259
+ show_current_direction(induced_voltage, ax1, coil_radius)
260
+
261
+ # Update previous position for next frame
262
+ # Update state for next frame
263
+ animation_state['magnet_pos'] = magnet_pos
264
+ animation_state['previous_pos'] = magnet_pos
265
+ animation_state['frame_count'] = frame_count + 1
266
+ # Convert plot to image
267
+ buf = BytesIO()
268
+ fig.tight_layout()
269
+ fig.savefig(buf, format='png', dpi=100)
270
+ buf.seek(0)
271
+ img_str = base64.b64encode(buf.read()).decode('utf-8')
272
+
273
+ return f'data:image/png;base64,{img_str}'
274
+
275
+ # Create a loop to update the plot repeatedly
276
+ while True:
277
+ img_data = update()
278
+ plot_placeholder.image(img_data, use_column_width=True)
279
+ # Sleep briefly to control animation speed
280
+ import time
281
+ time.sleep(0.1)
282
+
283
+ # Additional explanations
284
+ st.markdown("""
285
+ ### How Electromagnetic Induction Works
286
+
287
+ 1. **Faraday's Law**: When a magnetic field changes through a coil of wire, an electromotive force (EMF) is induced, leading to current flow if there is a complete circuit.
288
+
289
+ 2. **Key Factors Affecting Induced Voltage**:
290
+ - Magnetic field strength: Stronger fields create greater induced voltages
291
+ - Number of coil turns: More turns create more induced voltage
292
+ - Rate of change: Faster movement of the magnet creates higher voltage
293
+ - Coil area: Larger coils intercept more magnetic flux
294
+
295
+ 3. **Lenz's Law**: The induced current creates its own magnetic field that opposes the change that produced it.
296
+
297
+ Try moving the magnet at different speeds and directions to see how the induced voltage changes!
298
+ """)
pages/electric-potential.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ from scipy.constants import k as coulomb_k
5
+
6
+ # Title of the app
7
+ st.title("Electric Potential Visualization")
8
+
9
+ # Sidebar for user inputs
10
+ st.sidebar.header("Charge Configuration")
11
+ num_charges = st.sidebar.selectbox("Number of charges", [1, 2, 3, 4, 5])
12
+
13
+ charges = []
14
+ for i in range(num_charges):
15
+ st.sidebar.subheader(f"Charge {i+1}")
16
+ # Sliders for charge magnitude and position
17
+ Q = st.sidebar.slider(f"Q_{i+1} (arbitrary units)", min_value=-10.0, max_value=10.0, value=1.0, key=f"Q{i}")
18
+ x = st.sidebar.slider(f"x_{i+1}", min_value=-5.0, max_value=5.0, value=0.0, key=f"x{i}")
19
+ y = st.sidebar.slider(f"y_{i+1}", min_value=-5.0, max_value=5.0, value=0.0, key=f"y{i}")
20
+ charges.append({'Q': Q, 'x': x, 'y': y})
21
+
22
+ # Define the 2D grid for calculation
23
+ x = np.linspace(-10, 10, 100)
24
+ y = np.linspace(-10, 10, 100)
25
+ X, Y = np.meshgrid(x, y)
26
+
27
+ # Calculate total electric potential
28
+ V = np.zeros_like(X)
29
+ for charge in charges:
30
+ r = np.sqrt((X - charge['x'])**2 + (Y - charge['y'])**2)
31
+ # Avoid division by zero at charge location
32
+ r = np.where(r == 0, 1e-6, r)
33
+ V += coulomb_k * charge['Q'] / r
34
+
35
+ # Create interactive Plotly contour plot
36
+ fig = go.Figure(data=go.Contour(x=x, y=y, z=V, colorscale='RdBu', colorbar=dict(title='Potential')))
37
+
38
+ # Add charge markers
39
+ for charge in charges:
40
+ color = 'red' if charge['Q'] > 0 else 'blue'
41
+ fig.add_trace(go.Scatter(x=[charge['x']], y=[charge['y']], mode='markers',
42
+ marker=dict(color=color, size=10)))
43
+
44
+ # Customize plot layout
45
+ fig.update_layout(title='Electric Potential', xaxis_title='x', yaxis_title='y')
46
+ fig.update_yaxes(scaleanchor="x", scaleratio=1) # Equal aspect ratio
47
+
48
+ # Display the plot in Streamlit
49
+ st.plotly_chart(fig)
pages/electromagnetc-waves.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+
6
+ # Set up the figure and axis
7
+ fig, ax = plt.subplots(figsize=(10, 6))
8
+ ax.set_xlim(0, 4*np.pi)
9
+ ax.set_ylim(-2, 2)
10
+ ax.set_xlabel("Position (m)")
11
+ ax.set_ylabel("Field Strength")
12
+ ax.grid(True)
13
+
14
+ # Initialize empty lines for E and B fields
15
+ e_line, = ax.plot([], [], lw=2, label='Electric Field (E)')
16
+ b_line, = ax.plot([], [], lw=2, label='Magnetic Field (B)')
17
+ ax.legend()
18
+
19
+ # Widgets in sidebar
20
+ st.sidebar.header("Wave Parameters")
21
+ frequency = st.sidebar.slider("Frequency (Hz)", 1, 20, 5)
22
+ wavelength = st.sidebar.slider("Wavelength (m)", 1, 10, 5)
23
+ amplitude = st.sidebar.slider("Amplitude (V/m)", 0.1, 2.0, 1.0)
24
+
25
+ # Animation function
26
+ def animate(i):
27
+ x = np.linspace(0, 4*np.pi, 1000)
28
+ phase = 2 * np.pi * frequency * (i/30)
29
+
30
+ # Electric field (y-direction)
31
+ e = amplitude * np.sin((2*np.pi*x)/wavelength - phase)
32
+
33
+ # Magnetic field (z-direction, scaled by 1/c)
34
+ b = (amplitude/3e8) * np.sin((2*np.pi*x)/wavelength - phase)
35
+
36
+ e_line.set_data(x, e)
37
+ b_line.set_data(x, b)
38
+ return e_line, b_line
39
+
40
+ # Create and display animation
41
+ ani = FuncAnimation(fig, animate, frames=100, interval=50, blit=True)
42
+ st.pyplot(fig)
pages/electromagnetic-spectrum.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import plotly.express as px
5
+
6
+ # Constants
7
+ SPEED_OF_LIGHT = 3e8 # m/s
8
+
9
+ # Configure app
10
+ st.set_page_config(page_title="EM Spectrum Visualizer", layout="wide")
11
+ st.title("Electromagnetic Spectrum Visualization")
12
+ st.markdown("Explore different regions of the electromagnetic spectrum")
13
+
14
+ # Sidebar controls
15
+ with st.sidebar:
16
+ st.header("Configuration")
17
+ wavelength = st.slider("Wavelength (meters)",
18
+ 1e-12, 100.0,
19
+ value=5e-7,
20
+ format="%.1e")
21
+
22
+ region = st.selectbox("Predefined Regions", [
23
+ "Radio Waves", "Microwaves", "Infrared",
24
+ "Visible Light", "Ultraviolet", "X-Rays", "Gamma Rays"
25
+ ])
26
+
27
+ # Calculate frequency
28
+ frequency = SPEED_OF_LIGHT / wavelength
29
+
30
+ # Main display
31
+ tab1, tab2 = st.tabs(["Wave Visualization", "Spectrum Diagram"])
32
+
33
+ with tab1:
34
+ # Create wave visualization
35
+ x = np.linspace(0, 4*np.pi, 1000)
36
+ y_electric = np.sin(x * wavelength * 100) # Scaled for visualization
37
+ y_magnetic = np.cos(x * wavelength * 100)
38
+
39
+ fig, ax = plt.subplots(figsize=(10, 4))
40
+ ax.plot(x, y_electric, label='Electric Field (E)')
41
+ ax.plot(x, y_magnetic, label='Magnetic Field (B)')
42
+ ax.set_title(f"EM Wave Visualization\nλ = {wavelength:.2e} m | ν = {frequency:.2e} Hz")
43
+ ax.set_xlabel("Propagation Direction")
44
+ ax.legend()
45
+ ax.grid(True)
46
+ st.pyplot(fig)
47
+
48
+ with tab2:
49
+ # Create spectrum diagram
50
+ spectrum_data = {
51
+ "Region": ["Gamma", "X-Ray", "UV", "Visible", "IR", "Microwave", "Radio"],
52
+ "Start (m)": [1e-12, 1e-10, 4e-7, 4e-7, 7e-7, 1e-3, 1e-1],
53
+ "End (m)": [1e-10, 1e-8, 4e-7, 7e-7, 1e-3, 1e-1, 1e4],
54
+ "Color": ["purple", "blue", "violet", "#FF00FF", "red", "orange", "yellow"]
55
+ }
56
+
57
+ fig = px.bar(spectrum_data,
58
+ x=["Gamma", "X-Ray", "UV", "Visible", "IR", "Microwave", "Radio"],
59
+ y=[1,1,1,1,1,1,1],
60
+ color="Region",
61
+ color_discrete_map={r: c for r,c in zip(spectrum_data["Region"],
62
+ spectrum_data["Color"])},
63
+ orientation="h",
64
+ log_x=True,
65
+ height=400)
66
+
67
+ fig.update_layout(title="Electromagnetic Spectrum Regions",
68
+ xaxis_title="Wavelength (meters)",
69
+ yaxis_visible=False,
70
+ showlegend=False)
71
+ st.plotly_chart(fig, use_container_width=True)
72
+
73
+ # Region information
74
+ region_info = {
75
+ "Radio Waves": {"range": "1 mm - 100 km", "uses": "Communications, broadcasting"},
76
+ "Microwaves": {"range": "1 mm - 1 m", "uses": "Radar, cooking"},
77
+ "Infrared": {"range": "700 nm - 1 mm", "uses": "Thermal imaging"},
78
+ "Visible Light": {"range": "400-700 nm", "uses": "Human vision, photography"},
79
+ "Ultraviolet": {"range": "10-400 nm", "uses": "Sterilization, astronomy"},
80
+ "X-Rays": {"range": "0.01-10 nm", "uses": "Medical imaging"},
81
+ "Gamma Rays": {"range": "<0.01 nm", "uses": "Cancer treatment"}
82
+ }
83
+
84
+ st.subheader(f"Region Information: {region}")
85
+ col1, col2 = st.columns(2)
86
+ with col1:
87
+ st.metric("Wavelength Range", region_info[region]["range"])
88
+ with col2:
89
+ st.metric("Common Applications", region_info[region]["uses"])
pages/energy.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import plotly.graph_objs as go
5
+ import plotly.express as px
6
+
7
+ class EnergyVisualization:
8
+ def __init__(self):
9
+ # Energy types and their descriptions
10
+ self.energy_types = {
11
+ "Kinetic Energy": {
12
+ "description": "Energy of motion. Depends on mass and velocity.",
13
+ "formula": "KE = 1/2 * m * v²",
14
+ "example": "A moving car, a rolling ball"
15
+ },
16
+ "Potential Energy": {
17
+ "description": "Stored energy due to position or configuration.",
18
+ "subtypes": {
19
+ "Gravitational": "Energy due to height in a gravitational field.",
20
+ "Elastic": "Energy stored in stretched or compressed springs.",
21
+ "Chemical": "Energy stored in chemical bonds"
22
+ },
23
+ "formula": "PE = m * g * h (for gravitational)",
24
+ "example": "A book on a high shelf, a stretched rubber band"
25
+ },
26
+ "Thermal Energy": {
27
+ "description": "Energy associated with the motion of particles.",
28
+ "formula": "Q = m * c * ΔT",
29
+ "example": "Heat from a fire, warmth of your body"
30
+ },
31
+ "Electrical Energy": {
32
+ "description": "Energy from electric charges and their movement.",
33
+ "formula": "E = V * Q",
34
+ "example": "Lightning, electricity in your home"
35
+ },
36
+ "Chemical Energy": {
37
+ "description": "Energy stored in chemical bonds between atoms and molecules.",
38
+ "formula": "ΔH (Change in Enthalpy)",
39
+ "example": "Batteries, food, fuel"
40
+ }
41
+ }
42
+
43
+ def display_energy_overview(self):
44
+ """Create an overview of energy types"""
45
+ st.title("Energy Visualization and Exploration")
46
+
47
+ # Sidebar for energy type selection
48
+ selected_energy = st.sidebar.selectbox(
49
+ "Select Energy Type",
50
+ list(self.energy_types.keys())
51
+ )
52
+
53
+ # Display details of selected energy type
54
+ energy_info = self.energy_types[selected_energy]
55
+
56
+ st.header(f"{selected_energy}")
57
+ st.write(energy_info["description"])
58
+ st.write(f"**Formula:** {energy_info['formula']}")
59
+ st.write(f"**Example:** {energy_info['example']}")
60
+
61
+ # Check for subtypes
62
+ if "subtypes" in energy_info:
63
+ st.subheader("Subtypes")
64
+ for subtype, sub_desc in energy_info["subtypes"].items():
65
+ st.write(f"- **{subtype}:** {sub_desc}")
66
+
67
+ # Interactive visualization based on energy type
68
+ self._visualize_energy_type(selected_energy)
69
+
70
+ def _visualize_energy_type(self, energy_type):
71
+ """Create interactive visualizations for different energy types"""
72
+ if energy_type == "Kinetic Energy":
73
+ self._kinetic_energy_visualization()
74
+ elif energy_type == "Potential Energy":
75
+ self._potential_energy_visualization()
76
+ elif energy_type == "Thermal Energy":
77
+ self._thermal_energy_visualization()
78
+ elif energy_type == "Electrical Energy":
79
+ self._electrical_energy_visualization()
80
+ elif energy_type == "Chemical Energy":
81
+ self._chemical_energy_visualization()
82
+
83
+ def _kinetic_energy_visualization(self):
84
+ """Visualization for Kinetic Energy"""
85
+ st.subheader("Kinetic Energy Interactive Simulation")
86
+
87
+ # Mass and velocity sliders
88
+ mass = st.slider("Mass (kg)", 1.0, 100.0, 10.0, 0.1)
89
+ velocity = st.slider("Velocity (m/s)", 0.0, 50.0, 10.0, 0.1)
90
+
91
+ # Calculate Kinetic Energy
92
+ ke = 0.5 * mass * (velocity ** 2)
93
+
94
+ # Plotly visualization
95
+ fig = go.Figure(data=[go.Bar(
96
+ x=['Kinetic Energy'],
97
+ y=[ke],
98
+ text=[f'{ke:.2f} J'],
99
+ textposition='auto'
100
+ )])
101
+ fig.update_layout(
102
+ title='Kinetic Energy Calculation',
103
+ yaxis_title='Energy (Joules)'
104
+ )
105
+ st.plotly_chart(fig)
106
+
107
+ st.write(f"**Calculation:** KE = 1/2 * {mass} * {velocity}² = {ke:.2f} Joules")
108
+
109
+ def _potential_energy_visualization(self):
110
+ """Visualization for Potential Energy"""
111
+ st.subheader("Gravitational Potential Energy Simulation")
112
+
113
+ # Inputs for potential energy calculation
114
+ mass = st.slider("Mass (kg)", 1.0, 100.0, 10.0, 0.1)
115
+ height = st.slider("Height (m)", 0.0, 50.0, 10.0, 0.1)
116
+ g = 9.8 # acceleration due to gravity
117
+
118
+ # Calculate Potential Energy
119
+ pe = mass * g * height
120
+
121
+ # Create a simple visualization
122
+ fig = plt.figure(figsize=(8, 6))
123
+ plt.title("Gravitational Potential Energy")
124
+ plt.bar(['Potential Energy'], [pe], color='green')
125
+ plt.ylabel('Energy (Joules)')
126
+ plt.text(0, pe, f'{pe:.2f} J', ha='center', va='bottom')
127
+ st.pyplot(fig)
128
+
129
+ st.write(f"**Calculation:** PE = {mass} * {g} * {height} = {pe:.2f} Joules")
130
+
131
+ def _thermal_energy_visualization(self):
132
+ """Visualization for Thermal Energy"""
133
+ st.subheader("Thermal Energy Temperature Simulation")
134
+
135
+ # Inputs for thermal energy
136
+ mass = st.slider("Mass (kg)", 0.1, 10.0, 1.0, 0.1)
137
+ specific_heat = st.slider("Specific Heat Capacity (J/kg·K)", 100, 5000, 1000, 10)
138
+ delta_temp = st.slider("Temperature Change (°C)", -100, 100, 20, 1)
139
+
140
+ # Calculate Thermal Energy
141
+ thermal_energy = mass * specific_heat * delta_temp
142
+
143
+ # Plotly heat map visualization
144
+ temps = np.linspace(-100, 100, 100)
145
+ heat_map = [mass * specific_heat * t for t in temps]
146
+
147
+ fig = go.Figure(data=[go.Scatter(
148
+ x=temps,
149
+ y=heat_map,
150
+ mode='lines',
151
+ name='Thermal Energy'
152
+ )])
153
+ fig.update_layout(
154
+ title='Thermal Energy vs Temperature',
155
+ xaxis_title='Temperature Change (°C)',
156
+ yaxis_title='Thermal Energy (Joules)'
157
+ )
158
+ st.plotly_chart(fig)
159
+
160
+ st.write(f"**Calculation:** Q = {mass} * {specific_heat} * {delta_temp} = {thermal_energy:.2f} Joules")
161
+
162
+ def _electrical_energy_visualization(self):
163
+ """Visualization for Electrical Energy"""
164
+ st.subheader("Electrical Energy Simulation")
165
+
166
+ # Inputs for electrical energy
167
+ voltage = st.slider("Voltage (V)", 1, 240, 120, 1)
168
+ charge = st.slider("Charge (Coulombs)", 0.1, 10.0, 1.0, 0.1)
169
+
170
+ # Calculate Electrical Energy
171
+ electrical_energy = voltage * charge
172
+
173
+ # Plotly bar chart
174
+ fig = go.Figure(data=[go.Bar(
175
+ x=['Electrical Energy'],
176
+ y=[electrical_energy],
177
+ text=[f'{electrical_energy:.2f} J'],
178
+ textposition='auto'
179
+ )])
180
+ fig.update_layout(
181
+ title='Electrical Energy Calculation',
182
+ yaxis_title='Energy (Joules)'
183
+ )
184
+ st.plotly_chart(fig)
185
+
186
+ st.write(f"**Calculation:** E = {voltage} * {charge} = {electrical_energy:.2f} Joules")
187
+
188
+ def _chemical_energy_visualization(self):
189
+ """Visualization for Chemical Energy"""
190
+ st.subheader("Chemical Energy Bond Simulation")
191
+
192
+ # Chemical bond energy types
193
+ bond_energies = {
194
+ "Hydrogen Bond": 20,
195
+ "Ionic Bond": 700,
196
+ "Covalent Bond": 350,
197
+ "Metallic Bond": 250
198
+ }
199
+
200
+ # Multiselect for bond types
201
+ selected_bonds = st.multiselect(
202
+ "Select Bond Types",
203
+ list(bond_energies.keys()),
204
+ default=["Covalent Bond"]
205
+ )
206
+
207
+ # Calculate total bond energy
208
+ total_bond_energy = sum(bond_energies[bond] for bond in selected_bonds)
209
+
210
+ # Pie chart visualization
211
+ fig = go.Figure(data=[go.Pie(
212
+ labels=selected_bonds,
213
+ values=[bond_energies[bond] for bond in selected_bonds],
214
+ hole=.3
215
+ )])
216
+ fig.update_layout(title='Chemical Bond Energy Distribution')
217
+ st.plotly_chart(fig)
218
+
219
+ st.write("**Bond Energies:**")
220
+ for bond in selected_bonds:
221
+ st.write(f"- {bond}: {bond_energies[bond]} kJ/mol")
222
+ st.write(f"**Total Bond Energy:** {total_bond_energy} kJ/mol")
223
+
224
+ def main():
225
+ # Initialize and run the Energy Visualization
226
+ energy_viz = EnergyVisualization()
227
+ energy_viz.display_energy_overview()
228
+
229
+ if __name__ == "__main__":
230
+ main()
231
+
232
+ # Requirements for this Streamlit app
233
+ # Run this in terminal:
234
+ # pip install streamlit numpy matplotlib plotly
235
+ # streamlit run energy_visualization.py
pages/farady-law.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from mpl_toolkits.mplot3d import Axes3D
5
+
6
+ # Set up the page
7
+ st.set_page_config(page_title="Faraday's Law Visualization", layout="wide")
8
+ st.title("Visualization of Faraday's Law of Electromagnetic Induction")
9
+
10
+ # Theory section
11
+ st.markdown("""
12
+ ## Faraday's Law
13
+ Faraday's Law states that the induced electromotive force (EMF) in a closed circuit is equal to:
14
+ """)
15
+ st.latex(r'''\varepsilon = -N \frac{d\Phi_B}{dt}''')
16
+ st.markdown("""
17
+ Where:
18
+ - ε = Induced EMF (Volts)
19
+ - N = Number of turns in coil
20
+ - Φ_B = Magnetic flux (Weber)
21
+ - t = Time (seconds)
22
+
23
+ Magnetic flux is given by:
24
+ """)
25
+ st.latex(r'''\Phi_B = B \cdot A \cdot \cos(\theta)''')
26
+
27
+ # Sidebar controls
28
+ st.sidebar.header("Simulation Parameters")
29
+ B0 = st.sidebar.slider("Peak Magnetic Field (T)", 0.1, 10.0, 2.0)
30
+ N = st.sidebar.slider("Number of Turns", 1, 100, 50)
31
+ radius = st.sidebar.slider("Coil Radius (m)", 0.1, 2.0, 0.5)
32
+ theta_deg = st.sidebar.slider("Angle between B and Normal (degrees)", 0, 180, 45)
33
+ freq = st.sidebar.slider("Oscillation Frequency (Hz)", 0.1, 10.0, 1.0)
34
+
35
+ # Convert angle to radians
36
+ theta = np.deg2rad(theta_deg)
37
+
38
+ # Time array
39
+ t = np.linspace(0, 2, 1000) # 2 seconds simulation
40
+
41
+ # Calculations
42
+ B = B0 * np.sin(2 * np.pi * freq * t) # Time-varying magnetic field
43
+ A = np.pi * radius**2 # Coil area
44
+ flux = N * B * A * np.cos(theta) # Magnetic flux
45
+ emf = -np.gradient(flux, t) # Induced EMF
46
+
47
+ # Create figure for plots
48
+ fig = plt.figure(figsize=(12, 10))
49
+
50
+ # Magnetic field plot
51
+ ax1 = plt.subplot(311)
52
+ ax1.plot(t, B, color='tab:red')
53
+ ax1.set_ylabel('Magnetic Field (T)')
54
+ ax1.set_title('Time-varying Magnetic Field')
55
+ ax1.grid(True)
56
+
57
+ # Magnetic flux plot
58
+ ax2 = plt.subplot(312)
59
+ ax2.plot(t, flux, color='tab:green')
60
+ ax2.set_ylabel('Magnetic Flux (Wb)')
61
+ ax2.set_title('Magnetic Flux Through Coil')
62
+ ax2.grid(True)
63
+
64
+ # Induced EMF plot
65
+ ax3 = plt.subplot(313)
66
+ ax3.plot(t, emf, color='tab:blue')
67
+ ax3.set_ylabel('Induced EMF (V)')
68
+ ax3.set_xlabel('Time (s)')
69
+ ax3.set_title('Induced Electromotive Force')
70
+ ax3.grid(True)
71
+
72
+ plt.tight_layout()
73
+ st.pyplot(fig)
74
+
75
+ # 3D Visualization
76
+ st.markdown("### 3D Visualization of Coil Orientation")
77
+
78
+ fig3d = plt.figure(figsize=(8, 8))
79
+ ax3d = fig3d.add_subplot(111, projection='3d')
80
+
81
+ # Generate coil coordinates
82
+ theta_coil = np.linspace(0, 2*np.pi, 100)
83
+ x = radius * np.cos(theta_coil)
84
+ y = radius * np.sin(theta_coil) * np.cos(theta)
85
+ z = radius * np.sin(theta_coil) * np.sin(theta)
86
+
87
+ # Plot coil
88
+ ax3d.plot(x, y, z, color='blue', linewidth=2, label='Coil')
89
+
90
+ # Plot magnetic field direction
91
+ ax3d.quiver(0, 0, -1, 0, 0, 2, color='red',
92
+ linewidth=3, arrow_length_ratio=0.1, label='Magnetic Field (B)')
93
+
94
+ # Set plot limits and labels
95
+ ax3d.set_xlim([-radius*1.5, radius*1.5])
96
+ ax3d.set_ylim([-radius*1.5, radius*1.5])
97
+ ax3d.set_zlim([-radius*1.5, radius*1.5])
98
+ ax3d.set_xlabel('X-axis')
99
+ ax3d.set_ylabel('Y-axis')
100
+ ax3d.set_zlabel('Z-axis')
101
+ ax3d.set_title('Coil Orientation Relative to Magnetic Field')
102
+ ax3d.legend()
103
+
104
+ st.pyplot(fig3d)
105
+
106
+ # Explanation
107
+ st.markdown("""
108
+ ## Explanation
109
+ 1. **Magnetic Field (Red Plot):** Shows the sinusoidal variation of the external magnetic field
110
+ 2. **Magnetic Flux (Green Plot):** Demonstrates the flux through the coil depending on angle θ
111
+ 3. **Induced EMF (Blue Plot):** Shows the EMF generated by the changing magnetic flux
112
+
113
+ The 3D visualization shows:
114
+ - Blue coil in its orientation relative to the magnetic field (red arrow)
115
+ - The angle θ between the coil's normal vector and magnetic field direction
116
+ """)
pages/field-concepts.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Introduction and instructions
6
+ st.markdown("""
7
+ # Electric Field Visualization
8
+
9
+ This app helps you visualize the electric field created by point charges in a 2D space.
10
+
11
+ ### How to Use
12
+ - In the sidebar, enter the charges in the format `x,y,Q` (one per line), where:
13
+ - `x` and `y` are the coordinates of the charge.
14
+ - `Q` is the charge value (positive or negative).
15
+ - Example: `0,0,1` places a positive charge of 1 at the origin, and `3,0,-1` places a negative charge at (3,0).
16
+ - Select the visualization type:
17
+ - **Quiver Plot**: Shows arrows representing the direction and strength of the electric field.
18
+ - **Field Lines**: Displays lines that follow the electric field's direction.
19
+
20
+ Try starting with `0,0,1` and `3,0,-1` to see a dipole field!
21
+ """)
22
+
23
+ # Sidebar inputs
24
+ charges_input = st.sidebar.text_area(
25
+ "Enter charges (x,y,Q) one per line",
26
+ "0,0,1\n3,0,-1"
27
+ )
28
+ vis_type = st.sidebar.radio("Visualization Type", ["Quiver Plot", "Field Lines"])
29
+
30
+ # Parse the input charges
31
+ charges = []
32
+ for line in charges_input.split('\n'):
33
+ if line.strip():
34
+ try:
35
+ x, y, Q = map(float, line.split(','))
36
+ charges.append((x, y, Q))
37
+ except ValueError:
38
+ st.sidebar.error(f"Invalid input: {line}. Use format x,y,Q.")
39
+ st.stop()
40
+
41
+ # Check if there are any charges
42
+ if not charges:
43
+ st.warning("No charges entered. Please add charges in the sidebar.")
44
+ st.stop()
45
+
46
+ # Function to compute the electric field at a point (x, y)
47
+ def electric_field(x, y, charges):
48
+ Ex, Ey = 0, 0
49
+ for cx, cy, Q in charges:
50
+ dx = x - cx
51
+ dy = y - cy
52
+ r2 = dx**2 + dy**2
53
+ # Avoid division by zero (when point is exactly at a charge)
54
+ if r2 < 1e-10:
55
+ continue
56
+ r3 = r2**1.5
57
+ Ex += Q * dx / r3
58
+ Ey += Q * dy / r3
59
+ return Ex, Ey
60
+
61
+ # Set up the plot
62
+ fig, ax = plt.subplots(figsize=(8, 8))
63
+
64
+ # Define the grid for quiver plot
65
+ x = np.arange(-10, 11, 1)
66
+ y = np.arange(-10, 11, 1)
67
+ X, Y = np.meshgrid(x, y)
68
+
69
+ if vis_type == "Quiver Plot":
70
+ # Compute electric field across the grid
71
+ Ex = np.zeros_like(X, dtype=float)
72
+ Ey = np.zeros_like(Y, dtype=float)
73
+ for i in range(X.shape[0]):
74
+ for j in range(X.shape[1]):
75
+ Ex[i, j], Ey[i, j] = electric_field(X[i, j], Y[i, j], charges)
76
+
77
+ # Calculate field magnitude for coloring
78
+ M = np.sqrt(Ex**2 + Ey**2)
79
+
80
+ # Plot quiver with magnitude-based coloring
81
+ quiv = ax.quiver(X, Y, Ex, Ey, M, cmap='viridis', scale=50)
82
+ fig.colorbar(quiv, ax=ax, label='Field Magnitude')
83
+
84
+ else: # Field Lines
85
+ # Generate starting points around each charge
86
+ starting_points = []
87
+ for cx, cy, Q in charges:
88
+ # 8 points around each charge at a small radius
89
+ for angle in np.linspace(0, 2*np.pi, 8, endpoint=False):
90
+ x0 = cx + 0.1 * np.cos(angle)
91
+ y0 = cy + 0.1 * np.sin(angle)
92
+ starting_points.append((x0, y0))
93
+
94
+ # Trace field lines from each starting point
95
+ for x0, y0 in starting_points:
96
+ path = [(x0, y0)]
97
+ for _ in range(100): # Maximum steps
98
+ Ex, Ey = electric_field(x0, y0, charges)
99
+ mag = np.sqrt(Ex**2 + Ey**2)
100
+ if mag < 1e-5: # Stop if field is too weak
101
+ break
102
+ # Normalize step size
103
+ step_x = 0.1 * Ex / mag
104
+ step_y = 0.1 * Ey / mag
105
+ x0 += step_x
106
+ y0 += step_y
107
+ # Stop if outside the plot boundaries
108
+ if x0 < -10 or x0 > 10 or y0 < -10 or y0 > 10:
109
+ break
110
+ path.append((x0, y0))
111
+
112
+ # Plot the field line
113
+ px, py = zip(*path)
114
+ ax.plot(px, py, 'k-', linewidth=1)
115
+
116
+ # Plot the charges
117
+ for cx, cy, Q in charges:
118
+ color = 'red' if Q > 0 else 'blue'
119
+ ax.plot(cx, cy, 'o', color=color, markersize=10, label=f'Q={Q}')
120
+
121
+ # Customize the plot
122
+ ax.set_xlim(-10, 10)
123
+ ax.set_ylim(-10, 10)
124
+ ax.set_aspect('equal')
125
+ ax.set_xlabel('x')
126
+ ax.set_ylabel('y')
127
+ ax.set_title(f'Electric Field - {vis_type}')
128
+ ax.grid(True)
129
+
130
+ # Display the plot in Streamlit
131
+ st.pyplot(fig)
pages/first-law-of-thermodynamics.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+
4
+ def main():
5
+ st.title("First Law of Thermodynamics Simulation")
6
+ st.write("""
7
+ The First Law of Thermodynamics is essentially an expression of energy conservation.
8
+ It states that the change in internal energy (ΔU) of a system equals the heat (Q) added
9
+ to the system minus the work (W) done by the system:
10
+
11
+ **ΔU = Q - W**
12
+
13
+ Use the sliders below to adjust the heat added and the work done.
14
+ """)
15
+
16
+ # User input for heat (Q) and work (W)
17
+ Q = st.slider("Heat added (Q) [Joules]", min_value=-200, max_value=200, value=50, step=1)
18
+ W = st.slider("Work done (W) [Joules]", min_value=-200, max_value=200, value=30, step=1)
19
+
20
+ # Calculate the change in internal energy
21
+ delta_U = Q - W
22
+ st.write(f"**Calculated change in internal energy (ΔU):** {delta_U} Joules")
23
+
24
+ # Create a bar chart to visualize the energy contributions
25
+ labels = ['Heat (Q)', 'Work (W)', 'ΔU']
26
+ values = [Q, W, delta_U]
27
+ colors = ['#69b3a2', '#404080', '#e377c2']
28
+
29
+ fig, ax = plt.subplots()
30
+ bars = ax.bar(labels, values, color=colors)
31
+ ax.axhline(0, color='black', linewidth=0.8)
32
+ ax.set_ylabel("Energy (Joules)")
33
+ ax.set_title("Energy Balance: Q, W, and ΔU")
34
+ for bar in bars:
35
+ yval = bar.get_height()
36
+ # Position the text slightly above the bar if positive, or below if negative.
37
+ offset = 3 if yval >= 0 else -15
38
+ ax.text(bar.get_x() + bar.get_width()/2.0, yval + offset, f'{yval}',
39
+ ha='center', color='black', fontsize=10)
40
+ st.pyplot(fig)
41
+
42
+ st.write("""
43
+ **Explanation:**
44
+ - **Q (Heat added):** The energy put into the system via heating.
45
+ - **W (Work done):** The energy used by the system to perform work (like expanding against a piston).
46
+ - **ΔU:** The resulting change in the system's internal energy.
47
+
48
+ Notice that increasing Q or decreasing W (or both) will result in a higher ΔU, showing that more energy remains stored within the system.
49
+ """)
50
+
51
+ if __name__ == '__main__':
52
+ main()
pages/fluid-dynamics.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import time
5
+
6
+ # Sidebar parameters for simulation control
7
+ num_particles = st.sidebar.number_input("Number of particles", min_value=10, max_value=500, value=100)
8
+ dt = st.sidebar.slider("Time step (dt)", min_value=0.01, max_value=1.0, value=0.1)
9
+ num_steps = st.sidebar.slider("Number of simulation steps", min_value=10, max_value=500, value=200)
10
+ refresh_rate = st.sidebar.slider("Refresh rate (seconds)", min_value=0.01, max_value=0.5, value=0.1)
11
+
12
+ # Initialize particle positions randomly within a region
13
+ positions = np.random.uniform(-5, 5, (num_particles, 2))
14
+
15
+ def velocity_field(x, y):
16
+ """
17
+ Defines a vortex velocity field centered at the origin.
18
+ u = -y, v = x gives a counter-clockwise rotation.
19
+ """
20
+ u = -y
21
+ v = x
22
+ return u, v
23
+
24
+ st.title("Fluid Dynamics Simulation: Particle Advection in a Vortex")
25
+
26
+ if st.button("Start Simulation"):
27
+ placeholder = st.empty() # container to update the plot
28
+ for step in range(num_steps):
29
+ # Compute the velocity for all particles at their current positions
30
+ u, v = velocity_field(positions[:, 0], positions[:, 1])
31
+ # Update particle positions using Euler integration
32
+ positions[:, 0] += u * dt
33
+ positions[:, 1] += v * dt
34
+
35
+ # Create a plot of the current particle positions
36
+ fig, ax = plt.subplots(figsize=(6, 6))
37
+ ax.scatter(positions[:, 0], positions[:, 1], color='blue', s=10)
38
+ ax.set_xlim(-10, 10)
39
+ ax.set_ylim(-10, 10)
40
+ ax.set_title(f"Step {step + 1}")
41
+ ax.set_xlabel("X")
42
+ ax.set_ylabel("Y")
43
+
44
+ # Update the plot in the Streamlit app
45
+ placeholder.pyplot(fig)
46
+ time.sleep(refresh_rate)
pages/fluid-statics.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ st.title("Fluid Statics Simulation")
6
+ st.markdown("""
7
+ This simulation visualizes how pressure varies in a fluid column at rest.
8
+ According to fluid statics, the pressure increases with depth according to:
9
+ \[ P = P_0 + \rho g h \]
10
+ where:
11
+ - \(P_0\) is the surface pressure,
12
+ - \(\rho\) is the fluid density,
13
+ - \(g\) is the gravitational acceleration,
14
+ - \(h\) is the depth.
15
+ """)
16
+
17
+ # User input for simulation parameters
18
+ density = st.slider("Fluid Density (kg/m³)", min_value=500, max_value=2000, value=1000, step=50)
19
+ gravity = st.slider("Gravitational Acceleration (m/s²)", min_value=1.0, max_value=20.0, value=9.81, step=0.1)
20
+ column_height = st.slider("Fluid Column Height (m)", min_value=1.0, max_value=100.0, value=10.0, step=1.0)
21
+ P0 = st.slider("Surface Pressure (Pa)", min_value=0, max_value=200000, value=101325, step=1000)
22
+
23
+ # Calculate pressures at various depths
24
+ depths = np.linspace(0, column_height, 200)
25
+ pressures = P0 + density * gravity * depths
26
+
27
+ # Plot Pressure vs. Depth
28
+ fig, ax = plt.subplots(figsize=(6, 4))
29
+ ax.plot(pressures, depths, color="blue")
30
+ ax.set_xlabel("Pressure (Pa)")
31
+ ax.set_ylabel("Depth (m)")
32
+ ax.set_title("Pressure Variation with Depth")
33
+ ax.invert_yaxis() # so that the plot displays depth increasing downward
34
+ st.pyplot(fig)
35
+
36
+ # Visualize the fluid column using a color gradient
37
+ fig2, ax2 = plt.subplots(figsize=(2, 6))
38
+ # Create a vertical gradient image
39
+ gradient = np.linspace(0, 1, 256)
40
+ gradient = np.vstack((gradient, gradient))
41
+ ax2.imshow(gradient, extent=[0, 1, 0, column_height], aspect='auto', cmap='Blues')
42
+ ax2.set_title("Fluid Column (Pressure Gradient)")
43
+ ax2.set_ylabel("Depth (m)")
44
+ ax2.set_xticks([])
45
+ st.pyplot(fig2)
pages/force.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.patches as patches
5
+
6
+ def calculate_resultant_force(forces):
7
+ """
8
+ Calculate the resultant force vector from multiple forces.
9
+
10
+ Args:
11
+ forces (list): List of tuples (magnitude, angle in degrees)
12
+
13
+ Returns:
14
+ tuple: Resultant force magnitude and angle
15
+ """
16
+ # Convert forces to x and y components
17
+ fx = sum(force[0] * np.cos(np.radians(force[1])) for force in forces)
18
+ fy = sum(force[0] * np.sin(np.radians(force[1])) for force in forces)
19
+
20
+ # Calculate resultant magnitude and angle
21
+ resultant_magnitude = np.sqrt(fx**2 + fy**2)
22
+ resultant_angle = np.degrees(np.arctan2(fy, fx))
23
+
24
+ return resultant_magnitude, resultant_angle
25
+
26
+ def draw_force_diagram(forces):
27
+ """
28
+ Create a force diagram using matplotlib.
29
+
30
+ Args:
31
+ forces (list): List of tuples (magnitude, angle in degrees)
32
+
33
+ Returns:
34
+ matplotlib figure
35
+ """
36
+ fig, ax = plt.subplots(figsize=(8, 8))
37
+ ax.set_xlim(-10, 10)
38
+ ax.set_ylim(-10, 10)
39
+ ax.grid(True, linestyle='--', alpha=0.7)
40
+ ax.set_title('Force Diagram')
41
+ ax.set_xlabel('X-axis')
42
+ ax.set_ylabel('Y-axis')
43
+
44
+ # Draw origin point
45
+ ax.plot(0, 0, 'ro', markersize=10)
46
+
47
+ # Colors for different forces
48
+ colors = ['blue', 'green', 'red', 'purple', 'orange']
49
+
50
+ # Draw individual forces
51
+ for i, (magnitude, angle) in enumerate(forces):
52
+ color = colors[i % len(colors)]
53
+ # Calculate x and y components
54
+ fx = magnitude * np.cos(np.radians(angle))
55
+ fy = magnitude * np.sin(np.radians(angle))
56
+
57
+ # Draw force vector
58
+ ax.arrow(0, 0, fx, fy, head_width=0.3, head_length=0.5,
59
+ fc=color, ec=color, alpha=0.7,
60
+ label=f'Force {i+1}: {magnitude} N at {angle}°')
61
+
62
+ # Calculate and draw resultant force
63
+ resultant_magnitude, resultant_angle = calculate_resultant_force(forces)
64
+ ax.arrow(0, 0,
65
+ resultant_magnitude * np.cos(np.radians(resultant_angle)),
66
+ resultant_magnitude * np.sin(np.radians(resultant_angle)),
67
+ head_width=0.5, head_length=0.8,
68
+ fc='black', ec='black', alpha=1,
69
+ label=f'Resultant: {resultant_magnitude:.2f} N at {resultant_angle:.2f}°')
70
+
71
+ ax.legend()
72
+ return fig
73
+
74
+ def main():
75
+ st.title('Force Visualization Simulator')
76
+
77
+ st.sidebar.header('Force Input')
78
+
79
+ # Initialize session state for forces
80
+ if 'forces' not in st.session_state:
81
+ st.session_state.forces = []
82
+
83
+ # Force input
84
+ magnitude = st.sidebar.number_input('Force Magnitude (N)', min_value=0.0, step=0.1, value=5.0)
85
+ angle = st.sidebar.number_input('Force Angle (degrees)', min_value=0.0, max_value=360.0, step=1.0, value=0.0)
86
+
87
+ # Add force button
88
+ if st.sidebar.button('Add Force'):
89
+ st.session_state.forces.append((magnitude, angle))
90
+
91
+ # Remove force button
92
+ if st.sidebar.button('Remove Last Force') and st.session_state.forces:
93
+ st.session_state.forces.pop()
94
+
95
+ # Clear all forces
96
+ if st.sidebar.button('Clear All Forces'):
97
+ st.session_state.forces = []
98
+
99
+ # Display current forces
100
+ st.sidebar.subheader('Current Forces:')
101
+ for i, (mag, ang) in enumerate(st.session_state.forces, 1):
102
+ st.sidebar.text(f'Force {i}: {mag} N at {ang}°')
103
+
104
+ # Visualization
105
+ if st.session_state.forces:
106
+ fig = draw_force_diagram(st.session_state.forces)
107
+ st.pyplot(fig)
108
+
109
+ # Resultant force calculation
110
+ resultant_magnitude, resultant_angle = calculate_resultant_force(st.session_state.forces)
111
+ st.subheader('Resultant Force')
112
+ st.write(f'Magnitude: {resultant_magnitude:.2f} N')
113
+ st.write(f'Angle: {resultant_angle:.2f}°')
114
+ else:
115
+ st.info('Add forces to visualize the force diagram')
116
+
117
+ # Physics explanation
118
+ st.sidebar.markdown("""
119
+ ### How to Use
120
+ 1. Enter force magnitude and angle
121
+ 2. Click 'Add Force' to include in diagram
122
+ 3. Visualize vector forces and resultant
123
+
124
+ ### Force Visualization Concepts
125
+ - **Magnitude**: Strength of the force (Newtons)
126
+ - **Angle**: Direction of the force (0-360 degrees)
127
+ - **Resultant Force**: Combined effect of all forces
128
+ """)
129
+
130
+ if __name__ == '__main__':
131
+ main()
pages/free-fall.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ def calculate_free_fall(initial_height, gravity=9.8):
6
+ """
7
+ Calculate free fall motion parameters
8
+
9
+ Args:
10
+ initial_height (float): Starting height in meters
11
+ gravity (float): Acceleration due to gravity (default 9.8 m/s²)
12
+
13
+ Returns:
14
+ tuple: Time of fall, position array, velocity array
15
+ """
16
+ # Calculate time to hit ground
17
+ time_to_ground = np.sqrt(2 * initial_height / gravity)
18
+
19
+ # Create time array
20
+ time = np.linspace(0, time_to_ground, 100)
21
+
22
+ # Calculate position and velocity
23
+ position = initial_height - 0.5 * gravity * time**2
24
+ velocity = -gravity * time
25
+
26
+ return time, position, velocity
27
+
28
+ def plot_free_fall(time, position, velocity):
29
+ """
30
+ Create plots for free fall motion
31
+
32
+ Args:
33
+ time (array): Time array
34
+ position (array): Position array
35
+ velocity (array): Velocity array
36
+
37
+ Returns:
38
+ matplotlib figure
39
+ """
40
+ # Create figure and axis
41
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 10),
42
+ gridspec_kw={'height_ratios': [2, 1]})
43
+
44
+ # Position plot
45
+ ax1.plot(time, position, 'b-')
46
+ ax1.scatter(time[-1], position[-1], color='red', s=100)
47
+ ax1.set_title('Free Fall - Position vs Time')
48
+ ax1.set_xlabel('Time (s)')
49
+ ax1.set_ylabel('Height (m)')
50
+ ax1.grid(True, linestyle='--', alpha=0.7)
51
+
52
+ # Velocity plot
53
+ ax2.plot(time, velocity, 'g-')
54
+ ax2.scatter(time[-1], velocity[-1], color='red', s=100)
55
+ ax2.set_title('Free Fall - Velocity vs Time')
56
+ ax2.set_xlabel('Time (s)')
57
+ ax2.set_ylabel('Velocity (m/s)')
58
+ ax2.grid(True, linestyle='--', alpha=0.7)
59
+
60
+ plt.tight_layout()
61
+ return fig
62
+
63
+ def main():
64
+ """
65
+ Streamlit app for Free Fall Simulation
66
+ """
67
+ st.title('Free Fall Motion Simulator')
68
+
69
+ # Sidebar for inputs
70
+ st.sidebar.header('Simulation Parameters')
71
+
72
+ # Initial height input
73
+ initial_height = st.sidebar.slider(
74
+ 'Initial Height (m)',
75
+ min_value=1.0,
76
+ max_value=100.0,
77
+ value=10.0,
78
+ step=0.5
79
+ )
80
+
81
+ # Gravity selection (optional)
82
+ gravity_options = [
83
+ ('Earth (9.8 m/s²)', 9.8),
84
+ ('Moon (1.6 m/s²)', 1.6),
85
+ ('Mars (3.7 m/s²)', 3.7),
86
+ ]
87
+ gravity = st.sidebar.selectbox(
88
+ 'Planet/Location',
89
+ gravity_options,
90
+ format_func=lambda x: x[0]
91
+ )[1]
92
+
93
+ # Calculate free fall motion
94
+ time, position, velocity = calculate_free_fall(initial_height, gravity)
95
+
96
+ # Create columns for information display
97
+ col1, col2 = st.columns(2)
98
+
99
+ with col1:
100
+ st.metric('Initial Height', f'{initial_height} m')
101
+ st.metric('Time to Ground', f'{time[-1]:.2f} s')
102
+
103
+ with col2:
104
+ st.metric('Gravity', f'{gravity} m/s²')
105
+ st.metric('Final Velocity', f'{velocity[-1]:.2f} m/s')
106
+
107
+ # Create and display plots
108
+ st.header('Motion Visualization')
109
+ fig = plot_free_fall(time, position, velocity)
110
+
111
+ # Display matplotlib figure
112
+ st.pyplot(fig)
113
+
114
+ # Detailed explanation
115
+ st.markdown("""
116
+ ### Physics Behind Free Fall
117
+
118
+ In free fall motion:
119
+ - The object falls under the influence of gravity
120
+ - Acceleration is constant (9.8 m/s² on Earth)
121
+ - Air resistance is neglected
122
+ - Position follows quadratic equation:
123
+ $h(t) = h_0 - \\frac{1}{2}gt^2$
124
+ - Velocity follows linear equation:
125
+ $v(t) = -gt$
126
+
127
+ ### How to Interpret the Graphs
128
+ - Top Graph: Shows object's height decreasing over time
129
+ - Bottom Graph: Shows velocity increasing in magnitude
130
+ (becoming more negative) as the object falls
131
+ - Red dot indicates final position/velocity
132
+ """)
133
+
134
+ if __name__ == '__main__':
135
+ main()
136
+
137
+ # Installation requirements
138
+ # Run in terminal:
139
+ # pip install streamlit numpy matplotlib
140
+ # streamlit run free_fall_simulation.py
pages/friction.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import pandas as pd
5
+ import seaborn as sns
6
+
7
+ class FrictionSimulation:
8
+ def __init__(self):
9
+ # Constants for simulation
10
+ self.g = 9.8 # Acceleration due to gravity (m/s^2)
11
+ self.mass = 10.0 # Mass of the object (kg)
12
+ self.angle = 0.0 # Angle of the inclined plane (degrees)
13
+ self.coefficient_static = 0.5 # Static friction coefficient
14
+ self.coefficient_kinetic = 0.3 # Kinetic friction coefficient
15
+
16
+ def calculate_forces(self):
17
+ """
18
+ Calculate forces acting on an object on an inclined plane
19
+ """
20
+ # Convert angle to radians
21
+ theta = np.deg2rad(self.angle)
22
+
23
+ # Normal force
24
+ normal_force = self.mass * self.g * np.cos(theta)
25
+
26
+ # Weight components
27
+ weight_parallel = self.mass * self.g * np.sin(theta)
28
+ weight_perpendicular = self.mass * self.g * np.cos(theta)
29
+
30
+ # Maximum static friction
31
+ max_static_friction = self.coefficient_static * normal_force
32
+
33
+ # Kinetic friction
34
+ kinetic_friction = self.coefficient_kinetic * normal_force
35
+
36
+ return {
37
+ 'Normal Force': normal_force,
38
+ 'Weight Parallel': weight_parallel,
39
+ 'Weight Perpendicular': weight_perpendicular,
40
+ 'Max Static Friction': max_static_friction,
41
+ 'Kinetic Friction': kinetic_friction
42
+ }
43
+
44
+ def plot_force_diagram(self, forces):
45
+ """
46
+ Create a force diagram visualization
47
+ """
48
+ plt.figure(figsize=(10, 6))
49
+
50
+ # Create a bar plot of forces
51
+ force_names = list(forces.keys())
52
+ force_values = list(forces.values())
53
+
54
+ plt.bar(force_names, force_values)
55
+ plt.title('Forces Acting on Object')
56
+ plt.xlabel('Force Types')
57
+ plt.ylabel('Force Magnitude (N)')
58
+ plt.xticks(rotation=45, ha='right')
59
+ plt.tight_layout()
60
+
61
+ return plt
62
+
63
+ def main():
64
+ st.title('Interactive Friction Simulation')
65
+
66
+ # Sidebar for inputs
67
+ st.sidebar.header('Simulation Parameters')
68
+
69
+ # Create simulation instance
70
+ sim = FrictionSimulation()
71
+
72
+ # Mass input
73
+ sim.mass = st.sidebar.slider('Object Mass (kg)', 1.0, 50.0, 10.0, 0.5)
74
+
75
+ # Angle input
76
+ sim.angle = st.sidebar.slider('Incline Angle (degrees)', 0, 90, 30, 1)
77
+
78
+ # Friction coefficients
79
+ sim.coefficient_static = st.sidebar.slider('Static Friction Coefficient', 0.0, 1.0, 0.5, 0.01)
80
+ sim.coefficient_kinetic = st.sidebar.slider('Kinetic Friction Coefficient', 0.0, 1.0, 0.3, 0.01)
81
+
82
+ # Calculate forces
83
+ forces = sim.calculate_forces()
84
+
85
+ # Display force calculations
86
+ st.header('Force Calculations')
87
+ force_df = pd.DataFrame.from_dict(forces, orient='index', columns=['Force (N)'])
88
+ st.dataframe(force_df)
89
+
90
+ # Visualization of forces
91
+ st.header('Force Diagram')
92
+ fig = sim.plot_force_diagram(forces)
93
+ st.pyplot(fig)
94
+
95
+ # Explanation section
96
+ st.header('Friction Explanation')
97
+ st.markdown("""
98
+ ### Understanding Friction on an Inclined Plane
99
+
100
+ - **Normal Force**: The force perpendicular to the surface, supporting the object's weight
101
+ - **Weight Parallel**: Component of weight pushing the object down the slope
102
+ - **Max Static Friction**: Maximum friction force that prevents object from sliding
103
+ - **Kinetic Friction**: Friction force when the object is moving
104
+
105
+ #### When will the object start sliding?
106
+ - If the parallel component of weight exceeds maximum static friction
107
+ - The critical angle can be calculated using the static friction coefficient
108
+ """)
109
+
110
+ # Critical angle calculation
111
+ critical_angle = np.arctan(sim.coefficient_static) * 180 / np.pi
112
+ st.info(f"Critical Angle: {critical_angle:.2f} degrees")
113
+
114
+ if __name__ == '__main__':
115
+ main()
pages/general-relativity.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+
5
+ def generate_surface(mass, grid_size=100, range_val=10):
6
+ """
7
+ Generates a grid and calculates a "curvature" value for each point,
8
+ using a simplified gravitational potential analogy:
9
+
10
+ Z = - mass / sqrt(x^2 + y^2 + epsilon)
11
+
12
+ The epsilon is added to avoid singularity at (0,0).
13
+ """
14
+ x = np.linspace(-range_val, range_val, grid_size)
15
+ y = np.linspace(-range_val, range_val, grid_size)
16
+ X, Y = np.meshgrid(x, y)
17
+ epsilon = 0.1 # small constant to prevent division by zero
18
+ Z = -mass / np.sqrt(X**2 + Y**2 + epsilon)
19
+ return X, Y, Z
20
+
21
+ def main():
22
+ st.title("General Relativity Visualization")
23
+ st.write("""
24
+ This simulation visualizes a simplified model of space-time curvature due to a massive object.
25
+ Although general relativity is much more complex, the following "rubber-sheet" analogy
26
+ helps illustrate how mass can curve space.
27
+ """)
28
+
29
+ # Sidebar controls for interactivity
30
+ mass = st.sidebar.slider("Mass", min_value=1.0, max_value=10.0, value=5.0, step=0.5,
31
+ help="Increase mass to deepen the gravitational well.")
32
+ grid_size = st.sidebar.slider("Grid Resolution", min_value=50, max_value=200, value=100, step=10,
33
+ help="Adjust the grid resolution of the visualization.")
34
+ range_val = st.sidebar.slider("Range", min_value=5, max_value=20, value=10, step=1,
35
+ help="Set the spatial range for the grid (in arbitrary units).")
36
+
37
+ # Generate the grid and curvature data
38
+ X, Y, Z = generate_surface(mass, grid_size, range_val)
39
+
40
+ # Create a 3D surface plot using Plotly
41
+ fig = go.Figure(data=[go.Surface(x=X, y=Y, z=Z, colorscale="Viridis")])
42
+ fig.update_layout(
43
+ title="Space-time Curvature",
44
+ scene=dict(
45
+ xaxis_title="X",
46
+ yaxis_title="Y",
47
+ zaxis_title="Curvature (analogy)",
48
+ aspectratio=dict(x=1, y=1, z=0.5)
49
+ ),
50
+ autosize=True
51
+ )
52
+
53
+ st.plotly_chart(fig, use_container_width=True)
54
+
55
+ if __name__ == "__main__":
56
+ main()
pages/gravity.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+ import matplotlib.animation as animation
6
+
7
+ class GravitySimulation:
8
+ def __init__(self, num_bodies=3, width=10, height=10):
9
+ """
10
+ Initialize the gravity simulation
11
+
12
+ Parameters:
13
+ -----------
14
+ num_bodies : int, optional
15
+ Number of bodies in the simulation (default is 3)
16
+ width : float, optional
17
+ Width of the simulation space (default is 10)
18
+ height : float, optional
19
+ Height of the simulation space (default is 10)
20
+ """
21
+ self.num_bodies = num_bodies
22
+ self.width = width
23
+ self.height = height
24
+
25
+ # Gravitational constant (scaled for visualization)
26
+ self.G = 1.0
27
+
28
+ # Initialize positions, velocities, and masses
29
+ self.positions = np.random.rand(num_bodies, 2) * np.array([width, height])
30
+ self.velocities = np.random.randn(num_bodies, 2) * 0.1
31
+ self.masses = np.random.uniform(0.5, 2, num_bodies)
32
+
33
+ def compute_accelerations(self):
34
+ """
35
+ Compute gravitational accelerations between bodies
36
+
37
+ Returns:
38
+ --------
39
+ numpy.ndarray
40
+ Accelerations for each body
41
+ """
42
+ accelerations = np.zeros_like(self.positions)
43
+
44
+ for i in range(self.num_bodies):
45
+ for j in range(self.num_bodies):
46
+ if i != j:
47
+ # Vector from body i to body j
48
+ r_vector = self.positions[j] - self.positions[i]
49
+
50
+ # Distance between bodies
51
+ r_magnitude = np.linalg.norm(r_vector)
52
+
53
+ # Avoid division by zero
54
+ if r_magnitude > 0:
55
+ # Gravitational acceleration
56
+ acceleration_magnitude = self.G * self.masses[j] / (r_magnitude ** 2)
57
+
58
+ # Direction of acceleration
59
+ acceleration = acceleration_magnitude * (r_vector / r_magnitude)
60
+
61
+ accelerations[i] += acceleration
62
+
63
+ return accelerations
64
+
65
+ def update(self, dt=0.01):
66
+ """
67
+ Update positions and velocities using Euler integration
68
+
69
+ Parameters:
70
+ -----------
71
+ dt : float, optional
72
+ Time step for simulation (default is 0.01)
73
+ """
74
+ # Compute accelerations
75
+ accelerations = self.compute_accelerations()
76
+
77
+ # Update velocities
78
+ self.velocities += accelerations * dt
79
+
80
+ # Update positions
81
+ self.positions += self.velocities * dt
82
+
83
+ # Simple boundary conditions (bouncing off walls)
84
+ for i in range(self.num_bodies):
85
+ for dim in range(2):
86
+ if (self.positions[i, dim] < 0) or (self.positions[i, dim] > [self.width, self.height][dim]):
87
+ self.velocities[i, dim] *= -0.9 # Damped bounce
88
+ self.positions[i, dim] = np.clip(self.positions[i, dim], 0, [self.width, self.height][dim])
89
+
90
+ def main():
91
+ st.title("Gravitational N-Body Simulation")
92
+
93
+ # Sidebar for simulation parameters
94
+ st.sidebar.header("Simulation Parameters")
95
+
96
+ # Number of bodies slider
97
+ num_bodies = st.sidebar.slider("Number of Bodies", min_value=2, max_value=10, value=3)
98
+
99
+ # Simulation space dimensions
100
+ width = st.sidebar.number_input("Simulation Width", min_value=5.0, max_value=20.0, value=10.0)
101
+ height = st.sidebar.number_input("Simulation Height", min_value=5.0, max_value=20.0, value=10.0)
102
+
103
+ # Time step slider
104
+ dt = st.sidebar.slider("Time Step", min_value=0.001, max_value=0.1, value=0.01, step=0.001)
105
+
106
+ # Create simulation
107
+ sim = GravitySimulation(num_bodies=num_bodies, width=width, height=height)
108
+
109
+ # Matplotlib figure for animation
110
+ fig, ax = plt.subplots(figsize=(8, 6))
111
+ ax.set_xlim(0, width)
112
+ ax.set_ylim(0, height)
113
+ ax.set_title("Gravitational Interaction")
114
+ ax.set_xlabel("X Position")
115
+ ax.set_ylabel("Y Position")
116
+
117
+ # Scatter plot for bodies
118
+ scatter = ax.scatter(sim.positions[:, 0], sim.positions[:, 1],
119
+ c=sim.masses, cmap='viridis',
120
+ s=sim.masses*100, alpha=0.7)
121
+
122
+ # Animation update function
123
+ def update_plot(frame):
124
+ sim.update(dt)
125
+ scatter.set_offsets(sim.positions)
126
+ return scatter,
127
+
128
+ # Create animation
129
+ anim = FuncAnimation(fig, update_plot, frames=200, interval=50, blit=True)
130
+
131
+ # Convert animation to HTML for Streamlit
132
+ plt.close(fig)
133
+
134
+ # Display animation
135
+ st.pyplot(fig)
136
+
137
+ # Optional: Provide download of animation
138
+ st.sidebar.header("Animation Options")
139
+ if st.sidebar.button("Save Animation"):
140
+ # Save animation as gif
141
+ anim.save('gravity_simulation.gif', writer='pillow')
142
+ with open('gravity_simulation.gif', 'rb') as f:
143
+ st.sidebar.download_button(
144
+ label="Download Animation",
145
+ data=f.read(),
146
+ file_name="gravity_simulation.gif",
147
+ mime="image/gif"
148
+ )
149
+
150
+ # Information section
151
+ st.markdown("## About the Simulation")
152
+ st.markdown("""
153
+ This simulation demonstrates gravitational interactions between multiple bodies:
154
+ - Bodies attract each other based on their masses
155
+ - Gravitational force is inversely proportional to the square of the distance
156
+ - Bodies bounce off the simulation boundaries
157
+ - Larger/darker points represent bodies with more mass
158
+ """)
159
+
160
+ if __name__ == "__main__":
161
+ main()
pages/heat.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import time
5
+
6
+ st.title("Heat Diffusion Simulation")
7
+
8
+ # Simulation parameters
9
+ nx, ny = 50, 50 # Grid size
10
+ alpha = st.slider("Thermal Diffusivity (α)", min_value=0.1, max_value=1.0, value=0.2, step=0.1)
11
+ dt = st.slider("Time Step (dt)", min_value=0.001, max_value=0.1, value=0.01, step=0.005)
12
+ steps = st.number_input("Number of Simulation Steps", min_value=10, max_value=1000, value=100, step=10)
13
+
14
+ # Initialize temperature field: all zeros with a hot spot in the center
15
+ T = np.zeros((nx, ny))
16
+ T[nx//2, ny//2] = 100.0
17
+
18
+ # Placeholder for plotting
19
+ plot_placeholder = st.empty()
20
+
21
+ def update_temperature(T, alpha, dt):
22
+ """
23
+ Update temperature using a simple finite difference scheme for the 2D heat equation.
24
+ The update formula for interior grid points is:
25
+ T_new[i, j] = T[i, j] + α * dt * (T[i+1, j] + T[i-1, j] + T[i, j+1] + T[i, j-1] - 4*T[i, j])
26
+ """
27
+ T_new = T.copy()
28
+ T_new[1:-1, 1:-1] = T[1:-1, 1:-1] + alpha * dt * (
29
+ T[2:, 1:-1] + T[:-2, 1:-1] + T[1:-1, 2:] + T[1:-1, :-2] - 4 * T[1:-1, 1:-1]
30
+ )
31
+ return T_new
32
+
33
+ # Run the simulation loop
34
+ for i in range(int(steps)):
35
+ T = update_temperature(T, alpha, dt)
36
+
37
+ # Create the plot
38
+ fig, ax = plt.subplots()
39
+ heatmap = ax.imshow(T, cmap='hot', interpolation='nearest')
40
+ ax.set_title(f"Step {i+1}")
41
+ ax.axis('off')
42
+
43
+ # Display the plot in the Streamlit placeholder
44
+ plot_placeholder.pyplot(fig)
45
+
46
+ # Pause to simulate time evolution (adjust the sleep time as needed)
47
+ time.sleep(0.1)
pages/ideal-gas-law.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ # Set the title and description for the simulation
6
+ st.title("Ideal Gas Law Visualization")
7
+ st.write("""
8
+ This simulation visualizes the relationship between pressure, volume, and temperature of an ideal gas using the equation:
9
+ \[ P = \frac{nRT}{V} \]
10
+ Adjust the parameters in the sidebar to see how the pressure changes.
11
+ """)
12
+
13
+ # Constants
14
+ R = 8.314 # Ideal gas constant in J/(mol*K)
15
+
16
+ # Sidebar for user inputs
17
+ st.sidebar.header("Gas Parameters")
18
+ n = st.sidebar.slider("Number of moles (n)", min_value=0.1, max_value=5.0, value=1.0, step=0.1)
19
+ T = st.sidebar.slider("Temperature (T in Kelvin)", min_value=100, max_value=1000, value=300, step=50)
20
+ V = st.sidebar.slider("Volume (V in cubic meters)", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
21
+
22
+ # Calculate pressure using the ideal gas law
23
+ P = n * R * T / V
24
+ st.write(f"### Computed Pressure:")
25
+ st.write(f"The pressure of the gas is **{P:.2f} Pascals**.")
26
+
27
+ # Create a range of volumes to plot the Pressure vs Volume curve
28
+ volumes = np.linspace(0.1, 10, 200)
29
+ pressures = n * R * T / volumes
30
+
31
+ # Plotting the curve
32
+ fig, ax = plt.subplots()
33
+ ax.plot(volumes, pressures, color='b', label=f"T = {T} K, n = {n} mol")
34
+ ax.set_xlabel("Volume (m³)")
35
+ ax.set_ylabel("Pressure (Pascals)")
36
+ ax.set_title("Pressure vs Volume")
37
+ ax.legend()
38
+ ax.grid(True)
39
+
40
+ st.pyplot(fig)
pages/impulse.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ def calculate_impulse(force, time):
6
+ """Calculate impulse given force and time."""
7
+ return force * time
8
+
9
+ def calculate_velocity_change(mass, impulse):
10
+ """Calculate change in velocity using impulse-momentum theorem."""
11
+ return impulse / mass
12
+
13
+ def plot_force_time_graph(force, time):
14
+ """Create a force-time graph."""
15
+ plt.figure(figsize=(10, 6))
16
+ plt.plot([0, time, time], [force, force, 0], 'b-')
17
+ plt.fill_between([0, time], [force, force], alpha=0.3)
18
+ plt.title('Force-Time Graph')
19
+ plt.xlabel('Time (s)')
20
+ plt.ylabel('Force (N)')
21
+ plt.grid(True)
22
+ return plt
23
+
24
+ def main():
25
+ st.title('Impulse Visualization')
26
+
27
+ # Sidebar for input parameters
28
+ st.sidebar.header('Simulation Parameters')
29
+
30
+ # Mass input
31
+ mass = st.sidebar.number_input(
32
+ 'Mass of Object (kg)',
33
+ min_value=0.1,
34
+ max_value=100.0,
35
+ value=1.0,
36
+ step=0.1
37
+ )
38
+
39
+ # Force input with different input methods
40
+ input_type = st.sidebar.radio(
41
+ 'Force Input Method',
42
+ ['Constant Force', 'Peak Force and Duration']
43
+ )
44
+
45
+ if input_type == 'Constant Force':
46
+ # Constant force input
47
+ force = st.sidebar.number_input(
48
+ 'Constant Force (N)',
49
+ min_value=-1000.0,
50
+ max_value=1000.0,
51
+ value=10.0,
52
+ step=1.0
53
+ )
54
+ time = st.sidebar.number_input(
55
+ 'Duration (s)',
56
+ min_value=0.01,
57
+ max_value=10.0,
58
+ value=0.5,
59
+ step=0.1
60
+ )
61
+ else:
62
+ # Peak force and duration input
63
+ force = st.sidebar.number_input(
64
+ 'Peak Force (N)',
65
+ min_value=-1000.0,
66
+ max_value=1000.0,
67
+ value=50.0,
68
+ step=1.0
69
+ )
70
+ time = st.sidebar.number_input(
71
+ 'Force Duration (s)',
72
+ min_value=0.01,
73
+ max_value=10.0,
74
+ value=0.5,
75
+ step=0.1
76
+ )
77
+
78
+ # Calculate impulse and velocity change
79
+ impulse = calculate_impulse(force, time)
80
+ velocity_change = calculate_velocity_change(mass, impulse)
81
+
82
+ # Display results
83
+ st.header('Impulse Calculation Results')
84
+ col1, col2, col3 = st.columns(3)
85
+
86
+ with col1:
87
+ st.metric('Force', f'{force} N')
88
+ with col2:
89
+ st.metric('Duration', f'{time} s')
90
+ with col3:
91
+ st.metric('Mass', f'{mass} kg')
92
+
93
+ st.markdown('---')
94
+
95
+ col1, col2 = st.columns(2)
96
+
97
+ with col1:
98
+ st.metric('Impulse', f'{impulse:.2f} N·s')
99
+ with col2:
100
+ st.metric('Velocity Change', f'{velocity_change:.2f} m/s')
101
+
102
+ # Visualize Force-Time Graph
103
+ st.header('Force-Time Graph')
104
+ fig = plot_force_time_graph(force, time)
105
+ st.pyplot(fig)
106
+
107
+ # Explanation section
108
+ st.header('Understanding Impulse')
109
+ st.markdown("""
110
+ ### What is Impulse?
111
+ - **Impulse** is defined as the product of force and time: Impulse = Force × Time
112
+ - It represents the total effect of a force acting over a period of time
113
+ - Impulse is equal to the change in momentum of an object
114
+
115
+ ### Key Concepts
116
+ - The area under a Force-Time graph represents the impulse
117
+ - Larger impulse means a greater change in velocity
118
+ - Impulse can be increased by either increasing force or increasing time of application
119
+ """)
120
+
121
+ if __name__ == '__main__':
122
+ main()
pages/inertia.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
5
+
6
+ def inertia_simulation():
7
+ st.title('Inertia Visualization Simulation')
8
+
9
+ # Sidebar controls
10
+ st.sidebar.header('Simulation Parameters')
11
+
12
+ # Object mass
13
+ mass = st.sidebar.slider('Object Mass (kg)', min_value=1, max_value=25, value=10)
14
+
15
+ # Initial velocity
16
+ initial_velocity = st.sidebar.slider('Initial Velocity (m/s)', min_value=0, max_value=20, value=10)
17
+
18
+ # Friction coefficient
19
+ friction = st.sidebar.slider('Friction Coefficient', min_value=0.0, max_value=1.0, value=0.2, step=0.1)
20
+
21
+ # Explanation
22
+ st.markdown("""
23
+ ## Understanding Inertia
24
+
25
+ Inertia is an object's resistance to change in its state of motion.
26
+ This simulation shows how:
27
+ - Heavier objects maintain their motion longer
28
+ - Friction gradually reduces an object's speed
29
+ - Objects tend to continue moving unless stopped
30
+ """)
31
+
32
+ # Simulation calculations
33
+ def calculate_motion(mass, initial_velocity, friction, time):
34
+ # Gravitational acceleration
35
+ g = 9.8
36
+
37
+ # Deceleration due to friction
38
+ deceleration = friction * g *mass
39
+
40
+ # Calculate position and velocity
41
+ if time <= initial_velocity / deceleration:
42
+ # Object is still moving forward
43
+ position = initial_velocity * time - 0.5 * deceleration * time**2
44
+ velocity = initial_velocity - deceleration * time
45
+ else:
46
+ # Object has come to a stop
47
+ # Find the time when object stops
48
+ stop_time = initial_velocity / deceleration
49
+
50
+ # Calculate final position
51
+ final_position = initial_velocity * stop_time - 0.5 * deceleration * stop_time**2
52
+
53
+ # Position after stopping point remains constant
54
+ position = final_position
55
+ velocity = 0
56
+
57
+ return position, velocity
58
+
59
+ # Prepare plot
60
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
61
+
62
+ # Time array
63
+ time = np.linspace(0, 5, 100)
64
+
65
+ # Calculate motion
66
+ positions = []
67
+ velocities = []
68
+ for t in time:
69
+ pos, vel = calculate_motion(mass, initial_velocity, friction, t)
70
+ positions.append(pos)
71
+ velocities.append(vel)
72
+
73
+ # Position plot
74
+ ax1.plot(time, positions, label='Position')
75
+ ax1.set_title(f'Position over Time (Mass: {mass} kg)')
76
+ ax1.set_xlabel('Time (s)')
77
+ ax1.set_ylabel('Distance (m)')
78
+ ax1.legend()
79
+ ax1.grid(True)
80
+
81
+ # Velocity plot
82
+ ax2.plot(time, velocities, label='Velocity', color='red')
83
+ ax2.set_title(f'Velocity over Time (Initial Velocity: {initial_velocity} m/s)')
84
+ ax2.set_xlabel('Time (s)')
85
+ ax2.set_ylabel('Velocity (m/s)')
86
+ ax2.legend()
87
+ ax2.grid(True)
88
+
89
+ # Adjust layout and display
90
+ plt.tight_layout()
91
+ st.pyplot(fig)
92
+
93
+ # Detailed explanation
94
+ st.markdown(f"""
95
+ ### Simulation Details
96
+ - **Mass of Object:** {mass} kg
97
+ - **Initial Velocity:** {initial_velocity} m/s
98
+ - **Friction Coefficient:** {friction}
99
+
100
+ #### Observation
101
+ The graphs show how:
102
+ 1. Position changes over time (distance traveled)
103
+ 2. Velocity decreases due to friction
104
+ """)
105
+
106
+
107
+
108
+ # Streamlit page configuration
109
+ if __name__ == '__main__':
110
+ # Run the simulation
111
+ inertia_simulation()
pages/interference.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ def interference_pattern(lambda_, d, grid_size=200, x_range=5):
6
+ """
7
+ Compute the interference pattern for two point sources.
8
+
9
+ Parameters:
10
+ - lambda_: Wavelength of the waves (float).
11
+ - d: Separation distance between the two sources (float).
12
+ - grid_size: Number of points in the grid (int, default=200).
13
+ - x_range: Range of x and y coordinates (float, default=5).
14
+
15
+ Returns:
16
+ - I: 2D array of interference intensity.
17
+ - x: 1D array of x coordinates.
18
+ - y: 1D array of y coordinates.
19
+ - S1: Position of the first source (list).
20
+ - S2: Position of the second source (list).
21
+ """
22
+ # Create a 2D grid
23
+ x = np.linspace(-x_range, x_range, grid_size)
24
+ y = np.linspace(-x_range, x_range, grid_size)
25
+ X, Y = np.meshgrid(x, y)
26
+
27
+ # Define source positions
28
+ S1 = [-d/2, 0] # First source at (-d/2, 0)
29
+ S2 = [d/2, 0] # Second source at (d/2, 0)
30
+
31
+ # Calculate distances from each source to every point on the grid
32
+ r1 = np.sqrt((X - S1[0])**2 + (Y - S1[1])**2)
33
+ r2 = np.sqrt((X - S2[0])**2 + (Y - S2[1])**2)
34
+
35
+ # Compute path difference
36
+ delta = r2 - r1
37
+
38
+ # Calculate intensity (proportional to cos^2(π * Δ / λ))
39
+ I = np.cos(np.pi * delta / lambda_)**2
40
+
41
+ return I, x, y, S1, S2
42
+
43
+ # Streamlit app setup
44
+ st.title("Interference Pattern Simulation")
45
+
46
+ # Explanatory text
47
+ st.write("""
48
+ This simulation visualizes the interference pattern created by two point sources emitting waves.
49
+ - **Bright regions**: Constructive interference, where waves reinforce each other.
50
+ - **Dark regions**: Destructive interference, where waves cancel out.
51
+ Use the sliders below to adjust the **wavelength (λ)** and **source separation (d)** to see how the pattern changes.
52
+ """)
53
+
54
+ # Interactive sliders
55
+ lambda_ = st.slider("Wavelength λ", min_value=0.1, max_value=2.0, value=1.0, step=0.1)
56
+ d = st.slider("Source Separation d", min_value=0.5, max_value=5.0, value=2.0, step=0.1)
57
+
58
+ # Compute the interference pattern
59
+ I, x, y, S1, S2 = interference_pattern(lambda_, d)
60
+
61
+ # Create and display the plot
62
+ fig, ax = plt.subplots()
63
+ ax.imshow(I, cmap='hot', extent=[x.min(), x.max(), y.min(), y.max()], origin='lower')
64
+ ax.plot([S1[0], S2[0]], [S1[1], S2[1]], 'wo', markersize=5) # Mark sources with white dots
65
+ ax.set_xlabel("x")
66
+ ax.set_ylabel("y")
67
+ ax.set_title("Interference Pattern")
68
+ st.pyplot(fig)
pages/kinetic-theory-of-gas.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+ import time
5
+
6
+ # Streamlit app title and description
7
+ st.title("Kinetic Theory of Gases Simulation")
8
+ st.write("This simulation visualizes gas molecules as particles moving in a 2D container. Watch how their motion relates to temperature and pressure!")
9
+
10
+ # Simulation parameters
11
+ L = 1.0 # Container side length
12
+ N = 50 # Number of particles
13
+ sigma = 0.05 # Standard deviation of velocities (related to temperature)
14
+ dt = 0.01 # Time step for simulation
15
+
16
+ # Initialize particle positions and velocities
17
+ x = np.random.uniform(0, L, N)
18
+ y = np.random.uniform(0, L, N)
19
+ vx = np.random.normal(0, sigma, N)
20
+ vy = np.random.normal(0, sigma, N)
21
+
22
+ # Initialize momentum transfer for pressure calculation
23
+ total_momentum_transfer = 0
24
+
25
+ # Create placeholders for plot and metrics
26
+ plot_placeholder = st.empty()
27
+ metrics_placeholder = st.empty()
28
+
29
+ # Simulation loop
30
+ step = 0
31
+ while True:
32
+ # Update particle positions
33
+ x_new = x + vx * dt
34
+ y_new = y + vy * dt
35
+
36
+ # Handle collisions with walls and accumulate momentum transfer
37
+ for i in range(N):
38
+ if x_new[i] < 0:
39
+ x_new[i] = -x_new[i] # Reflect position
40
+ vx[i] = -vx[i] # Reverse velocity
41
+ total_momentum_transfer += 2 * abs(vx[i])
42
+ elif x_new[i] > L:
43
+ x_new[i] = 2 * L - x_new[i] # Reflect position
44
+ vx[i] = -vx[i] # Reverse velocity
45
+ total_momentum_transfer += 2 * abs(vx[i])
46
+ if y_new[i] < 0:
47
+ y_new[i] = -y_new[i] # Reflect position
48
+ vy[i] = -vy[i] # Reverse velocity
49
+ total_momentum_transfer += 2 * abs(vy[i])
50
+ elif y_new[i] > L:
51
+ y_new[i] = 2 * L - y_new[i] # Reflect position
52
+ vy[i] = -vy[i] # Reverse velocity
53
+ total_momentum_transfer += 2 * abs(vy[i])
54
+
55
+ # Update positions
56
+ x = x_new
57
+ y = y_new
58
+
59
+ # Increment step counter
60
+ step += 1
61
+
62
+ # Calculate and display temperature and pressure every 100 steps
63
+ if step % 100 == 0:
64
+ # Temperature: T = (1/2) * <v^2> in 2D (with k=1, m=1)
65
+ v_squared = vx**2 + vy**2
66
+ T = (1 / (2 * N)) * np.sum(v_squared)
67
+
68
+ # Pressure: P = momentum_transfer / (perimeter * time)
69
+ time_elapsed = 100 * dt
70
+ P = total_momentum_transfer / (4 * L * time_elapsed)
71
+
72
+ # Update metrics display
73
+ metrics_placeholder.write(f"Temperature: {T:.4f} | Pressure: {P:.4f}")
74
+
75
+ # Reset momentum transfer
76
+ total_momentum_transfer = 0
77
+
78
+ # Create and update the Plotly scatter plot
79
+ fig = go.Figure()
80
+ fig.add_trace(go.Scatter(x=x, y=y, mode='markers', marker=dict(size=5)))
81
+ fig.update_layout(
82
+ xaxis_range=[0, L],
83
+ yaxis_range=[0, L],
84
+ width=500,
85
+ height=500,
86
+ title="Gas Molecules in Motion"
87
+ )
88
+ plot_placeholder.plotly_chart(fig)
89
+
90
+ # Control animation speed
91
+ time.sleep(0.05)
pages/lenz-law.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+
5
+ st.title("Lenz's Law Visual Simulation")
6
+
7
+ # --- Simulation Controls ---
8
+ st.header("Simulation Controls")
9
+ magnet_position = st.slider("Magnet Position", -5.0, 5.0, 0.0, step=0.1,
10
+ help="Control the horizontal position of the magnet.")
11
+ magnet_velocity = st.slider("Magnet Velocity (for change visualization)", -1.0, 1.0, 0.0, step=0.1,
12
+ help="Simulate magnet velocity to show change in flux (for visualization purposes only). A positive velocity means magnet is moving right, negative velocity means left.")
13
+ show_field_lines = st.checkbox("Show Magnetic Field Lines", value=True)
14
+ show_induced_current = st.checkbox("Show Induced Current", value=True)
15
+ show_forces = st.checkbox("Show Forces", value=True)
16
+
17
+ # --- Plotting Area ---
18
+ plot_placeholder = st.empty() # Placeholder for the plot
19
+
20
+ def draw_magnet(ax, x_magnet, magnet_width=1.0, magnet_height=0.5):
21
+ """Draws the magnet on the plot."""
22
+ y_center = 0
23
+ ax.add_patch(plt.Rectangle((x_magnet - magnet_width / 2, y_center - magnet_height / 2), magnet_width, magnet_height,
24
+ facecolor='red', edgecolor='black'))
25
+ ax.text(x_magnet - magnet_width / 4, y_center, 'N', color='white', ha='center', va='center', fontweight='bold')
26
+ ax.text(x_magnet + magnet_width / 4, y_center, 'S', color='white', ha='center', va='center', fontweight='bold')
27
+
28
+ def draw_coil(ax, coil_x_center=0, coil_width=1.5, coil_height=1.0):
29
+ """Draws the coil on the plot."""
30
+ y_center = 0
31
+ ax.add_patch(plt.Rectangle((coil_x_center - coil_width / 2, y_center - coil_height / 2), coil_width, coil_height,
32
+ facecolor='lightgray', edgecolor='black'))
33
+ ax.text(coil_x_center, y_center, 'Coil', color='black', ha='center', va='center')
34
+
35
+ def draw_magnetic_field_magnet(ax, x_magnet, field_strength=0.5, num_lines=15):
36
+ """Draws simplified magnetic field lines from the magnet."""
37
+ y_center = 0
38
+ start_x = x_magnet - 0.5 # Approximate North pole position for field lines
39
+ end_x = x_magnet + 0.5 # Approximate South pole position
40
+
41
+ for i in range(num_lines):
42
+ y_offset = (i - num_lines // 2) * 0.2 * field_strength
43
+ if y_offset != 0: # Avoid drawing lines exactly on top of each other which can cause issues
44
+ ax.plot([start_x, end_x], [y_center + 0.2 + y_offset, y_center + 0.2 - y_offset], color='blue', linewidth=0.5, alpha=0.7) # Lines above
45
+ ax.plot([start_x, end_x], [y_center - 0.2 + y_offset, y_center - 0.2 - y_offset], color='blue', linewidth=0.5, alpha=0.7) # Lines below
46
+
47
+ def draw_induced_current(ax, coil_x_center, magnet_velocity):
48
+ """Draws arrows indicating induced current direction based on magnet velocity."""
49
+ y_center = 0
50
+ current_direction = 0 # 0: No current, 1: Clockwise, -1: Counter-clockwise
51
+
52
+ if magnet_velocity > 0.1: # Magnet moving right (towards coil)
53
+ current_direction = 1 # Clockwise to oppose increasing flux into the page (assuming field is into the page when N pole approaches)
54
+ elif magnet_velocity < -0.1: # Magnet moving left (away from coil)
55
+ current_direction = -1 # Counter-clockwise to oppose decreasing flux into the page
56
+
57
+ if current_direction == 1: # Clockwise
58
+ ax.arrow(coil_x_center + 0.3, y_center + 0.4, 0, -0.3, head_width=0.1, head_length=0.1, fc='green', ec='green')
59
+ ax.arrow(coil_x_center - 0.3, y_center - 0.4, 0, 0.3, head_width=0.1, head_length=0.1, fc='green', ec='green')
60
+ ax.text(coil_x_center, y_center + 0.6, "Induced Current (Clockwise)", color='green', ha='center', va='center')
61
+ elif current_direction == -1: # Counter-clockwise
62
+ ax.arrow(coil_x_center + 0.3, y_center - 0.4, 0, 0.3, head_width=0.1, head_length=0.1, fc='green', ec='green')
63
+ ax.arrow(coil_x_center - 0.3, y_center + 0.4, 0, -0.3, head_width=0.1, head_length=0.1, fc='green', ec='green')
64
+ ax.text(coil_x_center, y_center + 0.6, "Induced Current (Counter-Clockwise)", color='green', ha='center', va='center')
65
+ else:
66
+ ax.text(coil_x_center, y_center + 0.6, "No Induced Current", color='gray', ha='center', va='center')
67
+
68
+
69
+ def draw_force_arrow(ax, x_magnet, magnet_velocity):
70
+ """Draws force arrows indicating repulsion or attraction based on Lenz's Law."""
71
+ force_direction = 0 # 0: No force, 1: Repulsion (away from coil), -1: Attraction (towards coil)
72
+
73
+ if magnet_velocity > 0.1: # Moving towards coil - Repulsion
74
+ force_direction = 1
75
+ elif magnet_velocity < -0.1: # Moving away from coil - Attraction
76
+ force_direction = -1
77
+
78
+ if force_direction == 1: # Repulsion - Force on Magnet to the Left
79
+ ax.arrow(x_magnet, 1.0, -0.5, 0, head_width=0.1, head_length=0.2, fc='purple', ec='purple')
80
+ ax.text(x_magnet - 0.25, 1.2, "Repulsive Force", color='purple', ha='center', va='center')
81
+ elif force_direction == -1: # Attraction - Force on Magnet to the Right
82
+ ax.arrow(x_magnet, 1.0, 0.5, 0, head_width=0.1, head_length=0.2, fc='purple', ec='purple')
83
+ ax.text(x_magnet + 0.25, 1.2, "Attractive Force", color='purple', ha='center', va='center')
84
+ else:
85
+ ax.text(x_magnet, 1.2, "No Force", color='gray', ha='center', va='center')
86
+
87
+
88
+ # --- Main Plot Update Function ---
89
+ def update_plot(magnet_position, magnet_velocity, show_field_lines, show_induced_current, show_forces):
90
+ """Updates the plot based on slider values and checkboxes."""
91
+ fig, ax = plt.subplots()
92
+ ax.set_xlim([-6, 6])
93
+ ax.set_ylim([-2, 2])
94
+ ax.set_aspect('equal') # Ensure circle is drawn as circle
95
+ ax.axis('off') # Hide axes
96
+
97
+ draw_coil(ax)
98
+ draw_magnet(ax, magnet_position)
99
+
100
+ if show_field_lines:
101
+ draw_magnetic_field_magnet(ax, magnet_position)
102
+
103
+ if show_induced_current:
104
+ draw_induced_current(ax, 0, magnet_velocity) # Coil is at x=0
105
+
106
+ if show_forces:
107
+ draw_force_arrow(ax, magnet_position, magnet_velocity)
108
+
109
+ plot_placeholder.pyplot(fig) # Update the plot in Streamlit
110
+
111
+ # --- Run the simulation and update plot on slider change ---
112
+ update_plot(magnet_position, magnet_velocity, show_field_lines, show_induced_current, show_forces)
pages/light-wave.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import time
5
+
6
+ # Title and description
7
+ st.title("Light Wave Simulation")
8
+ st.write("""
9
+ This simulation visualizes light as a transverse wave.
10
+ Adjust the wavelength and frequency to see how the wave changes.
11
+ Check the 'Animate' box to see the wave propagate over time.
12
+ Note: Units are arbitrary, and the wave speed is slowed down for visualization purposes.
13
+ """)
14
+
15
+ # Interactive sliders for wave parameters
16
+ wavelength = st.slider("Wavelength", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
17
+ frequency = st.slider("Frequency", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
18
+
19
+ # Checkbox to toggle animation
20
+ animate = st.checkbox("Animate")
21
+
22
+ # Define wave parameters
23
+ A = 1 # Amplitude (fixed at 1 for simplicity)
24
+ k = 2 * np.pi / wavelength # Wave number (k = 2π/λ)
25
+ omega = 2 * np.pi * frequency # Angular frequency (ω = 2πf)
26
+
27
+ # Spatial coordinates
28
+ x = np.linspace(0, 10, 1000) # Position array from 0 to 10 with 1000 points
29
+
30
+ # Animation or static plot based on user input
31
+ if animate:
32
+ # Create a placeholder for dynamic updates
33
+ placeholder = st.empty()
34
+
35
+ # Run animation for 100 frames
36
+ num_frames = 100
37
+ for frame in range(num_frames):
38
+ # Calculate time for this frame
39
+ t = frame * 0.1
40
+
41
+ # Compute wave amplitude at this time
42
+ y = A * np.sin(k * x - omega * t)
43
+
44
+ # Create and configure the plot
45
+ fig, ax = plt.subplots()
46
+ ax.plot(x, y)
47
+ ax.set_ylim(-1.5, 1.5) # Fixed y-axis limits to show wave clearly
48
+ ax.set_xlabel("Position")
49
+ ax.set_ylabel("Amplitude")
50
+ ax.set_title(f"Light Wave at t = {t:.1f}")
51
+
52
+ # Update the placeholder with the new plot
53
+ placeholder.pyplot(fig)
54
+
55
+ # Close the figure to prevent memory buildup
56
+ plt.close(fig)
57
+
58
+ # Control animation speed
59
+ time.sleep(0.1)
60
+ else:
61
+ # Display a static plot at t=0 when animation is off
62
+ t = 0
63
+ y = A * np.sin(k * x - omega * t)
64
+ fig, ax = plt.subplots()
65
+ ax.plot(x, y)
66
+ ax.set_ylim(-1.5, 1.5)
67
+ ax.set_xlabel("Position")
68
+ ax.set_ylabel("Amplitude")
69
+ ax.set_title("Light Wave at t = 0")
70
+ st.pyplot(fig)
pages/magnetic-field.py ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from matplotlib.patches import Circle
5
+
6
+ def magnetic_field_of_wire(x, y, z, current, wire_pos):
7
+ """Calculate magnetic field due to infinite straight wire at position wire_pos"""
8
+ r_vec = np.array([x - wire_pos[0], y - wire_pos[1], 0])
9
+ r = np.sqrt(r_vec[0]**2 + r_vec[1]**2)
10
+
11
+ # Prevent division by zero
12
+ if r < 1e-10:
13
+ return np.array([0, 0, 0])
14
+
15
+ # Direction: cross product of z unit vector with r_vec
16
+ direction = np.array([-r_vec[1], r_vec[0], 0]) / r
17
+
18
+ # Magnitude: μ0 * I / (2π * r)
19
+ mu0 = 4 * np.pi * 1e-7 # magnetic permeability
20
+ magnitude = mu0 * current / (2 * np.pi * r)
21
+
22
+ return magnitude * direction
23
+
24
+ def magnetic_field_of_loop(x, y, z, current, loop_center, radius):
25
+ """Calculate magnetic field due to a circular current loop in the xy-plane"""
26
+ # Distance from the point to loop center
27
+ r_vec = np.array([x - loop_center[0], y - loop_center[1], z - loop_center[2]])
28
+ r = np.sqrt(r_vec[0]**2 + r_vec[1]**2 + r_vec[2]**2)
29
+
30
+ # Prevent division by zero
31
+ if r < 1e-10:
32
+ return np.array([0, 0, 0])
33
+
34
+ # On-axis field calculation (simplified)
35
+ mu0 = 4 * np.pi * 1e-7 # magnetic permeability
36
+
37
+ if abs(r_vec[0]) < 1e-10 and abs(r_vec[1]) < 1e-10:
38
+ # On the z-axis
39
+ magnitude = mu0 * current * radius**2 / (2 * (radius**2 + r_vec[2]**2)**(3/2))
40
+ return np.array([0, 0, magnitude if r_vec[2] >= 0 else -magnitude])
41
+
42
+ # For off-axis points, return a more approximate field (full calculation is complex)
43
+ # This is a simplification that gives reasonable visualization
44
+ z_component = mu0 * current * radius**2 / (2 * (radius**2 + r**2)**(3/2))
45
+ r_component = mu0 * current * radius**2 * r_vec[2] / (4 * r * (radius**2 + r**2)**(3/2))
46
+
47
+ return np.array([
48
+ r_component * r_vec[0]/r,
49
+ r_component * r_vec[1]/r,
50
+ z_component
51
+ ])
52
+
53
+ def magnetic_field_of_bar_magnet(x, y, z, magnet_center, magnet_length, magnet_strength):
54
+ """Calculate magnetic field due to a simple bar magnet (dipole approximation)"""
55
+ r_vec = np.array([x - magnet_center[0], y - magnet_center[1], z - magnet_center[2]])
56
+ r = np.sqrt(r_vec[0]**2 + r_vec[1]**2 + r_vec[2]**2)
57
+
58
+ # Prevent division by zero
59
+ if r < 1e-10:
60
+ return np.array([0, 0, 0])
61
+
62
+ # Dipole moment in the z-direction
63
+ m_vec = np.array([0, 0, magnet_strength * magnet_length])
64
+
65
+ # Dipole field formula
66
+ mu0 = 4 * np.pi * 1e-7 # magnetic permeability
67
+ constant = mu0 / (4 * np.pi * r**5)
68
+ dot_product = np.dot(m_vec, r_vec)
69
+
70
+ field = constant * (3 * r_vec * dot_product - r**2 * m_vec)
71
+
72
+ return field
73
+
74
+ def plot_magnetic_field(field_type, parameters):
75
+ """Generate the magnetic field plot"""
76
+ fig, ax = plt.subplots(figsize=(10, 8))
77
+
78
+ # Create grid of points
79
+ n = 20
80
+ x = np.linspace(-5, 5, n)
81
+ y = np.linspace(-5, 5, n)
82
+ X, Y = np.meshgrid(x, y)
83
+ Z = np.zeros_like(X) # Z=0 plane
84
+
85
+ # Calculate field at each point
86
+ U = np.zeros_like(X)
87
+ V = np.zeros_like(Y)
88
+ W = np.zeros_like(Z)
89
+
90
+ for i in range(n):
91
+ for j in range(n):
92
+ if field_type == "Wire":
93
+ field = magnetic_field_of_wire(
94
+ X[i, j], Y[i, j], Z[i, j],
95
+ parameters["current"],
96
+ parameters["position"]
97
+ )
98
+ elif field_type == "Loop":
99
+ field = magnetic_field_of_loop(
100
+ X[i, j], Y[i, j], Z[i, j],
101
+ parameters["current"],
102
+ parameters["center"],
103
+ parameters["radius"]
104
+ )
105
+ elif field_type == "Bar Magnet":
106
+ field = magnetic_field_of_bar_magnet(
107
+ X[i, j], Y[i, j], Z[i, j],
108
+ parameters["center"],
109
+ parameters["length"],
110
+ parameters["strength"]
111
+ )
112
+
113
+ # Normalize the field for better visualization
114
+ field_magnitude = np.sqrt(field[0]**2 + field[1]**2 + field[2]**2)
115
+ if field_magnitude > 0:
116
+ normalized_field = field / field_magnitude
117
+ else:
118
+ normalized_field = field
119
+
120
+ U[i, j] = normalized_field[0]
121
+ V[i, j] = normalized_field[1]
122
+ W[i, j] = normalized_field[2]
123
+
124
+ # Plot the magnetic field
125
+ ax.streamplot(X, Y, U, V, density=2, color='b', linewidth=1, arrowsize=1)
126
+
127
+ # Draw the source of the field
128
+ if field_type == "Wire":
129
+ ax.plot(parameters["position"][0], parameters["position"][1], 'ro', markersize=10)
130
+ ax.annotate('Current into page' if parameters["current"] > 0 else 'Current out of page',
131
+ xy=(parameters["position"][0], parameters["position"][1]),
132
+ xytext=(parameters["position"][0] + 0.5, parameters["position"][1] + 0.5))
133
+ elif field_type == "Loop":
134
+ circle = Circle(parameters["center"][:2], parameters["radius"], fill=False, color='r')
135
+ ax.add_patch(circle)
136
+ ax.annotate('Current loop',
137
+ xy=(parameters["center"][0], parameters["center"][1]),
138
+ xytext=(parameters["center"][0] + 0.5, parameters["center"][1] + 0.5))
139
+ elif field_type == "Bar Magnet":
140
+ half_length = parameters["length"] / 2
141
+ ax.plot([parameters["center"][0] - half_length, parameters["center"][0] + half_length],
142
+ [parameters["center"][1], parameters["center"][1]], 'r-', linewidth=4)
143
+ ax.text(parameters["center"][0] - half_length - 0.3, parameters["center"][1], 'S')
144
+ ax.text(parameters["center"][0] + half_length + 0.1, parameters["center"][1], 'N')
145
+
146
+ ax.set_xlim(-5, 5)
147
+ ax.set_ylim(-5, 5)
148
+ ax.set_xlabel('X')
149
+ ax.set_ylabel('Y')
150
+ ax.set_title(f'Magnetic Field of {field_type}')
151
+ ax.grid(True)
152
+ ax.set_aspect('equal')
153
+
154
+ return fig
155
+
156
+ def main():
157
+ st.title("Magnetic Field Visualization")
158
+ st.write("""
159
+ This app visualizes the magnetic field created by different sources.
160
+ Choose a magnetic field source below and adjust the parameters to see how the field changes.
161
+ """)
162
+
163
+ # Sidebar for parameters
164
+ st.sidebar.header("Field Source Parameters")
165
+
166
+ # Select field type
167
+ field_type = st.sidebar.selectbox(
168
+ "Select a magnetic field source:",
169
+ ["Wire", "Loop", "Bar Magnet"]
170
+ )
171
+
172
+ # Parameters based on field type
173
+ if field_type == "Wire":
174
+ current = st.sidebar.slider("Current (A)", -10.0, 10.0, 5.0)
175
+ wire_x = st.sidebar.slider("Wire X Position", -3.0, 3.0, 0.0)
176
+ wire_y = st.sidebar.slider("Wire Y Position", -3.0, 3.0, 0.0)
177
+
178
+ parameters = {
179
+ "current": current,
180
+ "position": [wire_x, wire_y]
181
+ }
182
+
183
+ st.write("""
184
+ ### Infinite Straight Wire
185
+
186
+ The magnetic field around a long straight wire forms concentric circles around the wire.
187
+ - Direction: determined by the right-hand rule (thumb points in the current direction)
188
+ - Magnitude: proportional to the current and inversely proportional to the distance from the wire
189
+
190
+ Mathematical formula: B = (μ₀ × I) / (2π × r)
191
+ """)
192
+
193
+ elif field_type == "Loop":
194
+ current = st.sidebar.slider("Current (A)", -10.0, 10.0, 5.0)
195
+ loop_x = st.sidebar.slider("Loop Center X", -3.0, 3.0, 0.0)
196
+ loop_y = st.sidebar.slider("Loop Center Y", -3.0, 3.0, 0.0)
197
+ loop_z = st.sidebar.slider("Loop Center Z", -3.0, 3.0, 0.0)
198
+ radius = st.sidebar.slider("Loop Radius", 0.5, 3.0, 1.0)
199
+
200
+ parameters = {
201
+ "current": current,
202
+ "center": [loop_x, loop_y, loop_z],
203
+ "radius": radius
204
+ }
205
+
206
+ st.write("""
207
+ ### Current Loop
208
+
209
+ A current loop creates a magnetic field similar to a bar magnet.
210
+ - Near the center of the loop, the field lines are nearly parallel
211
+ - Far from the loop, the field approximates that of a dipole
212
+
213
+ This is the basic principle behind electromagnets and solenoids.
214
+ """)
215
+
216
+ elif field_type == "Bar Magnet":
217
+ magnet_x = st.sidebar.slider("Magnet Center X", -3.0, 3.0, 0.0)
218
+ magnet_y = st.sidebar.slider("Magnet Center Y", -3.0, 3.0, 0.0)
219
+ magnet_z = st.sidebar.slider("Magnet Center Z", -3.0, 3.0, 0.0)
220
+ length = st.sidebar.slider("Magnet Length", 0.5, 3.0, 2.0)
221
+ strength = st.sidebar.slider("Magnet Strength", 1.0, 10.0, 5.0)
222
+
223
+ parameters = {
224
+ "center": [magnet_x, magnet_y, magnet_z],
225
+ "length": length,
226
+ "strength": strength
227
+ }
228
+
229
+ st.write("""
230
+ ### Bar Magnet
231
+
232
+ A bar magnet produces a dipole magnetic field.
233
+ - Field lines emerge from the north pole and enter the south pole
234
+ - The field strength decreases with the cube of the distance from the magnet
235
+
236
+ This simulation uses a simplified dipole approximation.
237
+ """)
238
+
239
+ # Generate and display plot
240
+ fig = plot_magnetic_field(field_type, parameters)
241
+ st.pyplot(fig)
242
+
243
+ # Additional explanations
244
+ st.write("""
245
+ ### About Magnetic Fields
246
+
247
+ Magnetic fields are vector fields that describe the magnetic influence on moving electric charges, electric currents, and magnetic materials.
248
+
249
+ Key concepts:
250
+ - Magnetic field lines are continuous loops
251
+ - The density of field lines indicates the strength of the field
252
+ - The direction of the field is determined by the right-hand rule
253
+
254
+ The visualization shows a 2D slice (Z=0 plane) of the magnetic field, with arrows indicating the field direction.
255
+ """)
256
+
257
+ if __name__ == "__main__":
258
+ main()
pages/mass-energy-equivalence.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+ # Define the speed of light constant (in m/s)
6
+ c = 3e8 # 300,000,000 m/s
7
+
8
+ # Set up the Streamlit app title and description.
9
+ st.title("Mass–Energy Equivalence Visualization")
10
+ st.write("""
11
+ Einstein's famous equation \(E = mc^2\) tells us that mass and energy are interchangeable.
12
+ Use the slider below to adjust the mass and see the equivalent energy.
13
+ """)
14
+
15
+ # Create a slider for selecting the mass (in kg)
16
+ mass = st.slider("Select mass (kg)", min_value=0.001, max_value=100.0, value=1.0, step=0.001)
17
+
18
+ # Calculate energy using E = m * c^2
19
+ energy = mass * c**2
20
+
21
+ # Display the calculated energy
22
+ st.write(f"**For a mass of {mass:.3f} kg, the equivalent energy is {energy:.3e} Joules.**")
23
+
24
+ # Additional explanation for perspective
25
+ st.write("""
26
+ For context, 1 kg of mass is equivalent to about 9 × 10^16 Joules of energy –
27
+ an amount that can power entire cities for days.
28
+ """)
29
+
30
+ # Plotting: Show a graph of Energy vs Mass for a range from 0 up to the selected mass.
31
+ st.subheader("Energy as a Function of Mass")
32
+ mass_values = np.linspace(0, mass, 100)
33
+ energy_values = mass_values * c**2
34
+
35
+ # Create a DataFrame for plotting
36
+ df = pd.DataFrame({
37
+ "Mass (kg)": mass_values,
38
+ "Energy (Joules)": energy_values
39
+ })
40
+
41
+ # Display the line chart
42
+ st.line_chart(df.set_index("Mass (kg)"))
pages/mass.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import plotly.graph_objs as go
5
+ import plotly.express as px
6
+ import pandas as pd
7
+
8
+ def introduction_to_mass():
9
+ """
10
+ Provides an introductory explanation of mass
11
+ """
12
+ st.header("Understanding Mass 🧊")
13
+ st.markdown("""
14
+ Mass is a fundamental property of matter that represents the amount of material in an object.
15
+ It is a measure of an object's resistance to acceleration when a force is applied.
16
+
17
+ Key Characteristics of Mass:
18
+ - Measured in kilograms (kg)
19
+ - Remains constant regardless of location (unlike weight)
20
+ - Determines an object's inertia
21
+ """)
22
+
23
+ def mass_vs_weight_simulation():
24
+ """
25
+ Interactive simulation showing difference between mass and weight
26
+ """
27
+ st.header("Mass vs Weight Comparison 🌍")
28
+
29
+ # Gravity values for different celestial bodies
30
+ gravity_dict = {
31
+ "Earth": 9.8,
32
+ "Moon": 1.62,
33
+ "Mars": 3.72,
34
+ "Jupiter": 24.79
35
+ }
36
+
37
+ # User input for mass
38
+ mass = st.slider("Select Object Mass (kg)", min_value=1, max_value=100, value=50)
39
+
40
+ # Create DataFrame for visualization
41
+ data = []
42
+ for planet, gravity in gravity_dict.items():
43
+ weight = mass * gravity
44
+ data.append({"Celestial Body": planet, "Weight (N)": weight})
45
+
46
+ df = pd.DataFrame(data)
47
+
48
+ # Plotly bar chart
49
+ fig = px.bar(df, x="Celestial Body", y="Weight (N)",
50
+ title=f"Weight of a {mass} kg Object on Different Planets",
51
+ labels={"Weight (N)": "Weight (Newtons)"})
52
+ st.plotly_chart(fig)
53
+
54
+ # Explanation
55
+ st.markdown(f"""
56
+ 🔍 For a {mass} kg object:
57
+ - Mass remains constant at {mass} kg everywhere
58
+ - Weight changes based on local gravitational acceleration
59
+ """)
60
+
61
+ def density_visualization():
62
+ """
63
+ Visualization of mass density
64
+ """
65
+ st.header("Mass Density Exploration 📊")
66
+
67
+ # Predefined materials and their densities
68
+ materials = {
69
+ "Air": 0.001225,
70
+ "Water": 1000,
71
+ "Wood": 700,
72
+ "Aluminum": 2700,
73
+ "Iron": 7874,
74
+ "Lead": 11340
75
+ }
76
+
77
+ # User selects volume
78
+ volume = st.slider("Select Volume (m³)", min_value=0.1, max_value=10.0, value=1.0, step=0.1)
79
+
80
+ # Calculate masses
81
+ masses = {material: density * volume for material, density in materials.items()}
82
+
83
+ # Create DataFrame
84
+ df = pd.DataFrame.from_dict(masses, orient='index', columns=['Mass (kg)'])
85
+ df.index.name = 'Material'
86
+ df = df.reset_index()
87
+
88
+ # Plotly bar chart
89
+ fig = px.bar(df, x='Material', y='Mass (kg)',
90
+ title=f"Mass of Different Materials (Volume: {volume} m³)",
91
+ labels={'Mass (kg)': 'Mass (kg)'})
92
+ st.plotly_chart(fig)
93
+
94
+ st.markdown("""
95
+ 💡 Density determines how much mass is contained in a given volume:
96
+ - Low density: Less mass per volume (e.g., Air)
97
+ - High density: More mass per volume (e.g., Lead)
98
+ """)
99
+
100
+ def main():
101
+ """
102
+ Main Streamlit app
103
+ """
104
+ st.title("Mass Visualization Simulator 🔬")
105
+
106
+ # Sidebar for navigation
107
+ page = st.sidebar.selectbox("Choose a Visualization", [
108
+ "Introduction to Mass",
109
+ "Mass vs Weight",
110
+ "Density Exploration"
111
+ ])
112
+
113
+ # Page routing
114
+ if page == "Introduction to Mass":
115
+ introduction_to_mass()
116
+ elif page == "Mass vs Weight":
117
+ mass_vs_weight_simulation()
118
+ elif page == "Density Exploration":
119
+ density_visualization()
120
+
121
+ if __name__ == "__main__":
122
+ main()
pages/maxwells-equations.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import time
5
+
6
+ st.title("Visualizing Maxwell's Equations")
7
+ st.write("An illustrative visualization of fundamental concepts related to Maxwell's Equations.")
8
+
9
+ equation_choice = st.selectbox("Choose a Maxwell's Equation Concept to Visualize:",
10
+ ["Electric Field of a Point Charge (Gauss's Law for Electricity)",
11
+ "Magnetic Field of a Current-Carrying Wire (Ampere's Law)",
12
+ "Electromagnetic Plane Wave (Faraday's & Ampere-Maxwell - Simplified)"])
13
+
14
+ # --- Visualization Functions ---
15
+
16
+ def electric_field_point_charge(charge_pos_x, charge_pos_y, charge_magnitude, grid_size):
17
+ """Visualizes the electric field of a point charge."""
18
+ x = np.linspace(-grid_size, grid_size, 30)
19
+ y = np.linspace(-grid_size, grid_size, 30)
20
+ X, Y = np.meshgrid(x, y)
21
+
22
+ Ex = np.zeros_like(X)
23
+ Ey = np.zeros_like(Y)
24
+
25
+ for i in range(X.shape[0]):
26
+ for j in range(X.shape[1]):
27
+ r_vec = np.array([X[i, j] - charge_pos_x, Y[i, j] - charge_pos_y])
28
+ r_mag = np.linalg.norm(r_vec)
29
+ if r_mag > 0.1: # Avoid singularity at charge location
30
+ E_direction = r_vec / r_mag
31
+ E_magnitude = charge_magnitude / (r_mag**2) #Simplified formula, ignoring constants for visualization
32
+ Ex[i, j] = E_magnitude * E_direction[0]
33
+ Ey[i, j] = E_magnitude * E_direction[1]
34
+
35
+ return X, Y, Ex, Ey
36
+
37
+ def magnetic_field_wire(wire_pos_x, wire_pos_y, current_magnitude, grid_size):
38
+ """Visualizes the magnetic field around a long straight wire."""
39
+ x = np.linspace(-grid_size, grid_size, 30)
40
+ y = np.linspace(-grid_size, grid_size, 30)
41
+ X, Y = np.meshgrid(x, y)
42
+
43
+ Bx = np.zeros_like(X)
44
+ By = np.zeros_like(Y)
45
+ Bz = np.zeros_like(X) # Magnetic field in the Z direction (out of plane)
46
+
47
+ for i in range(X.shape[0]):
48
+ for j in range(X.shape[1]):
49
+ r_vec = np.array([X[i, j] - wire_pos_x, Y[i, j] - wire_pos_y])
50
+ r_mag = np.linalg.norm(r_vec)
51
+ if r_mag > 0.1: # Avoid singularity at wire location
52
+ B_direction = np.array([-r_vec[1], r_vec[0]]) / r_mag # Tangential direction (right-hand rule)
53
+ B_magnitude = current_magnitude / r_mag # Simplified, ignoring constants
54
+ Bx[i, j] = B_magnitude * B_direction[0]
55
+ By[i, j] = B_magnitude * B_direction[1]
56
+ Bz[i,j] = 0 # for 2D visualization, assume B is in xy plane
57
+
58
+ return X, Y, Bx, By, Bz
59
+
60
+ def electromagnetic_plane_wave(time_step, grid_size, wave_direction):
61
+ """Illustrative plane EM wave propagation (simplified)."""
62
+ x = np.linspace(-grid_size, grid_size, 30)
63
+ y = np.linspace(-grid_size, grid_size, 30)
64
+ X, Y = np.meshgrid(x, y)
65
+ t = time_step
66
+
67
+ # Simplified Plane Wave along x-axis for illustration
68
+ if wave_direction == "x":
69
+ Ex = np.sin(X - t)
70
+ Ey = np.zeros_like(X)
71
+ Ez = np.zeros_like(X)
72
+ Bx = np.zeros_like(X)
73
+ By = np.zeros_like(X)
74
+ Bz = np.sin(X - t) # B field perpendicular to E and propagation direction (along z)
75
+ elif wave_direction == "y":
76
+ Ex = np.zeros_like(X)
77
+ Ey = np.sin(Y - t)
78
+ Ez = np.zeros_like(X)
79
+ Bx = np.sin(Y - t) # B perpendicular to E and prop. direction (along x)
80
+ By = np.zeros_like(X)
81
+ Bz = np.zeros_like(X)
82
+
83
+
84
+ return X, Y, Ex, Ey, Ez, Bx, By, Bz
85
+
86
+
87
+ # --- Streamlit UI based on equation choice ---
88
+
89
+ st.subheader("Visualization Parameters")
90
+
91
+ if equation_choice == "Electric Field of a Point Charge (Gauss's Law for Electricity)":
92
+ charge_pos_x = st.slider("Charge Position X", -5.0, 5.0, 0.0)
93
+ charge_pos_y = st.slider("Charge Position Y", -5.0, 5.0, 0.0)
94
+ charge_magnitude = st.slider("Charge Magnitude", -5.0, 5.0, 1.0)
95
+ grid_size = st.slider("Grid Size", 5, 15, 10)
96
+
97
+ X, Y, Ex, Ey = electric_field_point_charge(charge_pos_x, charge_pos_y, charge_magnitude, grid_size)
98
+
99
+ fig, ax = plt.subplots()
100
+ q = ax.quiver(X, Y, Ex, Ey, color='r', label='Electric Field (E)')
101
+ ax.plot(charge_pos_x, charge_pos_y, 'ro', markersize=10, label='Point Charge')
102
+ ax.set_title("Electric Field of a Point Charge")
103
+ ax.set_xlabel("x")
104
+ ax.set_ylabel("y")
105
+ ax.axis('equal')
106
+ ax.legend()
107
+ st.pyplot(fig)
108
+
109
+ elif equation_choice == "Magnetic Field of a Current-Carrying Wire (Ampere's Law)":
110
+ wire_pos_x = st.slider("Wire Position X", -5.0, 5.0, 0.0)
111
+ wire_pos_y = st.slider("Wire Position Y", -5.0, 5.0, 0.0)
112
+ current_magnitude = st.slider("Current Magnitude (out of plane)", -5.0, 5.0, 1.0)
113
+ grid_size = st.slider("Grid Size", 5, 15, 10)
114
+
115
+ X, Y, Bx, By, Bz = magnetic_field_wire(wire_pos_x, wire_pos_y, current_magnitude, grid_size)
116
+
117
+ fig, ax = plt.subplots()
118
+ q = ax.quiver(X, Y, Bx, By, color='b', label='Magnetic Field (B)')
119
+ ax.plot(wire_pos_x, wire_pos_y, 'ko', markersize=10, label='Wire (Current out of plane)')
120
+ ax.set_title("Magnetic Field of a Current-Carrying Wire")
121
+ ax.set_xlabel("x")
122
+ ax.set_ylabel("y")
123
+ ax.axis('equal')
124
+ ax.legend()
125
+ st.pyplot(fig)
126
+
127
+ elif equation_choice == "Electromagnetic Plane Wave (Faraday's & Ampere-Maxwell - Simplified)":
128
+ wave_direction = st.selectbox("Wave Propagation Direction", ["x", "y"])
129
+ grid_size = st.slider("Grid Size", 5, 15, 10)
130
+
131
+ placeholder = st.empty() # For animation
132
+
133
+ for t in range(0, 100): # Animation loop
134
+ X, Y, Ex, Ey, Ez, Bx, By, Bz = electromagnetic_plane_wave(t * 0.1, grid_size, wave_direction) # Time step
135
+ fig, ax = plt.subplots()
136
+ q_e = ax.quiver(X[::2, ::2], Y[::2, ::2], Ex[::2, ::2], Ey[::2, ::2], color='r', label='Electric Field (E)', scale=50) # Subsample for clarity
137
+ q_b = ax.quiver(X[::2, ::2], Y[::2, ::2], Bx[::2, ::2], By[::2, ::2], color='b', label='Magnetic Field (B)', scale=50) # Subsample
138
+ ax.set_title("Electromagnetic Plane Wave Propagation")
139
+ ax.set_xlabel("x")
140
+ ax.set_ylabel("y")
141
+ ax.axis('equal')
142
+ ax.legend()
143
+ placeholder.pyplot(fig) # Update plot in placeholder
144
+ time.sleep(0.1) # Control animation speed
145
+ plt.close(fig) # Clear figure for next frame
pages/mechanical-advantage.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+
4
+ st.title("Mechanical Advantage Simulation: Lever")
5
+
6
+ st.write(
7
+ """
8
+ This simulation visualizes the concept of mechanical advantage for a simple lever.
9
+ Adjust the parameters below to see how the input force is amplified.
10
+ """
11
+ )
12
+
13
+ # Slider inputs for the simulation parameters
14
+ effort_arm = st.slider(
15
+ "Effort Arm Length (distance from fulcrum to applied force, in meters)",
16
+ min_value=0.1, max_value=10.0, value=5.0, step=0.1
17
+ )
18
+ load_arm = st.slider(
19
+ "Load Arm Length (distance from fulcrum to load, in meters)",
20
+ min_value=0.1, max_value=10.0, value=2.0, step=0.1
21
+ )
22
+ input_force = st.slider(
23
+ "Input Force (in Newtons)",
24
+ min_value=0.0, max_value=100.0, value=10.0, step=0.5
25
+ )
26
+
27
+ # Calculate mechanical advantage and output force
28
+ mechanical_advantage = effort_arm / load_arm
29
+ output_force = input_force * mechanical_advantage
30
+
31
+ st.markdown(f"### Mechanical Advantage: {mechanical_advantage:.2f}")
32
+ st.markdown(f"### Output Force: {output_force:.2f} N")
33
+
34
+ # Plotting a simple lever diagram
35
+ fig, ax = plt.subplots(figsize=(8, 3))
36
+ ax.set_xlim(-1, effort_arm + load_arm + 1)
37
+ ax.set_ylim(-3, 3)
38
+ ax.axhline(0, color="black", linewidth=2)
39
+
40
+ # Define positions for the fulcrum, effort, and load
41
+ fulcrum_pos = effort_arm
42
+ load_pos = fulcrum_pos + load_arm
43
+
44
+ # Plot the fulcrum
45
+ ax.plot(fulcrum_pos, 0, marker="v", markersize=15, color="red")
46
+ ax.annotate("Fulcrum", xy=(fulcrum_pos, 0), xytext=(fulcrum_pos, -0.5),
47
+ ha="center", color="red")
48
+
49
+ # Draw the lever as a horizontal line
50
+ ax.plot([0, load_pos], [0, 0], color="gray", linewidth=3)
51
+
52
+ # Plot effort force arrow
53
+ ax.arrow(0, 0, fulcrum_pos * 0.8, 1.0, head_width=0.3, head_length=0.3, fc="green", ec="green")
54
+ ax.text(0, 1.2, "Effort", color="green", ha="center")
55
+
56
+ # Plot load force arrow (direction reversed to indicate opposition)
57
+ ax.arrow(load_pos, 0, -load_arm * 0.8, -1.0, head_width=0.3, head_length=0.3, fc="blue", ec="blue")
58
+ ax.text(load_pos, -1.2, "Load", color="blue", ha="center")
59
+
60
+ # Remove axes for clarity
61
+ ax.axis("off")
62
+
63
+ st.pyplot(fig)
pages/moment-of-inertia.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+
5
+ # Title and Introduction
6
+ st.title("Moment of Inertia Simulation")
7
+ st.markdown("### What is Moment of Inertia?")
8
+ st.write("Moment of Inertia (I) measures an object's resistance to rotational motion. It depends on the mass and its distribution relative to the axis of rotation. In this simulation, adjust the masses, their positions, and the axis to see how I changes.")
9
+
10
+ # Sidebar for Number of Masses
11
+ st.sidebar.title("Settings")
12
+ num_masses = st.sidebar.slider("Number of masses", 1, 5, 1)
13
+
14
+ # Input Sliders for Masses and Positions
15
+ masses = []
16
+ positions = []
17
+ for i in range(num_masses):
18
+ st.subheader(f"Mass {i+1}")
19
+ col1, col2 = st.columns(2)
20
+ with col1:
21
+ m = st.slider(f"Mass (kg)", 0.1, 10.0, 1.0, key=f"m{i}")
22
+ with col2:
23
+ r = st.slider(f"Position (m)", 0.0, 1.0, 0.5, key=f"r{i}")
24
+ masses.append(m)
25
+ positions.append(r)
26
+
27
+ # Axis of Rotation Slider
28
+ axis_pos = st.slider("Axis of rotation position (m)", 0.0, 1.0, 0.0)
29
+
30
+ # Calculations
31
+ total_mass = sum(masses)
32
+ x_cm = sum(m * r for m, r in zip(masses, positions)) / total_mass if total_mass > 0 else 0
33
+ I = sum(m * (r - axis_pos)**2 for m, r in zip(masses, positions))
34
+
35
+ # Display Formula and Results
36
+ st.write("The Moment of Inertia is calculated as:")
37
+ st.latex(r"I = \sum_{i} m_i (r_i - a)^2")
38
+ st.write(f"where *a* is the axis position: {axis_pos} m")
39
+ st.write("**Calculation details:**")
40
+ for i, (m, r) in enumerate(zip(masses, positions)):
41
+ contribution = m * (r - axis_pos)**2
42
+ st.write(f"Mass {i+1}: {m} kg at {r} m, distance to axis: {abs(r - axis_pos):.2f} m, contribution: {contribution:.2f} kg m²")
43
+ st.write(f"**Total Moment of Inertia: {I:.2f} kg m²**")
44
+
45
+ # Plot Rod with Masses
46
+ fig, ax = plt.subplots()
47
+ ax.plot([0, 1], [0, 0], 'k-', linewidth=2, label="Rod")
48
+ for r in positions:
49
+ ax.plot(r, 0, 'ro', markersize=10) # Masses as red circles
50
+ ax.axvline(x=axis_pos, color='b', linestyle='--', label="Axis of Rotation")
51
+ ax.plot(x_cm, 0, 'g*', markersize=15, label="Center of Mass")
52
+ ax.set_xlim(-0.1, 1.1)
53
+ ax.set_ylim(-0.1, 0.1)
54
+ ax.set_xlabel("Position (m)")
55
+ ax.set_title("Rod with Masses")
56
+ ax.legend()
57
+ st.pyplot(fig)
58
+
59
+ # Plot I vs. Axis Position
60
+ st.subheader("Moment of Inertia vs. Axis Position")
61
+ axis_positions = np.linspace(0, 1, 100)
62
+ I_values = [sum(m * (r - a)**2 for m, r in zip(masses, positions)) for a in axis_positions]
63
+ fig2, ax2 = plt.subplots()
64
+ ax2.plot(axis_positions, I_values, label="I vs. Axis Position")
65
+ ax2.axvline(x=x_cm, color='g', linestyle='--', label="Center of Mass")
66
+ ax2.set_xlabel("Axis Position (m)")
67
+ ax2.set_ylabel("Moment of Inertia (kg m²)")
68
+ ax2.legend()
69
+ st.pyplot(fig2)
70
+
71
+ # Torque and Angular Acceleration
72
+ st.subheader("Apply Torque")
73
+ torque = st.number_input("Torque (N m)", 0.0, 100.0, 1.0)
74
+ if I > 0:
75
+ alpha = torque / I
76
+ st.write(f"Angular acceleration: {alpha:.2f} rad/s²")
77
+ else:
78
+ st.write("Moment of Inertia is zero, cannot calculate angular acceleration.")
pages/momentum.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib.animation as animation
5
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
6
+
7
+ class MomentumSimulation:
8
+ def __init__(self, mass1, velocity1, mass2, velocity2):
9
+ """
10
+ Initialize the momentum simulation with two objects
11
+ """
12
+ self.mass1 = mass1
13
+ self.velocity1 = velocity1
14
+ self.mass2 = mass2
15
+ self.velocity2 = velocity2
16
+
17
+ def calculate_momentum(self):
18
+ """
19
+ Calculate initial and final momentum
20
+ """
21
+ initial_momentum = self.mass1 * self.velocity1 + self.mass2 * self.velocity2
22
+
23
+ # Simplified elastic collision calculation
24
+ v1_final = ((self.mass1 - self.mass2) * self.velocity1 +
25
+ 2 * self.mass2 * self.velocity2) / (self.mass1 + self.mass2)
26
+
27
+ v2_final = ((self.mass2 - self.mass1) * self.velocity2 +
28
+ 2 * self.mass1 * self.velocity1) / (self.mass1 + self.mass2)
29
+
30
+ final_momentum = self.mass1 * v1_final + self.mass2 * v2_final
31
+
32
+ return initial_momentum, final_momentum, v1_final, v2_final
33
+
34
+ def visualize_collision(self):
35
+ """
36
+ Create an animation of the collision
37
+ """
38
+ # Create figure and axis
39
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
40
+ fig.suptitle('Momentum Collision Simulation')
41
+
42
+ # Initial momentum plot
43
+ initial_momentum, final_momentum, v1_final, v2_final = self.calculate_momentum()
44
+
45
+ # Before collision plot
46
+ ax1.set_title('Before Collision')
47
+ ax1.set_xlim(-10, 10)
48
+ ax1.set_ylim(0, max(self.mass1, self.mass2) + 1)
49
+ rect1 = plt.Rectangle((-5, 1), 1, self.mass1,
50
+ fc='blue', label=f'Mass 1: {self.mass1} kg')
51
+ rect2 = plt.Rectangle((5 - 1, 1), 1, self.mass2,
52
+ fc='red', label=f'Mass 2: {self.mass2} kg')
53
+
54
+ ax1.add_patch(rect1)
55
+ ax1.add_patch(rect2)
56
+
57
+ # Velocity annotations
58
+ ax1.annotate(f'v1: {self.velocity1} m/s', xy=(-4.5, 0.5),
59
+ xytext=(-4.5, 0.5), color='blue')
60
+ ax1.annotate(f'v2: {self.velocity2} m/s', xy=(5.5, 0.5),
61
+ xytext=(5.5, 0.5), color='red')
62
+
63
+ ax1.legend()
64
+ ax1.set_xlabel('Position (m)')
65
+ ax1.set_ylabel('Mass')
66
+
67
+ # After collision plot
68
+ ax2.set_title('After Collision')
69
+ ax2.set_xlim(-10, 10)
70
+ ax2.set_ylim(0, max(self.mass1, self.mass2) + 1)
71
+ rect1_final = plt.Rectangle((-5, 1), 1, self.mass1,
72
+ fc='blue', label=f'Mass 1: {self.mass1} kg')
73
+ rect2_final = plt.Rectangle((5 - 1, 1), 1, self.mass2,
74
+ fc='red', label=f'Mass 2: {self.mass2} kg')
75
+
76
+ ax2.add_patch(rect1_final)
77
+ ax2.add_patch(rect2_final)
78
+
79
+ # Final velocity annotations
80
+ ax2.annotate(f'v1 final: {v1_final:.2f} m/s', xy=(-4.5, 0.5),
81
+ xytext=(-4.5, 0.5), color='blue')
82
+ ax2.annotate(f'v2 final: {v2_final:.2f} m/s', xy=(5.5, 0.5),
83
+ xytext=(5.5, 0.5), color='red')
84
+
85
+ ax2.legend()
86
+ ax2.set_xlabel('Position (m)')
87
+ ax2.set_ylabel('Mass')
88
+
89
+ return fig
90
+
91
+ def main():
92
+ st.title('Momentum Collision Simulator')
93
+
94
+ # Sidebar for input
95
+ st.sidebar.header('Collision Parameters')
96
+
97
+ # Mass inputs
98
+ mass1 = st.sidebar.number_input('Mass of Object 1 (kg)',
99
+ min_value=0.1,
100
+ max_value=100.0,
101
+ value=10.0)
102
+ mass2 = st.sidebar.number_input('Mass of Object 2 (kg)',
103
+ min_value=0.1,
104
+ max_value=100.0,
105
+ value=5.0)
106
+
107
+ # Velocity inputs
108
+ velocity1 = st.sidebar.number_input('Initial Velocity of Object 1 (m/s)',
109
+ min_value=-50.0,
110
+ max_value=50.0,
111
+ value=2.0)
112
+ velocity2 = st.sidebar.number_input('Initial Velocity of Object 2 (m/s)',
113
+ min_value=-50.0,
114
+ max_value=50.0,
115
+ value=-1.0)
116
+
117
+ # Create simulation
118
+ simulation = MomentumSimulation(mass1, velocity1, mass2, velocity2)
119
+
120
+ # Calculate momentum
121
+ initial_momentum, final_momentum, v1_final, v2_final = simulation.calculate_momentum()
122
+
123
+ # Display momentum calculations
124
+ st.subheader('Momentum Analysis')
125
+ col1, col2 = st.columns(2)
126
+
127
+ with col1:
128
+ st.metric('Initial Momentum', f'{initial_momentum:.2f} kg·m/s')
129
+
130
+ with col2:
131
+ st.metric('Final Momentum', f'{final_momentum:.2f} kg·m/s')
132
+
133
+ # Visualization
134
+ st.subheader('Collision Visualization')
135
+ fig = simulation.visualize_collision()
136
+ st.pyplot(fig)
137
+
138
+ # Additional explanation
139
+ st.markdown("""
140
+ ### Understanding Momentum
141
+
142
+ **Momentum** is the product of an object's mass and velocity (p = mv).
143
+ In this simulation:
144
+ - The total momentum before and after the collision remains constant (Conservation of Momentum)
145
+ - The objects exchange velocities based on their masses
146
+ - Positive velocity indicates movement to the right
147
+ - Negative velocity indicates movement to the left
148
+ """)
149
+
150
+ if __name__ == '__main__':
151
+ main()