defmodule JPLEphem do @moduledoc """ JPL Ephemeris Library for Elixir - Main API Module This module provides a unified interface to the JPL Ephemeris library, offering convenient access to astronomical calculations, coordinate transformations, and time scale conversions. ## Quick Start # Time conversions {:ok, jd} = JPLEphem.utc_to_jd(DateTime.utc_now()) {:ok, datetime} = JPLEphem.jd_to_utc(jd) # Coordinate transformations {:ok, ecliptic} = JPLEphem.icrf_to_ecliptic({1.0, 0.0, 0.0}) {:ok, {ra, dec}} = JPLEphem.to_ra_dec({1.0, 0.0, 0.0}) # Caching JPLEphem.cache_put(:mars, position_data, 3600) {:ok, data} = JPLEphem.cache_get(:mars) ## Modules - `JPLEphem.Time` - Time scale conversions and Julian Day calculations - `JPLEphem.Coord` - Coordinate system transformations and vector math - `JPLEphem.Cache` - High-performance caching with TTL support """ alias JPLEphem.{Time, Coord, Cache} @type vector3 :: {float(), float(), float()} @type datetime :: DateTime.t() @type julian_day :: float() @type cache_key :: any() @type cache_value :: any() # Time conversion functions @doc """ Convert UTC datetime to Julian Day. ## Examples iex> JPLEphem.utc_to_jd(~U[2025-01-01 12:00:00Z]) {:ok, 2460677.0} """ @spec utc_to_jd(datetime()) :: {:ok, julian_day()} | {:error, atom()} def utc_to_jd(datetime), do: Time.utc_to_jd(datetime) @doc """ Convert Julian Day to UTC datetime. ## Examples iex> JPLEphem.jd_to_utc(2460677.0) {:ok, ~U[2025-01-01 12:00:00.000000Z]} """ @spec jd_to_utc(julian_day()) :: {:ok, datetime()} | {:error, atom()} def jd_to_utc(jd), do: Time.jd_to_utc(jd) @doc """ Convert UTC to Terrestrial Time (TT). ## Examples iex> {:ok, tt_jd} = JPLEphem.utc_to_tt(~U[2025-01-01 12:00:00Z]) iex> is_float(tt_jd) and tt_jd > 2460677.0 true """ @spec utc_to_tt(datetime()) :: {:ok, julian_day()} | {:error, atom()} def utc_to_tt(datetime), do: Time.utc_to_tt(datetime) @doc """ Convert Terrestrial Time (TT) to Barycentric Dynamical Time (TDB). ## Examples iex> {:ok, tdb_jd} = JPLEphem.tt_to_tdb(2460677.0) iex> is_float(tdb_jd) and tdb_jd > 2460676.0 and tdb_jd < 2460678.0 true """ @spec tt_to_tdb(julian_day()) :: {:ok, julian_day()} | {:error, atom()} def tt_to_tdb(tt_jd), do: Time.tt_to_tdb(tt_jd) @doc """ Get leap seconds for a given UTC datetime. ## Examples iex> {:ok, leap_secs} = JPLEphem.leap_seconds_at(~U[2025-01-01 12:00:00Z]) iex> is_integer(leap_secs) and leap_secs > 30 true """ @spec leap_seconds_at(datetime()) :: {:ok, integer()} | {:error, atom()} def leap_seconds_at(datetime), do: Time.leap_seconds_at(datetime) # Coordinate transformation functions @doc """ Transform coordinates from ICRF to Ecliptic system. ## Examples iex> JPLEphem.icrf_to_ecliptic({1.0, 0.0, 0.0}) {:ok, {1.0, 0.0, 0.0}} """ @spec icrf_to_ecliptic(vector3()) :: {:ok, vector3()} | {:error, atom()} def icrf_to_ecliptic(vector), do: Coord.icrf_to_ecliptic(vector) @doc """ Transform coordinates from Ecliptic to ICRF system. ## Examples iex> JPLEphem.ecliptic_to_icrf({1.0, 0.0, 0.0}) {:ok, {1.0, 0.0, 0.0}} """ @spec ecliptic_to_icrf(vector3()) :: {:ok, vector3()} | {:error, atom()} def ecliptic_to_icrf(vector), do: Coord.ecliptic_to_icrf(vector) @doc """ Convert Cartesian to spherical coordinates. Uses physics convention where phi is polar angle from Z-axis. ## Examples iex> JPLEphem.to_spherical({3.0, 4.0, 0.0}) {:ok, {5.0, 0.9272952180016122, 1.5707963267948966}} """ @spec to_spherical(vector3()) :: {:ok, {float(), float(), float()}} | {:error, atom()} def to_spherical(vector), do: Coord.to_spherical(vector) @doc """ Convert spherical to Cartesian coordinates. Uses physics convention where phi is polar angle from Z-axis. ## Examples iex> {:ok, {x, y, z}} = JPLEphem.from_spherical(5.0, 0.9272952180016122, 1.5707963267948966) iex> abs(x - 3.0) < 1.0e-10 and abs(y - 4.0) < 1.0e-10 and abs(z) < 1.0e-10 true """ @spec from_spherical(float(), float(), float()) :: {:ok, vector3()} | {:error, atom()} def from_spherical(r, theta, phi), do: Coord.from_spherical(r, theta, phi) @doc """ Convert Cartesian coordinates to Right Ascension and Declination. ## Examples iex> JPLEphem.to_ra_dec({1.0, 0.0, 0.0}) {:ok, {0.0, 0.0}} """ @spec to_ra_dec(vector3()) :: {:ok, {float(), float()}} | {:error, atom()} def to_ra_dec(vector), do: Coord.to_ra_dec(vector) @doc """ Convert Right Ascension and Declination to Cartesian coordinates. ## Examples iex> JPLEphem.from_ra_dec({0.0, 0.0}, 1.0) {:ok, {1.0, 0.0, 0.0}} """ @spec from_ra_dec({float(), float()}, float()) :: {:ok, vector3()} | {:error, atom()} def from_ra_dec(ra_dec, distance), do: Coord.from_ra_dec(ra_dec, distance) @doc """ Convert AU to kilometers. ## Examples iex> JPLEphem.au_to_km({1.0, 0.5, 0.0}) {149597870.7, 74798935.35, 0.0} """ @spec au_to_km(vector3()) :: vector3() def au_to_km(vector), do: Coord.au_to_km(vector) @doc """ Convert kilometers to AU. ## Examples iex> JPLEphem.km_to_au({149597870.7, 74798935.35, 0.0}) {1.0, 0.5, 0.0} """ @spec km_to_au(vector3()) :: vector3() def km_to_au(vector), do: Coord.km_to_au(vector) # Vector mathematics @doc """ Calculate vector magnitude. ## Examples iex> JPLEphem.vector_magnitude({3.0, 4.0, 0.0}) 5.0 """ @spec vector_magnitude(vector3()) :: float() def vector_magnitude(vector), do: Coord.vector_magnitude(vector) @doc """ Normalize a vector to unit length. ## Examples iex> {x, y, z} = JPLEphem.normalize_vector({3.0, 4.0, 0.0}) iex> abs(x - 0.6) < 1.0e-10 and abs(y - 0.8) < 1.0e-10 and abs(z) < 1.0e-10 true """ @spec normalize_vector(vector3()) :: vector3() | {:error, atom()} def normalize_vector(vector), do: Coord.normalize_vector(vector) @doc """ Calculate dot product of two vectors. ## Examples iex> JPLEphem.dot_product({1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}) 32.0 """ @spec dot_product(vector3(), vector3()) :: float() def dot_product(v1, v2), do: Coord.dot_product(v1, v2) @doc """ Calculate cross product of two vectors. ## Examples iex> JPLEphem.cross_product({1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}) {0.0, 0.0, 1.0} """ @spec cross_product(vector3(), vector3()) :: vector3() def cross_product(v1, v2), do: Coord.cross_product(v1, v2) # Cache functions @doc """ Store a value in the cache with TTL. ## Examples iex> JPLEphem.cache_put(:doctest_key, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}, 3600) :ok """ @spec cache_put(cache_key(), cache_value(), number()) :: :ok def cache_put(key, value, ttl \\ nil), do: Cache.put(key, value, ttl) @doc """ Retrieve a value from the cache. ## Examples iex> JPLEphem.cache_put(:doctest_get, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}, 3600) iex> JPLEphem.cache_get(:doctest_get) {:ok, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}} """ @spec cache_get(cache_key()) :: {:ok, cache_value()} | :not_found def cache_get(key), do: Cache.get(key) @doc """ Delete a key from the cache. ## Examples iex> JPLEphem.cache_put(:doctest_del, {1, 2, 3}, 3600) iex> JPLEphem.cache_delete(:doctest_del) :ok """ @spec cache_delete(cache_key()) :: :ok def cache_delete(key), do: Cache.delete(key) @doc """ Clear all cache entries. ## Examples iex> JPLEphem.cache_clear() :ok """ @spec cache_clear() :: :ok def cache_clear(), do: Cache.clear() @doc """ Get cache statistics. ## Examples iex> stats = JPLEphem.cache_stats() iex> is_map(stats) and Map.has_key?(stats, :size) and Map.has_key?(stats, :hits) true """ @spec cache_stats() :: map() def cache_stats(), do: Cache.stats() @doc """ Get current cache size. ## Examples iex> size = JPLEphem.cache_size() iex> is_integer(size) and size >= 0 true """ @spec cache_size() :: non_neg_integer() def cache_size(), do: Cache.size() @doc """ Trigger garbage collection of expired cache entries. ## Examples iex> JPLEphem.cache_gc() :ok """ @spec cache_gc() :: :ok def cache_gc(), do: Cache.gc() # Convenience functions @doc """ Get current time as Julian Day. ## Examples iex> {:ok, jd} = JPLEphem.now_jd() iex> is_float(jd) true """ @spec now_jd() :: {:ok, julian_day()} | {:error, atom()} def now_jd(), do: utc_to_jd(DateTime.utc_now()) @doc """ Get current time in Terrestrial Time (TT). ## Examples iex> {:ok, tt} = JPLEphem.now_tt() iex> is_float(tt) true """ @spec now_tt() :: {:ok, julian_day()} | {:error, atom()} def now_tt(), do: utc_to_tt(DateTime.utc_now()) @doc """ Get current time in Barycentric Dynamical Time (TDB). ## Examples iex> {:ok, tdb} = JPLEphem.now_tdb() iex> is_float(tdb) true """ @spec now_tdb() :: {:ok, julian_day()} | {:error, atom()} def now_tdb() do with {:ok, tt} <- now_tt(), {:ok, tdb} <- tt_to_tdb(tt) do {:ok, tdb} end end end