Source code for autoemulate.simulations.projectile

# from https://github.com/alan-turing-institute/mogp-emulator/blob/main/mogp_emulator/demos/projectile.py
import numpy as np
import torch
from scipy.integrate import solve_ivp

from autoemulate.core.types import NumpyLike, TensorLike
from autoemulate.simulations.base import Simulator


[docs] class Projectile(Simulator): """ Simulator of projectile motion. A projectile is launched from an initial height of 2 meters at an angle of 45 degrees and falls under the influence of gravity and air resistance. Drag is proportional to the square of the velocity. We would like to determine the distance travelled by the projectile as a function of the drag coefficient and the launch velocity. """ def __init__( self, parameters_range: dict[str, tuple[float, float]] | None = None, output_names: list[str] | None = None, log_level: str = "progress_bar", ): if parameters_range is None: parameters_range = {"c": (-5.0, 1.0), "v0": (0.0, 1000)} if output_names is None: output_names = ["distance"] super().__init__(parameters_range, output_names, log_level) def _forward(self, x: TensorLike) -> TensorLike: """ Simulate the projectile motion and return the distance travelled. Parameters ---------- x: TensorLike Dictionary of input parameter values to simulate: - `c`: the drag coefficient on a log scale - `v0`: velocity Returns ------- TensorLike Distance travelled by projectile. """ assert x.shape[0] == 1, ( f"Simulator._forward expects a single input, got {x.shape[0]}" ) y = simulate_projectile(x.cpu().numpy()[0]) return torch.tensor([y]).view(-1, 1)
[docs] class ProjectileMultioutput(Simulator): """ Multi-output simulator of projectile motion. Simulator of projectile motion that outputs both the distance travelled by the projectile and its velocity on impact. """ def __init__( self, parameters_range: dict[str, tuple[float, float]] | None = None, output_names: list[str] | None = None, log_level: str = "progress_bar", ): if parameters_range is None: parameters_range = {"c": (-5.0, 1.0), "v0": (0.0, 1000)} if output_names is None: output_names = ["distance", "impact_velocity"] super().__init__(parameters_range, output_names, log_level) def _forward(self, x: TensorLike) -> TensorLike: """ Simulate the projectile motion with multiple outputs. Simulate projectile motion and return the distance travelled and impact velocity. Parameters ---------- x: TensorLike Dictionary of input parameter values to simulate: - `c`: the drag coefficient on a log scale - `v0`: velocity Returns ------- TensorLike Distance travelled by projectile and impact velocity. """ assert x.shape[0] == 1, ( f"Simulator._forward expects a single input, got {x.shape[0]}" ) y = simulate_projectile_multioutput(x.cpu().numpy()[0]) return torch.tensor([y])
[docs] def f(t: float, y: NumpyLike, c: float): # noqa: ARG001 """ Compute RHS of system of differential equations, returning vector derivative. Parameters ---------- t: float Time variable (not used). y: array Array of dependent variables (vx, vy, x, y). c: float Drag coefficient (non-negative). """ # check inputs and extract assert len(y) == 4 assert c >= 0.0 vx = y[0] vy = y[1] # calculate derivatives dydt = np.zeros(4) dydt[0] = -c * vx * np.sqrt(vx**2 + vy**2) dydt[1] = -9.8 - c * vy * np.sqrt(vx**2 + vy**2) dydt[2] = vx dydt[3] = vy return dydt
[docs] def event(t: float, y: NumpyLike, c: float) -> float: # noqa: ARG001 """ Event to trigger end of integration. Stops when projectile hits ground. Parameters ---------- t: float Time variable (not used). y: array Array of dependent variables (vx, vy, x, y). c: float Drag coefficient (non-negative). Returns ------- float The height of the projectile. """ assert len(y) == 4 assert c >= 0.0 return y[3]
# indicate this event terminates the simulation event.terminal = True # pyright: ignore[reportFunctionMemberAccess]
[docs] def simulator_base(x: NumpyLike): """ Simulate ODE system for projectile motion with drag. Returns distance projectile travels. Parameters ---------- x: NumpyLike Array of input parameters (c, v0). Returns ------- results: scipy.integrate.OdeResult Results of ODE integration. """ # unpack values assert len(x) == 2 assert x[1] > 0.0 c = 10.0 ** x[0] v0 = x[1] # set initial conditions y0 = np.zeros(4) y0[0] = v0 / np.sqrt(2.0) y0[1] = v0 / np.sqrt(2.0) y0[3] = 2.0 # run simulation return solve_ivp(f, (0.0, 1.0e8), y0, events=event, args=(c,))
[docs] def simulate_projectile(x: NumpyLike) -> float: """ Return the distance travelled by the projectile. Distance is obtained by solving the ODE system for projectile motion with drag. Parameters ---------- x: NumpyLike Array of input parameters (c, v0). Returns ------- distance: float Distance travelled by projectile. """ results = simulator_base(x) return results.y_events[0][0][2]
[docs] def simulate_projectile_multioutput(x: NumpyLike) -> tuple[float, float]: """ Return the distance travelled by the projectile and its impact velocity. Simulator to solve ODE system with multiple outputs. Parameters ---------- x: NumpyLike Array of input parameters (c, v0). Returns ------- float, float Distance travelled by projectile and its velocity on impact. """ results = simulator_base(x) return ( results.y_events[0][0][2], np.sqrt(results.y_events[0][0][0] ** 2 + results.y_events[0][0][1] ** 2), )