
import math
class Name:
    def __init__(self, name: str):
        self.name = str(name)
class Symbol:
    def __init__(self, symbol: str):
        self.symbol = str(symbol)
class Unit:
    def __init__(self, name: Name, symbols: list[Symbol]):
        if not isinstance(name, Name):
            raise TypeError('name must be a Name instance.')
        if not isinstance(symbols, list) or not symbols or any(not isinstance(symbol, Symbol) for symbol in symbols):
            raise TypeError('symbols must be a non-empty list of Symbol instances.')
        self.name = name
        self.symbols = list(symbols)
meter_unit = Unit(name=Name('meter'), symbols=[Symbol('m')])
centimeter_unit = Unit(name=Name('centimeter'), symbols=[Symbol('cm')])
gram_unit = Unit(name=Name('gram'), symbols=[Symbol('g')])
kilogram_unit = Unit(name=Name('kilogram'), symbols=[Symbol('kg')])
class Value:
    def __init__(self, number: float, unit: Unit):
        if unit is None:
            raise ValueError('unit is required.')
        if not isinstance(unit, Unit):
            raise TypeError('unit must be a Unit instance.')
        self.number = float(number)
        self.unit = unit
    def convert_to(self, target_unit: Unit) -> 'Value':
        if not isinstance(target_unit, Unit):
            raise TypeError('target_unit must be a Unit instance.')
        source_name = self.unit.name.name
        target_name = target_unit.name.name
        if source_name == target_name:
            return Value(self.number, self.unit)
        factor = unit_conversion_factors.get((source_name, target_name))
        if factor is None:
            raise ValueError(f'No conversion from {source_name} to {target_name}.')
        return Value(self.number * factor, target_unit)
class Distance:
    def __init__(self, value: Value):
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        if value.unit.name.name not in {'meter', 'centimeter'}:
            raise ValueError('Distance only accepts meter or centimeter units.')
        self.value = value
unit_conversion_factors: dict[tuple[str, str], float] = {
    ('meter', 'centimeter'): 100.0,
    ('centimeter', 'meter'): 0.01,
    ('gram', 'kilogram'): 0.001,
    ('kilogram', 'gram'): 1000.0,
}
def distance_validation_probe() -> dict[str, bool]:
    value_requires_unit = False
    distance_in_kg_rejected = False
    distance_in_m_ok = False
    try:
        Value(5, None)
    except ValueError:
        value_requires_unit = True
    try:
        Distance(Value(2.0, kilogram_unit))
    except ValueError:
        distance_in_kg_rejected = True
    distance_in_m_ok = Distance(Value(2.0, meter_unit)).value.unit.name.name == 'meter'
    return dict(value_requires_unit=value_requires_unit, distance_in_kg_rejected=distance_in_kg_rejected, distance_in_m_ok=distance_in_m_ok)
kilometer_unit = Unit(name=Name('kilometer'), symbols=[Symbol('km')])
inch_unit = Unit(name=Name('inch'), symbols=[Symbol('in')])
foot_unit = Unit(name=Name('foot'), symbols=[Symbol('ft')])
kilopascal_unit = Unit(name=Name('kilopascal'), symbols=[Symbol('kPa')])
pound_unit = Unit(name=Name('pound'), symbols=[Symbol('lb')])
pascal_unit = Unit(name=Name('pascal'), symbols=[Symbol('Pa')])
unit_conversion_factors.update({('kilometer','meter'):1000.0,('meter','kilometer'):0.001,('inch','centimeter'):2.54,('centimeter','inch'):0.39370078740157477,('foot','inch'):12.0,('inch','foot'):0.08333333333333333,('foot','meter'):0.3048,('meter','foot'):3.280839895013123})
unit_conversion_factors.update({('gram','pound'):0.0022046226218487757,('pound','gram'):453.59237,('pascal','kilopascal'):0.001,('kilopascal','pascal'):1000.0})
class Constant:
    def __init__(self, name: Name, value: Value):
        if not isinstance(name, Name):
            raise TypeError('name must be a Name instance.')
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        self.name = name
        self.value = value

    def get_value(self, target_unit: Unit) -> float:
        if not isinstance(target_unit, Unit):
            raise TypeError('target_unit must be a Unit instance.')
        return self.value.convert_to(target_unit).number
newton_unit = Unit(name=Name('newton'), symbols=[Symbol('N')])
cubic_meter_unit = Unit(name=Name('cubic_meter'), symbols=[Symbol('m^3')])
gravitational_constant_unit = Unit(name=Name('gravitational_constant_unit'), symbols=[Symbol('m^3/(kg*s^2)')])
gravitational_constant = Constant(name=Name('gravitational_constant'), value=Value(6.6743e-11, gravitational_constant_unit))
meter_per_second_squared_unit = Unit(name=Name('meter_per_second_squared'), symbols=[Symbol('m/s^2')])
kilogram_per_cubic_meter_unit = Unit(name=Name('kilogram_per_cubic_meter'), symbols=[Symbol('kg/m^3')])
class Mass:
    def __init__(self, value: Value):
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        if value.unit.name.name not in {'gram', 'kilogram', 'pound'}:
            raise ValueError('Mass only accepts gram, kilogram, or pound units.')
        self.value = value
