Vectors / src /vector_physics.py
NavyDevilDoc's picture
Upload vector_physics.py
b6893cc verified
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))
@property
def magnitude(self):
return math.sqrt(self.x**2 + self.y**2)
@property
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"""
@staticmethod
def speed_conversions():
return {
"m/s": 1.0,
"km/h": 3.6,
"mph": 2.237,
"ft/s": 3.281,
"knots": 1.944
}
@staticmethod
def distance_conversions():
return {
"meters": 1.0,
"kilometers": 0.001,
"miles": 0.000621,
"feet": 3.281,
"yards": 1.094
}
@staticmethod
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]
@staticmethod
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()