Skip to content

Examples

Many examples can be also found in the root of this page.

Unit Conversion¤

When exact=True is used, the exact conversion factor is returned.

docs/examples.py
import isqx
import isqx.usc as usc

tsfc_converter = isqx.convert(
    usc.LB / (isqx.HOUR * usc.LBF), isqx.KG / (isqx.S * isqx.N), exact=True
)
print(f"1 lb/(h·lbf) = {tsfc_converter.scale} g/(s·kN)")
1 lb/(h·lbf) = 50/1765197 g/(s·kN)

Disambiguation¤

isqx defines many quantity kinds that are generic over different unit systems (MKS, imperial...). Calling the quantity kind with a particular unit will produce a tagged expression:

docs/examples.py
print(isqx.SPECIFIC_HEAT_CAPACITY(usc.BTU_IT * usc.LB**-1 * usc.R**-1))
(btu_it · pound⁻¹ · rankine⁻¹)['specific_heat_capacity']
- btu_it = 1055.05585262 · joule
  - joule = newton · meter
    - newton = kilogram · meter · second⁻²
- pound = 0.45359237 · kilogram
- rankine = 5/9 · kelvin

Both quantity kinds and tagged expressions can be indexed. B = A["some_tag"] can roughly be interpreted as B is a subclass of A. However, because tags are designed to avoid strict inheritance hierarchies.

Tagged expressions are incompatible with another and will raise an error:

docs/examples.py
import isqx.aerospace as aero

try:
    isqx.convert(
        aero.PRESSURE_ALTITUDE(isqx.M), aero.GEOMETRIC_ALTITUDE(isqx.M)
    )
except isqx.DimensionMismatchError as e:
    print(e)
cannot convert from `meter['altitude', relative to `'mean_sea_level'`, 'pressure']` to `meter['altitude', relative to `'mean_sea_level'`, 'geometric']`.
= help: expected compatible dimensions, but found:
dimension of origin: `L['altitude', relative to `'mean_sea_level'`, 'pressure']`
dimension of target: `L['altitude', relative to `'mean_sea_level'`, 'geometric']`

Introspection¤

Annotations can be inspected at runtime. One example is to dynamically generate docstrings from the annotations:

docs/examples.py
import typing as t
from dataclasses import dataclass

from typing_extensions import ParamSpec

T = t.TypeVar("T")
P = ParamSpec("P")


@t.overload
def sphinx_doc(
    *, formatter: t.Union[isqx.Formatter, str]
) -> t.Callable[[T], T]: ...


@t.overload
def sphinx_doc(obj: T, /) -> T: ...


def sphinx_doc(
    obj: t.Optional[T] = None,
    /,
    *,
    formatter: t.Union[isqx.Formatter, str] = isqx.BasicFormatter(
        verbose=False
    ),
):
    def wrapper(obj_: T) -> T:
        doc = obj_.__doc__ or ""
        if doc.startswith(obj_.__qualname__ + "("):
            doc = ""

        hints = t.get_type_hints(obj_, include_extras=True)
        params_doc = []
        for name, hint in hints.items():
            if t.get_origin(hint) is not t.Annotated:
                continue
            # assuming the first metadata item is the isqx unit expression
            unit_expr = t.get_args(hint)[1]
            unit_str = isqx.fmt(unit_expr, formatter=formatter)
            directive = ":returns:" if name == "return" else f":param {name}:"
            params_doc.append(f"{directive} `{unit_str}`")

        if doc and params_doc:
            doc += "\n\n" + "\n".join(params_doc)
        elif params_doc:
            doc = "\n".join(params_doc)

        obj_.__doc__ = doc.strip()
        return obj_

    if obj is None:
        return wrapper
    return wrapper(obj)

We can apply this decorator to an annotated function to automatically document the units of its parameters:

docs/examples.py
_T = t.TypeVar("_T")

MPerSCruiseTas = t.Annotated[_T, aero.TAS["cruise"](isqx.M_PERS)]
LOverD = t.Annotated[_T, isqx.ratio(isqx.LIFT(isqx.N), isqx.DRAG(isqx.N))]
SIsp = t.Annotated[_T, isqx.TIME["specific_impulse"](isqx.S)]
WiOverWf = t.Annotated[
    _T, isqx.ratio(aero.TAKEOFF_MASS(isqx.KG), aero.LANDING_MASS(isqx.KG))
]
Range = t.Annotated[_T, isqx.DISTANCE(isqx.M)]


@sphinx_doc
def breguet_range(
    v: MPerSCruiseTas,
    l_over_d: LOverD,
    isp: SIsp,
    wi_over_wf: WiOverWf,
    *,
    xp,  # library that implements the array api
) -> Range:
    """Calculates the Breguet range of an aircraft."""
    return v * l_over_d * isp * xp.log(wi_over_wf)


print(breguet_range.__doc__)
Calculates the Breguet range of an aircraft.

:param v: `(meter · second⁻¹)['airspeed', 'true', 'cruise']`
:param l_over_d: `ratio[`newton[_Tensor(rank=1), 'lift']` to `newton[_Tensor(rank=1), 'drag']`]`
:param isp: `second['specific_impulse']`
:param wi_over_wf: `ratio[`kilogram['aircraft', 'takeoff']` to `kilogram['aircraft', 'landing']`]`
:returns: `meter['distance']`
Or with a dataclass:
docs/examples.py
@sphinx_doc
@dataclass
class AircraftParams:
    """Aircraft performance parameters."""

    l_over_d: LOverD
    specific_impulse: SIsp


print(AircraftParams.__doc__)
Aircraft performance parameters.

:param l_over_d: `ratio[`newton[_Tensor(rank=1), 'lift']` to `newton[_Tensor(rank=1), 'drag']`]`
:param specific_impulse: `second['specific_impulse']`