Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import streamlit as st | |
| import math | |
| import random | |
| class Vector2D: | |
| """A 2D vector class for physics calculations""" | |
| def __init__(self, x=0, y=0, magnitude=None, angle=None): | |
| if magnitude is not None and angle is not None: | |
| # Create vector from magnitude and angle (in degrees) | |
| self.x = magnitude * math.cos(math.radians(angle)) | |
| self.y = magnitude * math.sin(math.radians(angle)) | |
| else: | |
| self.x = x | |
| self.y = y | |
| def __repr__(self): | |
| return f"Vector2D(x={self.x:.2f}, y={self.y:.2f}, mag={self.magnitude:.2f}, angle={self.angle:.1f}°)" | |
| def __rmul__(self, scalar): | |
| """Allow scalar * vector multiplication""" | |
| return self.__mul__(scalar) | |
| def cross_product_2d(self, other): | |
| """2D cross product (returns scalar z-component)""" | |
| return self.x * other.y - self.y * other.x | |
| def angle_between(self, other): | |
| """Calculate angle between two vectors in degrees""" | |
| dot_prod = self.dot_product(other) | |
| mags = self.magnitude * other.magnitude | |
| if mags == 0: | |
| return 0 | |
| cos_angle = dot_prod / mags | |
| # Clamp to avoid floating point errors | |
| cos_angle = max(-1, min(1, cos_angle)) | |
| return math.degrees(math.acos(cos_angle)) | |
| def magnitude(self): | |
| return math.sqrt(self.x**2 + self.y**2) | |
| def angle(self): | |
| return math.degrees(math.atan2(self.y, self.x)) | |
| def __add__(self, other): | |
| return Vector2D(self.x + other.x, self.y + other.y) | |
| def __sub__(self, other): | |
| return Vector2D(self.x - other.x, self.y - other.y) | |
| def __mul__(self, scalar): | |
| return Vector2D(self.x * scalar, self.y * scalar) | |
| def dot_product(self, other): | |
| return self.x * other.x + self.y * other.y | |
| def unit_vector(self): | |
| mag = self.magnitude | |
| if mag == 0: | |
| return Vector2D(0, 0) | |
| return Vector2D(self.x / mag, self.y / mag) | |
| class UnitConverter: | |
| """Unit conversion utilities for physics calculations""" | |
| def speed_conversions(): | |
| return { | |
| "m/s": 1.0, | |
| "km/h": 3.6, | |
| "mph": 2.237, | |
| "ft/s": 3.281, | |
| "knots": 1.944 | |
| } | |
| def distance_conversions(): | |
| return { | |
| "meters": 1.0, | |
| "kilometers": 0.001, | |
| "miles": 0.000621, | |
| "feet": 3.281, | |
| "yards": 1.094 | |
| } | |
| def convert_speed(value, from_unit, to_unit): | |
| conversions = UnitConverter.speed_conversions() | |
| # Convert to m/s first, then to target unit | |
| ms_value = value / conversions[from_unit] | |
| return ms_value * conversions[to_unit] | |
| def convert_distance(value, from_unit, to_unit): | |
| conversions = UnitConverter.distance_conversions() | |
| # Convert to meters first, then to target unit | |
| m_value = value / conversions[from_unit] | |
| return m_value * conversions[to_unit] | |
| class QuizGenerator: | |
| """Generate quiz questions for vector concepts""" | |
| def __init__(self): | |
| self.question_types = [ | |
| "vector_addition", | |
| "vector_magnitude", | |
| "vector_angle", | |
| "boat_crossing", | |
| "projectile_range" | |
| ] | |
| def generate_question(self): | |
| question_type = random.choice(self.question_types) | |
| if question_type == "vector_addition": | |
| return self._vector_addition_question() | |
| elif question_type == "vector_magnitude": | |
| return self._vector_magnitude_question() | |
| elif question_type == "vector_angle": | |
| return self._vector_angle_question() | |
| elif question_type == "boat_crossing": | |
| return self._boat_crossing_question() | |
| elif question_type == "projectile_range": | |
| return self._projectile_range_question() | |
| def _vector_addition_question(self): | |
| mag_a = random.uniform(3, 8) | |
| angle_a = random.choice([0, 30, 45, 60, 90, 120, 135, 150, 180]) | |
| mag_b = random.uniform(3, 8) | |
| angle_b = random.choice([0, 30, 45, 60, 90, 120, 135, 150, 180]) | |
| vec_a = Vector2D(magnitude=mag_a, angle=angle_a) | |
| vec_b = Vector2D(magnitude=mag_b, angle=angle_b) | |
| result = vec_a + vec_b | |
| return { | |
| "type": "vector_addition", | |
| "question": f"Vector A has magnitude {mag_a:.1f} at {angle_a}°, Vector B has magnitude {mag_b:.1f} at {angle_b}°. What is the magnitude of their sum?", | |
| "answer": result.magnitude, | |
| "tolerance": 0.5, | |
| "explanation": f"Vector A + Vector B = ({vec_a.x:.2f}, {vec_a.y:.2f}) + ({vec_b.x:.2f}, {vec_b.y:.2f}) = ({result.x:.2f}, {result.y:.2f})\nMagnitude = √({result.x:.2f}² + {result.y:.2f}²) = {result.magnitude:.2f}" | |
| } | |
| def _vector_magnitude_question(self): | |
| x = random.uniform(-10, 10) | |
| y = random.uniform(-10, 10) | |
| vec = Vector2D(x, y) | |
| return { | |
| "type": "vector_magnitude", | |
| "question": f"A vector has components x = {x:.1f} and y = {y:.1f}. What is its magnitude?", | |
| "answer": vec.magnitude, | |
| "tolerance": 0.2, | |
| "explanation": f"Magnitude = √(x² + y²) = √({x:.1f}² + {y:.1f}²) = √({x**2:.1f} + {y**2:.1f}) = {vec.magnitude:.2f}" | |
| } | |
| def _vector_angle_question(self): | |
| x = random.uniform(-10, 10) | |
| y = random.uniform(-10, 10) | |
| vec = Vector2D(x, y) | |
| return { | |
| "type": "vector_angle", | |
| "question": f"A vector has components x = {x:.1f} and y = {y:.1f}. What is its angle in degrees?", | |
| "answer": vec.angle, | |
| "tolerance": 2.0, | |
| "explanation": f"Angle = arctan(y/x) = arctan({y:.1f}/{x:.1f}) = {vec.angle:.1f}°" | |
| } | |
| def _boat_crossing_question(self): | |
| boat_speed = random.uniform(4, 8) | |
| current_speed = random.uniform(2, 5) | |
| boat_angle = 90 # Straight across | |
| current_angle = 180 # Opposite direction | |
| boat_vel = Vector2D(magnitude=boat_speed, angle=boat_angle) | |
| current_vel = Vector2D(magnitude=current_speed, angle=current_angle) | |
| result = boat_vel + current_vel | |
| return { | |
| "type": "boat_crossing", | |
| "question": f"A boat aims straight across a river at {boat_speed:.1f} m/s. The current flows at {current_speed:.1f} m/s opposite to the boat. What is the boat's actual speed?", | |
| "answer": result.magnitude, | |
| "tolerance": 0.3, | |
| "explanation": f"Resultant velocity = √({boat_speed}² + {current_speed}²) = {result.magnitude:.2f} m/s" | |
| } | |
| def _projectile_range_question(self): | |
| speed = random.uniform(15, 25) | |
| angle = 45 # Optimal angle | |
| gravity = 9.81 | |
| # Range formula: R = v²sin(2θ)/g | |
| range_m = (speed**2 * math.sin(math.radians(2 * angle))) / gravity | |
| return { | |
| "type": "projectile_range", | |
| "question": f"A projectile is launched at {speed:.0f} m/s at 45°. What is its range in meters? (g = 9.81 m/s²)", | |
| "answer": range_m, | |
| "tolerance": 2.0, | |
| "explanation": f"Range = v²sin(2θ)/g = {speed}²×sin(90°)/9.81 = {speed**2}/9.81 = {range_m:.1f} m" | |
| } | |
| class VectorPhysicsSimulator: | |
| """Physics simulator for vector problems""" | |
| def __init__(self): | |
| self.time_step = 0.1 | |
| self.max_time = 20 | |
| def boat_crossing_problem(self, boat_speed, boat_angle, current_speed, current_angle): | |
| """ | |
| Simulate a boat crossing a river with current | |
| Returns the resultant velocity and trajectory | |
| """ | |
| boat_velocity = Vector2D(magnitude=boat_speed, angle=boat_angle) | |
| current_velocity = Vector2D(magnitude=current_speed, angle=current_angle) | |
| # Resultant velocity | |
| resultant_velocity = boat_velocity + current_velocity | |
| # Calculate angle between intended and actual heading | |
| heading_difference = boat_velocity.angle_between(resultant_velocity) | |
| # Calculate trajectory points | |
| time_points = np.arange(0, self.max_time, self.time_step) | |
| x_points = resultant_velocity.x * time_points | |
| y_points = resultant_velocity.y * time_points | |
| return { | |
| 'boat_velocity': boat_velocity, | |
| 'current_velocity': current_velocity, | |
| 'resultant_velocity': resultant_velocity, | |
| 'heading_difference': heading_difference, | |
| 'trajectory_x': x_points, | |
| 'trajectory_y': y_points, | |
| 'time_points': time_points | |
| } | |
| def projectile_motion(self, initial_speed, launch_angle, gravity=9.81): | |
| """ | |
| Simulate projectile motion with error analysis | |
| """ | |
| initial_velocity = Vector2D(magnitude=initial_speed, angle=launch_angle) | |
| # Calculate trajectory | |
| time_flight = 2 * initial_velocity.y / gravity if initial_velocity.y > 0 else 0 | |
| time_points = np.linspace(0, time_flight, 100) if time_flight > 0 else np.array([0]) | |
| x_points = initial_velocity.x * time_points | |
| y_points = initial_velocity.y * time_points - 0.5 * gravity * time_points**2 | |
| # Error analysis - show effect of 5% measurement uncertainty | |
| speed_error = initial_speed * 0.05 | |
| angle_error = 2 # 2 degree uncertainty | |
| # Upper bound | |
| upper_vel = Vector2D(magnitude=initial_speed + speed_error, angle=launch_angle + angle_error) | |
| upper_flight = 2 * upper_vel.y / gravity if upper_vel.y > 0 else 0 | |
| upper_time = np.linspace(0, upper_flight, 100) if upper_flight > 0 else np.array([0]) | |
| upper_x = upper_vel.x * upper_time | |
| upper_y = upper_vel.y * upper_time - 0.5 * gravity * upper_time**2 | |
| # Lower bound | |
| lower_vel = Vector2D(magnitude=max(0.1, initial_speed - speed_error), angle=launch_angle - angle_error) | |
| lower_flight = 2 * lower_vel.y / gravity if lower_vel.y > 0 else 0 | |
| lower_time = np.linspace(0, lower_flight, 100) if lower_flight > 0 else np.array([0]) | |
| lower_x = lower_vel.x * lower_time | |
| lower_y = lower_vel.y * lower_time - 0.5 * gravity * lower_time**2 | |
| return { | |
| 'initial_velocity': initial_velocity, | |
| 'trajectory_x': x_points, | |
| 'trajectory_y': y_points, | |
| 'time_points': time_points, | |
| 'max_range': max(x_points) if len(x_points) > 0 else 0, | |
| 'max_height': max(y_points) if len(y_points) > 0 else 0, | |
| 'error_upper_x': upper_x, | |
| 'error_upper_y': upper_y, | |
| 'error_lower_x': lower_x, | |
| 'error_lower_y': lower_y, | |
| 'speed_uncertainty': speed_error, | |
| 'angle_uncertainty': angle_error | |
| } | |
| def plot_vectors(ax, vectors, labels, colors, origin=(0, 0)): | |
| """Plot vectors on matplotlib axes""" | |
| ax.clear() | |
| for vector, label, color in zip(vectors, labels, colors): | |
| ax.arrow(origin[0], origin[1], vector.x, vector.y, | |
| head_width=0.5, head_length=0.5, fc=color, ec=color, | |
| linewidth=2, label=label) | |
| ax.set_xlim(-10, 15) | |
| ax.set_ylim(-10, 15) | |
| ax.grid(True, alpha=0.3) | |
| ax.set_aspect('equal') | |
| ax.legend() | |
| ax.set_xlabel('X (m/s)') | |
| ax.set_ylabel('Y (m/s)') | |
| def plot_trajectory(ax, x_points, y_points, title="Trajectory", error_bounds=None): | |
| """Plot trajectory on matplotlib axes with optional error bounds""" | |
| ax.clear() | |
| ax.plot(x_points, y_points, 'b-', linewidth=2, label='Trajectory') | |
| if error_bounds: | |
| ax.plot(error_bounds['upper_x'], error_bounds['upper_y'], 'r--', alpha=0.6, label='Upper bound (+5% speed, +2° angle)') | |
| ax.plot(error_bounds['lower_x'], error_bounds['lower_y'], 'g--', alpha=0.6, label='Lower bound (-5% speed, -2° angle)') | |
| ax.fill_between(error_bounds['upper_x'], error_bounds['upper_y'], | |
| error_bounds['lower_y'][:len(error_bounds['upper_y'])], | |
| alpha=0.2, color='yellow', label='Uncertainty range') | |
| ax.scatter(x_points[0], y_points[0], color='green', s=100, label='Start', zorder=5) | |
| if len(x_points) > 1: | |
| ax.scatter(x_points[-1], y_points[-1], color='red', s=100, label='End', zorder=5) | |
| ax.grid(True, alpha=0.3) | |
| ax.set_xlabel('X Position (m)') | |
| ax.set_ylabel('Y Position (m)') | |
| ax.set_title(title) | |
| ax.legend() |