class Radius:
    def __init__(self, value: Value):
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        if value.unit.name.name not in {'meter', 'centimeter', 'kilometer', 'inch', 'foot'}:
            raise ValueError('Radius only accepts meter, centimeter, kilometer, inch, or foot units.')
        self.value = value
class Volume:
    def __init__(self, value: Value):
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        if value.unit.name.name not in {'cubic_meter'}:
            raise ValueError('Volume only accepts cubic_meter units.')
        self.value = value
class PhysicalObject:
    def __init__(self, name: Name, mass: Mass, radius: Radius = None, volume: Volume = None):
        if not isinstance(name, Name):
            raise TypeError('name must be a Name instance.')
        if not isinstance(mass, Mass):
            raise TypeError('mass must be a Mass instance.')
        if radius is not None and not isinstance(radius, Radius):
            raise TypeError('radius must be a Radius instance when provided.')
        if volume is not None and not isinstance(volume, Volume):
            raise TypeError('volume must be a Volume instance when provided.')
        self.name = name
        self.mass = mass
        self.radius = radius
        self.volume = volume

    def calculate_density(self) -> Value:
        if self.volume is None:
            raise ValueError('volume is required to calculate density.')
        mass_kg = self.mass.value.convert_to(kilogram_unit).number
        volume_m3 = self.volume.value.convert_to(cubic_meter_unit).number
        if volume_m3 <= 0:
            raise ValueError('volume must be greater than zero.')
        return Value(mass_kg / volume_m3, kilogram_per_cubic_meter_unit)
standard_gravity = Constant(name=Name('standard_gravity'), value=Value(9.80665, meter_per_second_squared_unit))
class Force:
    def __init__(self, value: Value):
        if not isinstance(value, Value):
            raise TypeError('value must be a Value instance.')
        if value.unit.name.name != 'newton':
            raise ValueError('Force only accepts newton units.')
        self.value = value
def calculate_net_force(mass: Mass, acceleration: Value) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    if not isinstance(acceleration, Value):
        raise TypeError('acceleration must be a Value instance.')
    acceleration_m_s2 = acceleration.convert_to(meter_per_second_squared_unit).number
    mass_kg = mass.value.convert_to(kilogram_unit).number
    return Value(mass_kg * acceleration_m_s2, newton_unit)
def calculate_weight(mass: Mass) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    mass_kg = mass.value.convert_to(kilogram_unit).number
    g_m_s2 = standard_gravity.get_value(meter_per_second_squared_unit)
    return Value(mass_kg * g_m_s2, newton_unit)
second_unit = Unit(name=Name('second'), symbols=[Symbol('s')])
meter_per_second_unit = Unit(name=Name('meter_per_second'), symbols=[Symbol('m/s')])
def velocity_at_time_1d(v0_m_s: float, a_m_s2: float, t_s: float) -> float:
    t = float(t_s)
    if t < 0.0:
        raise ValueError('time must be non-negative.')
    return float(v0_m_s) + float(a_m_s2) * t
def position_at_time_1d(x0: Distance, v0: Value, a: Value, t: Value) -> Distance:
    if not isinstance(x0, Distance):
        raise TypeError("x0 must be a Distance instance.")
    if not isinstance(v0, Value) or not isinstance(a, Value) or not isinstance(t, Value):
        raise TypeError("v0, a, and t must be Value instances.")
    v0_m_s = v0.convert_to(meter_per_second_unit).number
    a_m_s2 = a.convert_to(meter_per_second_squared_unit).number
    t_s = t.convert_to(second_unit).number
    if t_s < 0.0:
        raise ValueError("time must be non‑negative.")
    x0_m = x0.value.convert_to(meter_unit).number
    x_m = x0_m + v0_m_s * t_s + 0.5 * a_m_s2 * (t_s ** 2)
    return Distance(Value(x_m, meter_unit))
joule_unit = Unit(name=Name('joule'), symbols=[Symbol('J')])
def potential_energy(mass: Mass, height: Distance) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    if not isinstance(height, Distance):
        raise TypeError('height must be a Distance instance.')
    mass_kg = mass.value.convert_to(kilogram_unit).number
    height_m = height.value.convert_to(meter_unit).number
    g_m_s2 = standard_gravity.get_value(meter_per_second_squared_unit)
    return Value(mass_kg * g_m_s2 * height_m, joule_unit)
def kinetic_energy(mass: Mass, velocity: Value) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    if not isinstance(velocity, Value):
        raise TypeError('velocity must be a Value instance.')
    mass_kg = mass.value.convert_to(kilogram_unit).number
    velocity_m_s = velocity.convert_to(meter_per_second_unit).number
    return Value(0.5 * mass_kg * velocity_m_s ** 2, joule_unit)
