Spaces:
Sleeping
Sleeping
| """Tier-3 physical systems: held out of training to support a generalisation | |
| claim ("converges on systems it never trained on").""" | |
| from __future__ import annotations | |
| import numpy as np | |
| from physix.systems.base import PhysicalSystem, SystemTier | |
| class ProjectileWithDrag(PhysicalSystem): | |
| """2-D projectile with quadratic air drag. | |
| Equations of motion:: | |
| d2x/dt2 = -k * |v| * vx | |
| d2y/dt2 = -g - k * |v| * vy | |
| where ``|v| = sqrt(vx**2 + vy**2)``. | |
| """ | |
| system_id: str = "projectile_drag" | |
| tier: SystemTier = SystemTier.TIER_3 | |
| state_variables: tuple[str, ...] = ("x", "y", "vx", "vy") | |
| duration: float = 5.0 # typical flight time for the parameter ranges below | |
| hint_template: str = ( | |
| "Projectile launched at angle {angle_deg:.0f} degrees with initial " | |
| "speed {v0:.1f} m/s. Air drag is non-negligible." | |
| ) | |
| def sample_parameters(self, rng: np.random.Generator) -> dict[str, float]: | |
| return { | |
| "g": 9.81, | |
| "k": float(rng.uniform(0.005, 0.02)), | |
| } | |
| def sample_initial_conditions(self, rng: np.random.Generator) -> dict[str, float]: | |
| speed = float(rng.uniform(15.0, 30.0)) | |
| angle = float(rng.uniform(np.deg2rad(30.0), np.deg2rad(70.0))) | |
| return { | |
| "x": 0.0, | |
| "y": 0.0, | |
| "vx": float(speed * np.cos(angle)), | |
| "vy": float(speed * np.sin(angle)), | |
| } | |
| def rhs( | |
| self, | |
| t: float, | |
| state: np.ndarray, | |
| params: dict[str, float], | |
| ) -> np.ndarray: | |
| _x, _y, vx, vy = state | |
| speed = float(np.sqrt(vx * vx + vy * vy)) | |
| return np.array( | |
| [ | |
| vx, | |
| vy, | |
| -params["k"] * speed * vx, | |
| -params["g"] - params["k"] * speed * vy, | |
| ], | |
| dtype=float, | |
| ) | |
| def ground_truth_equation(self) -> str: | |
| # Two-equation system rendered in a single string so the parser can | |
| # split on ';' or '\n'. Verifier handles both delimiters. | |
| return ( | |
| "d2x/dt2 = -k*sqrt(vx**2 + vy**2)*vx; " | |
| "d2y/dt2 = -g - k*sqrt(vx**2 + vy**2)*vy" | |
| ) | |
| def hint(self, parameters: dict[str, float]) -> str: | |
| ic = self.initial_conditions | |
| if not ic: | |
| return self.hint_template | |
| v0 = float(np.sqrt(ic["vx"] ** 2 + ic["vy"] ** 2)) | |
| angle_deg = float(np.rad2deg(np.arctan2(ic["vy"], ic["vx"]))) | |
| return self.hint_template.format(angle_deg=angle_deg, v0=v0) | |
| class ChargedInBField(PhysicalSystem): | |
| """Charged particle in a uniform magnetic field along z (circular motion). | |
| Equations of motion (assuming B = B_z * ẑ and v in xy-plane):: | |
| d2x/dt2 = (q*B/m) * vy | |
| d2y/dt2 = -(q*B/m) * vx | |
| """ | |
| system_id: str = "charged_b_field" | |
| tier: SystemTier = SystemTier.TIER_3 | |
| state_variables: tuple[str, ...] = ("x", "y", "vx", "vy") | |
| hint_template: str = ( | |
| "Charged particle in a uniform magnetic field. Charge-to-mass ratio " | |
| "q/m = {qm:.2f}, field strength {B:.2f} T." | |
| ) | |
| def sample_parameters(self, rng: np.random.Generator) -> dict[str, float]: | |
| return { | |
| "q": float(rng.choice([-1.0, 1.0])), | |
| "m": float(rng.uniform(0.5, 2.0)), | |
| "B": float(rng.uniform(0.5, 2.0)), | |
| } | |
| def sample_initial_conditions(self, rng: np.random.Generator) -> dict[str, float]: | |
| return { | |
| "x": 0.0, | |
| "y": 0.0, | |
| "vx": float(rng.uniform(0.5, 2.0)), | |
| "vy": float(rng.uniform(-2.0, 2.0)), | |
| } | |
| def rhs( | |
| self, | |
| t: float, | |
| state: np.ndarray, | |
| params: dict[str, float], | |
| ) -> np.ndarray: | |
| _x, _y, vx, vy = state | |
| omega = params["q"] * params["B"] / params["m"] | |
| return np.array([vx, vy, omega * vy, -omega * vx], dtype=float) | |
| def ground_truth_equation(self) -> str: | |
| return ( | |
| "d2x/dt2 = (q*B/m)*vy; " | |
| "d2y/dt2 = -(q*B/m)*vx" | |
| ) | |
| def hint(self, parameters: dict[str, float]) -> str: | |
| qm = parameters["q"] / parameters["m"] | |
| return self.hint_template.format(qm=qm, B=parameters["B"]) | |