def speed_from_drop_height(height: Distance) -> Value:
    if not isinstance(height, Distance):
        raise TypeError('height must be a Distance instance.')
    height_m = height.value.convert_to(meter_unit).number
    if height_m < 0.0:
        raise ValueError('height must be non-negative.')
    g_m_s2 = standard_gravity.get_value(meter_per_second_squared_unit)
    return Value((2.0 * g_m_s2 * height_m) ** 0.5, meter_per_second_unit)
kilogram_meter_per_second_unit = Unit(name=Name('kilogram_meter_per_second'), symbols=[Symbol('kg·m/s')])
def momentum(mass: Mass, velocity: Value) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    if not isinstance(velocity, Value):
        raise TypeError('velocity must be a Value instance.')
    mass_kg = mass.value.convert_to(kilogram_unit).number
    velocity_m_s = velocity.convert_to(meter_per_second_unit).number
    return Value(mass_kg * velocity_m_s, kilogram_meter_per_second_unit)
newton_second_unit = Unit(name=Name('newton_second'), symbols=[Symbol('N·s')])
def impulse(mass: Mass, delta_v: Value) -> Value:
    if not isinstance(mass, Mass):
        raise TypeError('mass must be a Mass instance.')
    if not isinstance(delta_v, Value):
        raise TypeError('delta_v must be a Value instance.')
    mass_kg = mass.value.convert_to(kilogram_unit).number
    delta_v_m_s = delta_v.convert_to(meter_per_second_unit).number
    return Value(mass_kg * delta_v_m_s, newton_second_unit)
def average_force_from_impulse(impulse_value: Value, delta_t: Value) -> Value:
    if not isinstance(impulse_value, Value):
        raise TypeError('impulse_value must be a Value instance.')
    if impulse_value.unit.name.name != 'newton_second':
        raise ValueError('impulse_value must use newton_second units.')
    if not isinstance(delta_t, Value):
        raise TypeError('delta_t must be a Value instance.')
    delta_t_s = delta_t.convert_to(second_unit).number
    if delta_t_s <= 0.0:
        raise ValueError('delta_t must be positive.')
    return Value(impulse_value.number / delta_t_s, newton_unit)
def projectile_time_of_flight(v0_m_s: float, theta_deg: float) -> float:
    import math
    speed = float(v0_m_s)
    angle_deg = float(theta_deg)
    if speed < 0.0:
        raise ValueError("launch speed must be non-negative.")
    if not 0.0 <= angle_deg <= 90.0:
        raise ValueError("theta_deg must be between 0 and 90 degrees for equal-height motion.")
    g_m_s2 = standard_gravity.get_value(meter_per_second_squared_unit)
    return (2.0 * speed * math.sin(math.radians(angle_deg))) / g_m_s2
def projectile_range(v0_m_s: float, theta_deg: float) -> float:
    """Return the horizontal range for a projectile launched at v0_m_s and angle theta_deg.
    The function reuses `projectile_time_of_flight` to compute the time of flight and then multiplies by the horizontal component of the initial velocity.
    """
    import math
    # Coerce inputs
    speed = float(v0_m_s)
    angle_deg = float(theta_deg)
    if speed < 0.0:
        raise ValueError("launch speed must be non-negative.")
    if not 0.0 <= angle_deg <= 90.0:
        raise ValueError("theta_deg must be between 0 and 90 degrees (inclusive).")
    # Handle edge angles
    if angle_deg == 0.0 or angle_deg == 90.0:
        return 0.0
    # Use existing time_of_flight
    time_of_flight = projectile_time_of_flight(speed, angle_deg)
    horizontal_velocity = speed * math.cos(math.radians(angle_deg))
    return horizontal_velocity * time_of_flight
def projectile_height(v0_m_s: float, theta_deg: float, t_s: float) -> float:
    """Return the vertical height (in meters) of a projectile at time `t_s`."""
    import math
    speed = float(v0_m_s)
    angle_deg = float(theta_deg)
    time_s = float(t_s)
    if speed < 0.0:
        raise ValueError("launch speed must be non-negative.")
    if not 0.0 <= angle_deg <= 90.0:
        raise ValueError("theta_deg must be between 0 and 90 degrees.")
    if time_s < 0.0:
        raise ValueError("t_s must be non-negative.")
    g_m_s2 = standard_gravity.get_value(meter_per_second_squared_unit)
    angle_rad = math.radians(angle_deg)
    return speed * math.sin(angle_rad) * time_s - 0.5 * g_m_s2 * time_s ** 2
def sphere_volume_m3(radius_m: float) -> float:
    import math
    radius = float(radius_m)
    if radius < 0.0:
        raise ValueError("radius must be non‑negative")
    return (4.0 / 3.0) * math.pi * radius ** 3
def cylinder_volume_m3(radius_m: float, height_m: float) -> float:
    radius = float(radius_m)
    height = float(height_m)
    if radius < 0.0 or height < 0.0:
        raise ValueError("radius and height must be non‑negative")
    return (math.pi * radius ** 2) * height
