diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..d0b7719
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,23 @@
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.11"
+
+ apt_packages:
+ - libcairo2-dev
+ - libfreetype6-dev
+ - libffi-dev
+ - libjpeg-dev
+ - libpng-dev
+ - libz-dev
+
+mkdocs:
+ configuration: mkdocs.yml
+
+python:
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
\ No newline at end of file
diff --git a/docs/docs/physics/compute.md b/docs/docs/physics/compute.md
index b75bc54..7b1d1eb 100644
--- a/docs/docs/physics/compute.md
+++ b/docs/docs/physics/compute.md
@@ -186,13 +186,28 @@ print(mag_v)
## Stochastic Physics (`phaethon.random`)
-Generates highly-optimized stochastic tensors that are instantly bounded by physical dimensions.
+Generates highly-optimized stochastic tensors that are instantly bounded by physical dimensions. Phaethon utilizes an isolated `RandomState` engine under the hood, ensuring that stochastic physics simulations remain completely reproducible without contaminating the global NumPy environment.
!!! warning "Axiom Bounds & Stochastic Generation"
Because `phaethon.random` functions automatically instantiate new `BaseUnit` tensors under the hood, generating stochastic values that violate the target unit's physical boundaries (e.g., generating negative `Kelvin` or `Mass`) will immediately trigger an `AxiomViolationError` under the `default` axiom strictness level.
To prevent unexpected crashes in your simulations, ensure your distribution parameters (`low`, `loc`, `scale`) remain within physically valid ranges, or temporarily adjust the axiom's strictness level using [`phaethon.using()`](config.md) or [`phaethon.config()`](config.md).
+### phaethon.random.seed
+
+Reseeds the isolated physics random number generator. Crucial for ensuring absolute reproducibility in stochastic physical models, thermodynamic simulations, or machine learning cross-validations.
+
+**Arguments:**
+
+
+
+
An integer to initialize the internal BitGenerator. If None, fresh entropy is drawn from the OS.
+
+
### phaethon.random.uniform / normal
Draws samples from continuous distributions (Uniform or Gaussian) and injects physical DNA.
@@ -231,6 +246,9 @@ Draws samples from continuous distributions (Uniform or Gaussian) and injects ph
```python
import phaethon as ptn
+# Ensure reproducibility
+ptn.random.seed(42)
+
# Uniform: Generate a 3x3 matrix of random pressures (10.5 to 20.5 Pascal)
pressures = ptn.random.uniform(low=10.5, high=20.5, size=(3, 3), unit='Pa')
@@ -300,15 +318,79 @@ Draws samples from an exponential distribution. Ideal for simulating the time be
The physical dimension to attach (typically 's' or u.Second).
+### phaethon.random.randint / choice
+
+Draws from discrete probability distributions. `randint` generates uniformly distributed integers, while `choice` generates a random sample from a predefined 1-D array of allowed physical states.
+
+**Arguments:**
+
+
+
+
Integer bounds, or the 1-D array of allowed magnitudes to sample from.
+
+
+
+
+
Output array shape.
+
+
+
+
+
The physical dimension to attach.
+
+
+**Example Usage:**
+
+```python
+import phaethon as ptn
+import phaethon.units as u
+
+# Generate quantized discrete energy levels (-1, 0, or 1 eV)
+energy_levels = ptn.random.randint(-1, 2, size=5, unit=u.Electronvolt)
+
+# Monte Carlo: Select a random speed from allowed discrete states
+allowed_speeds = [300.0, 400.0, 500.0]
+particles = ptn.random.choice(allowed_speeds, size=10, unit=u.MeterPerSecond)
+```
+
+### phaethon.random.shuffle / permutation
+
+Modifies sequence order. `shuffle` modifies a physical tensor sequence in-place along the first axis. `permutation` randomly permutes a tensor and returns a completely new copy (out-of-place).
+
+**Arguments:**
+
+
+
+
The physical tensor to shuffle/permute. If an integer is passed to permutation, it returns a dimensionless permuted range.
+
+
**Example Usage:**
```python
import phaethon as ptn
import phaethon.units as u
-# Simulating time between particle arrivals (average 1.5 seconds)
-arrival_times = ptn.random.exponential(scale=1.5, size=(3,), unit=u.Second)
+velocity_array = ptn.array([10.0, 20.0, 30.0], unit=u.MeterPerSecond)
+
+# Mutates the array directly in memory
+ptn.random.shuffle(velocity_array)
-print(arrival_times.dimension)
-# Output: 'time'
+# Creates a new array with randomized elements
+new_tensor = ptn.random.permutation(velocity_array)
```
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..9ea8871
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,4 @@
+mkdocs-material[imaging]>=9.0.0
+mkdocstrings[python]>=0.24.0
+mkdocs-minify-plugin>=0.7.0
+mkdocs-git-revision-date-localized-plugin>=1.2.0
\ No newline at end of file
diff --git a/src/phaethon/_typing.py b/src/phaethon/_typing.py
new file mode 100644
index 0000000..693660a
--- /dev/null
+++ b/src/phaethon/_typing.py
@@ -0,0 +1,102 @@
+from __future__ import annotations
+
+from typing import Any, TYPE_CHECKING, TypeAlias, Iterable, TypeVar, Callable, Literal, Protocol, Mapping, TypedDict
+from phaethon.core.compat import HAS_POLARS, HAS_PANDAS, HAS_NUMPY, HAS_TORCH
+
+if TYPE_CHECKING:
+ if HAS_POLARS: import polars as pl
+ if HAS_PANDAS: import pandas as pd
+ if HAS_NUMPY: import numpy as np
+ if HAS_TORCH: import torch
+ from .core.base import BaseUnit
+
+ DataFrameLike: TypeAlias = pd.DataFrame | pl.DataFrame
+ NumericLike: TypeAlias = int | float | str | np.ndarray | Iterable[Any]
+ UnitLike: TypeAlias = str | type[BaseUnit]
+
+ ConvertibleInput: TypeAlias = NumericLike | 'BaseUnit' | Iterable['BaseUnit']
+ ColumnTarget: TypeAlias = str | Any
+ Extractable: TypeAlias = BaseUnit | torch.Tensor | np.ndarray | float | int | str | list[Any] | tuple[Any, ...] | None
+ UnwrappedArray: TypeAlias = np.ndarray | float | int | None
+
+ ContextDict: TypeAlias = dict[str, NumericLike | BaseUnit]
+ AliasRegistry: TypeAlias = dict[str, str | list[str]]
+
+ ImputeMethod: TypeAlias = Literal['mean', 'median', 'mode', 'ffill', 'bfill'] | str | float
+ InterpolationMethod = Literal[
+ "linear", "nearest", "time", "index", "values", "pad", "zero", "slinear","quadratic",
+ "cubic", "spline", "barycentric", "polynomial", "krogh","piecewise_polynomial",
+ "pchip", "akima", "cubicspline"
+ ]
+ ErrorAction: TypeAlias = Literal['raise', 'coerce', 'clip']
+ StrictnessLevel = Literal["default", "strict", "strict_warn", "loose_warn", "ignore"]
+ NumDtype: TypeAlias = Literal["float64", "float32", "float16", "int64", "int32"]
+
+ if HAS_TORCH:
+ from .core.pinns.tensor import PTensor
+ TensorLikeDict: TypeAlias = dict[str, PTensor | torch.Tensor]
+ TensorLikeTuple: TypeAlias = tuple[PTensor | torch.Tensor, ...]
+ GradTarget: TypeAlias = bool | list[str]
+
+ class _ParquetConfig(TypedDict, total=False):
+ engine: Literal["pyarrow", "fastparquet", "auto"]
+ compression: Literal["snappy", "gzip", "brotli", "lz4", "zstd"]
+ index: bool
+ partition_cols: list[str]
+
+ class _HDF5Config(TypedDict, total=False):
+ compression: Literal["gzip", "lzf", "szip"]
+ compression_opts: int
+ chunks: bool | tuple[int, ...]
+
+ DatasetInput: TypeAlias = Mapping[str, Any] | Iterable[Any] | Any
+ DatasetStateDict: TypeAlias = dict[str, dict[str, Any]]
+
+ class _ResponsiveTableConfig(TypedDict, total=False):
+ max_col_width: int
+ float_format: str
+ justify: Literal["left", "right", "center"]
+
+else:
+ DataFrameLike: TypeAlias = Any
+ NumericLike: TypeAlias = Any
+ UnitLike: TypeAlias = Any
+ ConvertibleInput: TypeAlias = Any
+ ColumnTarget: TypeAlias = Any
+ Extractable: TypeAlias = Any
+ UnwrappedArray: TypeAlias = Any
+ ContextDict: TypeAlias = Any
+ AliasRegistry: TypeAlias = Any
+ ImputeMethod: TypeAlias = Any
+ InterpolationMethod: TypeAlias = Any
+ ErrorAction: TypeAlias = Any
+ StrictnessLevel: TypeAlias = Any
+ NumDtype: TypeAlias = Any
+ TensorLikeDict: TypeAlias = Any
+ TensorLikeTuple: TypeAlias = Any
+ GradTarget: TypeAlias = Any
+
+_Signature: TypeAlias = frozenset[tuple[str, int]]
+_DataFrameT = TypeVar("_DataFrameT", bound=DataFrameLike)
+_NumericT = TypeVar("_NumericT", bound=NumericLike)
+_UnitT = TypeVar("_UnitT", bound='BaseUnit')
+_UnitT_co = TypeVar("_UnitT_co", bound='BaseUnit', covariant=True)
+_UnitClassT = TypeVar("_UnitClassT", bound=type)
+_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
+_ReturnT = TypeVar("_ReturnT")
+_KeyT = TypeVar('_KeyT')
+
+class SupportsPredict(Protocol):
+ def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
+ def predict(self, X: Any) -> Any: ...
+
+class SupportsTransform(Protocol):
+ def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
+ def transform(self, X: Any) -> Any: ...
+
+class SupportsInverseTransform(SupportsTransform, Protocol):
+ def inverse_transform(self, X: Any) -> Any: ...
+
+_EstimatorT = TypeVar("_EstimatorT", bound=SupportsPredict)
+_TransformerT = TypeVar("_TransformerT", bound=SupportsTransform)
+_InvTransformerT = TypeVar("_InvTransformerT", bound=SupportsInverseTransform)
\ No newline at end of file
diff --git a/src/phaethon/core/axioms.py b/src/phaethon/core/axioms.py
index a60781f..0b6b1fb 100644
--- a/src/phaethon/core/axioms.py
+++ b/src/phaethon/core/axioms.py
@@ -17,7 +17,7 @@
class SupportsContextEvaluation(Protocol):
def __call__(self, context: ContextDict) -> NumericLike: ...
-from .compat import _UnitClassT, _CallableT, NumericLike, ContextDict
+from .._typing import _UnitClassT, _CallableT, NumericLike, ContextDict
from ..exceptions import AxiomViolationError, DimensionMismatchError
try:
diff --git a/src/phaethon/core/base.py b/src/phaethon/core/base.py
index c2eee84..a58de82 100644
--- a/src/phaethon/core/base.py
+++ b/src/phaethon/core/base.py
@@ -8,7 +8,7 @@
from .registry import ureg
from .config import get_config, using
from ..exceptions import DimensionMismatchError, PhysicalAlgebraError
-from .compat import _UnitT, ContextDict, NumericLike, UnitLike
+from .._typing import _UnitT, ContextDict, NumericLike, UnitLike
if TYPE_CHECKING:
import numpy as np
diff --git a/src/phaethon/core/base.pyi b/src/phaethon/core/base.pyi
new file mode 100644
index 0000000..4050976
--- /dev/null
+++ b/src/phaethon/core/base.pyi
@@ -0,0 +1,118 @@
+import numpy as np
+import numpy.typing as npt
+from typing import Any, overload
+from .._typing import ContextDict, NumericLike, UnitLike, _UnitT
+
+class _DecomposeDispatcher:
+ def __call__(self) -> str: ...
+ def __get__(self, instance: Any, owner: type) -> '_DecomposeDispatcher': ...
+
+class _PhaethonUnitMeta(type):
+ dimension: str | None
+ symbol: str | None
+ base_multiplier: float
+ base_offset: float
+ is_anonymous: bool
+ __semantic__: str | None
+ _signature: frozenset[Any]
+
+ def __mul__(cls, other: Any) -> type['BaseUnit']: ...
+ def __rmul__(cls, other: Any) -> type['BaseUnit']: ...
+ def __truediv__(cls, other: Any) -> type['BaseUnit']: ...
+ def __rtruediv__(cls, other: Any) -> type['BaseUnit']: ...
+ def __pow__(cls, power: int | float) -> type['BaseUnit']: ...
+ def __matmul__(cls, other: type['BaseUnit']) -> type['BaseUnit']: ...
+ def __rmatmul__(cls, other: type['BaseUnit']) -> type['BaseUnit']: ...
+
+ def __lt__(cls, other: Any) -> bool: ...
+ def __le__(cls, other: Any) -> bool: ...
+ def __gt__(cls, other: Any) -> bool: ...
+ def __ge__(cls, other: Any) -> bool: ...
+
+class BaseUnit(metaclass=_PhaethonUnitMeta):
+ """
+ The foundational core class for all units in Phaethon.
+ """
+ dimension: str | None
+ symbol: str | None
+ aliases: list[str] | None
+ base_multiplier: float
+ base_offset: float
+ is_anonymous: bool
+ __semantic__: str | None
+ context: ContextDict
+
+ def __init__(self, value: NumericLike, context: ContextDict | None = ...) -> None: ...
+
+ @property
+ def mag(self) -> float | npt.NDArray[np.float64]: ...
+
+ @property
+ def si(self) -> 'BaseUnit': ...
+
+ @property
+ def shape(self) -> tuple[int, ...]: ...
+
+ @property
+ def ndim(self) -> int: ...
+
+ @property
+ def T(self) -> 'BaseUnit': ...
+
+ def __getitem__(self, key: Any) -> 'BaseUnit': ...
+
+ def __add__(self, other: Any) -> 'BaseUnit': ...
+ def __sub__(self, other: Any) -> 'BaseUnit': ...
+ def __mul__(self, other: Any) -> 'BaseUnit': ...
+ def __truediv__(self, other: Any) -> 'BaseUnit': ...
+ def __floordiv__(self, other: Any) -> 'BaseUnit': ...
+ def __mod__(self, other: Any) -> 'BaseUnit': ...
+
+ def __radd__(self, other: Any) -> 'BaseUnit': ...
+ def __rsub__(self, other: Any) -> 'BaseUnit': ...
+ def __rmul__(self, other: Any) -> 'BaseUnit': ...
+ def __rtruediv__(self, other: Any) -> 'BaseUnit': ...
+
+ def __pow__(self, power: int | float) -> 'BaseUnit': ...
+ def __invert__(self) -> 'BaseUnit': ...
+ def __neg__(self) -> 'BaseUnit': ...
+ def __pos__(self) -> 'BaseUnit': ...
+ def __abs__(self) -> 'BaseUnit': ...
+ def __round__(self, ndigits: int | None = ...) -> 'BaseUnit': ...
+
+ def __matmul__(self, other: Any) -> 'BaseUnit': ...
+ def __rmatmul__(self, other: Any) -> 'BaseUnit': ...
+
+ def sum(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
+ def mean(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
+ def max(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
+ def min(self, axis: int | tuple[int, ...] | None = ..., keepdims: bool = ..., **kwargs: Any) -> 'BaseUnit': ...
+
+ def reshape(self, shape: tuple[int, ...] | int, *args: Any, order: str = ...) -> 'BaseUnit': ...
+ def flatten(self, order: str = ...) -> 'BaseUnit': ...
+
+ @overload
+ def to(self, unit: type[_UnitT], context: ContextDict | None = ...) -> _UnitT: ...
+ @overload
+ def to(self, unit: str, context: ContextDict | None = ...) -> 'BaseUnit': ...
+ def to(self, unit: UnitLike, context: ContextDict | None = ...) -> 'BaseUnit' | _UnitT: ...
+
+ def format(self, prec: int = ..., sigfigs: int | None = ..., scinote: bool = ..., delim: bool | str = ..., tag: bool = ...) -> str: ...
+
+ decompose: _DecomposeDispatcher
+
+ def __eq__(self, other: object) -> bool | npt.NDArray[np.bool_]: ...
+ def __ne__(self, other: object) -> bool | npt.NDArray[np.bool_]: ...
+ def __lt__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
+ def __le__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
+ def __gt__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
+ def __ge__(self, other: Any) -> bool | npt.NDArray[np.bool_]: ...
+
+ def __float__(self) -> float: ...
+ def __int__(self) -> int: ...
+ def __str__(self) -> str: ...
+ def __repr__(self) -> str: ...
+
+ def __array__(self, dtype: Any = ...) -> npt.NDArray[Any]: ...
+ def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) -> Any: ...
+ def __array_function__(self, func: Any, types: Any, args: Any, kwargs: Any) -> Any: ...
\ No newline at end of file
diff --git a/src/phaethon/core/compat.py b/src/phaethon/core/compat.py
index 4162b48..d1e0967 100644
--- a/src/phaethon/core/compat.py
+++ b/src/phaethon/core/compat.py
@@ -4,8 +4,7 @@
import importlib.metadata
import warnings
from packaging.version import parse
-from typing import Any, TYPE_CHECKING, TypeAlias, Iterable, TypeVar, Callable, Literal, Protocol, Mapping
-
+from typing import Any
def _check_dep(module_name: str, package_name: str | None = None) -> tuple[bool, str | None]:
if package_name is None:
@@ -61,112 +60,6 @@ def require_h5py(feature_name: str = "HDF5 I/O") -> None:
if not HAS_H5PY:
raise ImportError(f"{feature_name} requires h5py. Install via: pip install 'phaethon[io]' or h5py>=3.0.0")
-
-# =========================================================================
-# THE TYPE REGISTRY & CONFIGURATIONS
-# =========================================================================
-if TYPE_CHECKING:
- from typing import TypedDict, Unpack
- if HAS_POLARS: import polars as pl
- if HAS_PANDAS: import pandas as pd
- if HAS_NUMPY: import numpy as np
- if HAS_TORCH: import torch
- from .base import BaseUnit
-
- DataFrameLike: TypeAlias = pd.DataFrame | pl.DataFrame
- NumericLike: TypeAlias = int | float | str | np.ndarray | Iterable[Any]
- UnitLike: TypeAlias = str | type[BaseUnit]
-
- ConvertibleInput: TypeAlias = NumericLike | 'BaseUnit' | Iterable['BaseUnit']
- ColumnTarget: TypeAlias = str | Any
- Extractable: TypeAlias = BaseUnit | torch.Tensor | np.ndarray | float | int | str | list[Any] | tuple[Any, ...] | None
- UnwrappedArray: TypeAlias = np.ndarray | float | int | None
-
- ContextDict: TypeAlias = dict[str, NumericLike | BaseUnit]
- AliasRegistry: TypeAlias = dict[str, str | list[str]]
-
- ImputeMethod: TypeAlias = Literal['mean', 'median', 'mode', 'ffill', 'bfill'] | str | float
- InterpolationMethod = Literal[
- "linear", "nearest", "time", "index", "values", "pad", "zero", "slinear","quadratic",
- "cubic", "spline", "barycentric", "polynomial", "krogh","piecewise_polynomial",
- "pchip", "akima", "cubicspline"
- ]
- ErrorAction: TypeAlias = Literal['raise', 'coerce', 'clip']
- StrictnessLevel = Literal["default", "strict", "strict_warn", "loose_warn", "ignore"]
- NumDtype: TypeAlias = Literal["float64", "float32", "float16", "int64", "int32"]
-
- if HAS_TORCH:
- from .pinns.tensor import PTensor
- TensorLikeDict: TypeAlias = dict[str, PTensor | torch.Tensor]
- TensorLikeTuple: TypeAlias = tuple[PTensor | torch.Tensor, ...]
- GradTarget: TypeAlias = bool | list[str]
-
- class _ParquetConfig(TypedDict, total=False):
- engine: Literal["pyarrow", "fastparquet", "auto"]
- compression: Literal["snappy", "gzip", "brotli", "lz4", "zstd"]
- index: bool
- partition_cols: list[str]
-
- class _HDF5Config(TypedDict, total=False):
- compression: Literal["gzip", "lzf", "szip"]
- compression_opts: int
- chunks: bool | tuple[int, ...]
-
- DatasetInput: TypeAlias = Mapping[str, Any] | Iterable[Any] | Any
- DatasetStateDict: TypeAlias = dict[str, dict[str, Any]]
-
- class _ResponsiveTableConfig(TypedDict, total=False):
- max_col_width: int
- float_format: str
- justify: Literal["left", "right", "center"]
-
-else:
- DataFrameLike: TypeAlias = Any
- NumericLike: TypeAlias = Any
- UnitLike: TypeAlias = Any
- ConvertibleInput: TypeAlias = Any
- ColumnTarget: TypeAlias = Any
- Extractable: TypeAlias = Any
- UnwrappedArray: TypeAlias = Any
- ContextDict: TypeAlias = Any
- AliasRegistry: TypeAlias = Any
- ImputeMethod: TypeAlias = Any
- InterpolationMethod: TypeAlias = Any
- ErrorAction: TypeAlias = Any
- StrictnessLevel: TypeAlias = Any
- NumDtype: TypeAlias = Any
- TensorLikeDict: TypeAlias = Any
- TensorLikeTuple: TypeAlias = Any
- GradTarget: TypeAlias = Any
-
-_Signature: TypeAlias = frozenset[tuple[str, int]]
-_DataFrameT = TypeVar("_DataFrameT", bound=DataFrameLike)
-_NumericT = TypeVar("_NumericT", bound=NumericLike)
-_UnitT = TypeVar("_UnitT", bound='BaseUnit')
-_UnitT_co = TypeVar("_UnitT_co", bound='BaseUnit', covariant=True)
-_UnitClassT = TypeVar("_UnitClassT", bound=type)
-_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
-_ReturnT = TypeVar("_ReturnT")
-_KeyT = TypeVar('_KeyT')
-
-class SupportsPredict(Protocol):
- def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
- def predict(self, X: Any) -> Any: ...
-
-class SupportsTransform(Protocol):
- def fit(self, X: Any, y: Any = None, **kwargs: Any) -> Any: ...
- def transform(self, X: Any) -> Any: ...
-
-class SupportsInverseTransform(SupportsTransform, Protocol):
- def inverse_transform(self, X: Any) -> Any: ...
-
-_EstimatorT = TypeVar("_EstimatorT", bound=SupportsPredict)
-_TransformerT = TypeVar("_TransformerT", bound=SupportsTransform)
-_InvTransformerT = TypeVar("_InvTransformerT", bound=SupportsInverseTransform)
-
-# =========================================================================
-# COMPATIBILITY HELPERS
-# =========================================================================
def is_pandas_df(df: Any) -> bool:
return HAS_PANDAS and df.__class__.__module__.startswith('pandas')
diff --git a/src/phaethon/core/config.py b/src/phaethon/core/config.py
index 91490a0..49b6956 100644
--- a/src/phaethon/core/config.py
+++ b/src/phaethon/core/config.py
@@ -4,7 +4,7 @@
from contextvars import ContextVar
from contextlib import contextmanager
from typing import Any, Generator
-from .compat import ContextDict, AliasRegistry, ErrorAction, StrictnessLevel
+from .._typing import ContextDict, AliasRegistry, ErrorAction, StrictnessLevel
_GLB = "__phaethon_global__"
_CTX = "__phaethon_context__"
diff --git a/src/phaethon/core/dataset.py b/src/phaethon/core/dataset.py
index b8a04ad..1c4f52d 100644
--- a/src/phaethon/core/dataset.py
+++ b/src/phaethon/core/dataset.py
@@ -12,7 +12,7 @@
import inspect
from typing import Any, Mapping, Iterator, TYPE_CHECKING, overload
-from .compat import HAS_NUMPY, HAS_TORCH
+from .._typing import HAS_NUMPY, HAS_TORCH
from .base import BaseUnit, _find_existing_class
from .registry import ureg
@@ -20,7 +20,7 @@
import numpy as np
if TYPE_CHECKING:
- from .compat import DatasetInput, TensorLikeDict
+ from .._typing import DatasetInput, TensorLikeDict
if HAS_TORCH:
import torch
from .pinns.tensor import PTensor
diff --git a/src/phaethon/core/fluent.py b/src/phaethon/core/fluent.py
index 7c0e8b8..f710e0d 100644
--- a/src/phaethon/core/fluent.py
+++ b/src/phaethon/core/fluent.py
@@ -1,11 +1,11 @@
from __future__ import annotations
from math import log10, floor
-from typing import Literal, Any, overload, Generic
+from typing import Literal, Any, overload, Generic, TYPE_CHECKING
from ..exceptions import ConversionError, AmbiguousUnitError, UnitNotFoundError
from .registry import UnitRegistry, ureg
-from .compat import UnitLike, ConvertibleInput, _ReturnT, ContextDict, NumericLike, NumDtype, TYPE_CHECKING
+from .._typing import UnitLike, ConvertibleInput, _ReturnT, ContextDict, NumericLike, NumDtype
try:
from .base import BaseUnit
diff --git a/src/phaethon/core/io.py b/src/phaethon/core/io.py
index 8de7d93..185ac51 100644
--- a/src/phaethon/core/io.py
+++ b/src/phaethon/core/io.py
@@ -19,7 +19,7 @@
)
if TYPE_CHECKING:
- from .compat import DataFrameLike, _ParquetConfig, _HDF5Config
+ from .._typing import DataFrameLike, _ParquetConfig, _HDF5Config
from typing import Unpack
from .dataset import Dataset
diff --git a/src/phaethon/core/linalg.py b/src/phaethon/core/linalg.py
index aa112ad..23a48a2 100644
--- a/src/phaethon/core/linalg.py
+++ b/src/phaethon/core/linalg.py
@@ -3,9 +3,10 @@
Physics-aware wrappers around numpy.linalg operations.
"""
from __future__ import annotations
-from typing import Any, overload
+from typing import Any
-from .compat import HAS_NUMPY, _UnitT
+from .compat import HAS_NUMPY
+from .._typing import _UnitT
from .base import BaseUnit
if HAS_NUMPY:
diff --git a/src/phaethon/core/ml/estimator.py b/src/phaethon/core/ml/estimator.py
index b175f0e..8c7492d 100644
--- a/src/phaethon/core/ml/estimator.py
+++ b/src/phaethon/core/ml/estimator.py
@@ -11,7 +11,7 @@
if TYPE_CHECKING:
import numpy as np
from typing_extensions import Self
- from ..compat import _EstimatorT, _UnitT, DataFrameLike, NumericLike, ConvertibleInput
+ from ..._typing import _EstimatorT, _UnitT, DataFrameLike, NumericLike, ConvertibleInput
else:
_EstimatorT = TypeVar("_EstimatorT")
_UnitT = TypeVar("_UnitT")
diff --git a/src/phaethon/core/ml/metrics.py b/src/phaethon/core/ml/metrics.py
index bcc0e16..51bc4e2 100644
--- a/src/phaethon/core/ml/metrics.py
+++ b/src/phaethon/core/ml/metrics.py
@@ -3,7 +3,7 @@
"""
from __future__ import annotations
from typing import Any, Literal, overload
-from ..compat import NumericLike, _UnitT
+from ..._typing import NumericLike, _UnitT
from ...exceptions import PhysicalAlgebraError
from ..base import BaseUnit
diff --git a/src/phaethon/core/ml/preprocessing.py b/src/phaethon/core/ml/preprocessing.py
index 7b9ca2b..5b9b3d6 100644
--- a/src/phaethon/core/ml/preprocessing.py
+++ b/src/phaethon/core/ml/preprocessing.py
@@ -11,7 +11,7 @@
if TYPE_CHECKING:
import numpy as np
from typing_extensions import Self
- from ..compat import _InvTransformerT, DataFrameLike, NumericLike
+ from ..._typing import _InvTransformerT, DataFrameLike, NumericLike
else:
_InvTransformerT = TypeVar("_InvTransformerT")
Self = Any
diff --git a/src/phaethon/core/ml/selection.py b/src/phaethon/core/ml/selection.py
index cfce3eb..1311746 100644
--- a/src/phaethon/core/ml/selection.py
+++ b/src/phaethon/core/ml/selection.py
@@ -5,7 +5,8 @@
from typing import Any
from ..base import BaseUnit
-from ..compat import HAS_NUMPY, HAS_SKLEARN, DataFrameLike
+from ..compat import HAS_NUMPY, HAS_SKLEARN
+from ..._typing import DataFrameLike
if HAS_SKLEARN:
import numpy as np
diff --git a/src/phaethon/core/pinns/layers.py b/src/phaethon/core/pinns/layers.py
index 89a317d..838c1a8 100644
--- a/src/phaethon/core/pinns/layers.py
+++ b/src/phaethon/core/pinns/layers.py
@@ -11,7 +11,7 @@
import torch
import torch.nn as nn
from .ops import fft, ifft
- from ..compat import _UnitT_co
+ from ..._typing import _UnitT_co
_BaseModule = nn.Module
elif HAS_TORCH:
import torch
diff --git a/src/phaethon/core/pinns/ops.py b/src/phaethon/core/pinns/ops.py
index 2173c25..a7c9d77 100644
--- a/src/phaethon/core/pinns/ops.py
+++ b/src/phaethon/core/pinns/ops.py
@@ -12,7 +12,7 @@
if TYPE_CHECKING:
import torch
import torch.fft as tfft
- from ..compat import _UnitT_co
+ from ..._typing import _UnitT_co
elif HAS_TORCH:
import torch
import torch.fft as tfft
diff --git a/src/phaethon/core/pinns/tensor.py b/src/phaethon/core/pinns/tensor.py
index 1d41680..02fd5b8 100644
--- a/src/phaethon/core/pinns/tensor.py
+++ b/src/phaethon/core/pinns/tensor.py
@@ -4,7 +4,8 @@
from __future__ import annotations
from typing import Any, Callable, TYPE_CHECKING, Generic, overload
-from ..compat import HAS_TORCH, _UnitT_co, NumericLike, _UnitT
+from ..compat import HAS_TORCH
+from ..._typing import _UnitT_co, NumericLike, _UnitT
from ..registry import ureg
from ..base import BaseUnit
from ...exceptions import DimensionMismatchError
diff --git a/src/phaethon/core/plotting.py b/src/phaethon/core/plotting.py
index efae4e7..7afc74d 100644
--- a/src/phaethon/core/plotting.py
+++ b/src/phaethon/core/plotting.py
@@ -8,7 +8,8 @@
from typing import Any, TYPE_CHECKING, overload, Mapping
import numpy as np
-from .compat import HAS_NUMPY, _KeyT, Extractable, UnwrappedArray
+from .compat import HAS_NUMPY
+from .._typing import _KeyT, Extractable, UnwrappedArray
if TYPE_CHECKING:
from .base import BaseUnit
@@ -17,7 +18,8 @@
from collections.abc import Mapping
from typing import Any, TYPE_CHECKING, overload
-from .compat import HAS_NUMPY, _KeyT, Extractable, UnwrappedArray
+from .compat import HAS_NUMPY
+from .._typing import _KeyT, Extractable, UnwrappedArray
if TYPE_CHECKING:
from .base import BaseUnit
diff --git a/src/phaethon/core/random.py b/src/phaethon/core/random.py
index 0f76c86..97af4d4 100644
--- a/src/phaethon/core/random.py
+++ b/src/phaethon/core/random.py
@@ -3,121 +3,212 @@
Generates stochastic tensors strictly bounded by physical dimensions.
"""
from __future__ import annotations
-from typing import Any, overload
+from typing import Any
-from .compat import UnitLike, HAS_NUMPY, _UnitT
+from .compat import HAS_NUMPY
from .registry import ureg
from .base import BaseUnit
if HAS_NUMPY:
import numpy as np
-def _resolve_unit(unit: UnitLike) -> type[BaseUnit]:
+def _resolve_unit(unit: Any) -> type[BaseUnit]:
if isinstance(unit, str):
return ureg().get_unit_class(unit)
if isinstance(unit, type) and issubclass(unit, BaseUnit):
return unit
- raise TypeError(f"The 'unit' argument must be a string alias or a BaseUnit class.")
+ raise TypeError("The 'unit' argument must be a string alias or a BaseUnit class.")
-# =========================================================================
-# ptn.random.uniform()
-# =========================================================================
-@overload
-def uniform(low: float = ..., high: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
-@overload
-def uniform(low: float = ..., high: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
-
-def uniform(low: float = 0.0, high: float = 1.0, size: Any = None, unit: UnitLike | None = None) -> BaseUnit:
- """
- Draws samples from a uniform distribution and injects physical DNA.
-
- Args:
- low: Lower boundary of the output interval.
- high: Upper boundary of the output interval.
- size: Output shape (e.g., (2, 3)).
- unit: The physical dimension to attach (Class or alias string).
-
- Returns:
- A BaseUnit tensor containing uniformly distributed physical values.
- """
- if not HAS_NUMPY: raise ImportError("NumPy is required.")
- if unit is None: raise ValueError("A physical unit must be specified.")
-
- raw_arr = np.random.uniform(low, high, size)
- UnitClass = _resolve_unit(unit)
- return UnitClass(raw_arr)
+class RandomState:
+ def seed(self, seed: int | None = None) -> None:
+ """
+ Reseeds the isolated physics random number generator.
+
+ Crucial for ensuring absolute reproducibility in stochastic physical models,
+ thermodynamic simulations, or machine learning cross-validations.
+
+ Args:
+ seed: An integer to initialize the internal BitGenerator.
+ If None, fresh, unpredictable entropy will be pulled from the OS.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ self._rng = np.random.default_rng(seed)
-# =========================================================================
-# ptn.random.normal()
-# =========================================================================
-@overload
-def normal(loc: float = ..., scale: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
-@overload
-def normal(loc: float = ..., scale: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+ def uniform(self, low=0.0, high=1.0, size=None, unit=None) -> BaseUnit:
+ """
+ Draws samples from a uniform distribution and injects physical DNA.
+
+ Args:
+ low: Lower boundary of the output interval.
+ high: Upper boundary of the output interval.
+ size: Output shape (e.g., (2, 3)).
+ unit: The physical dimension to attach (Class or alias string).
+
+ Returns:
+ A BaseUnit tensor containing uniformly distributed physical values.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.uniform(low, high, size)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
-def normal(loc: float = 0.0, scale: float = 1.0, size: Any = None, unit: UnitLike | None = None) -> BaseUnit:
- """
- Draws random samples from a normal (Gaussian) distribution.
-
- Args:
- loc: Mean ("centre") of the distribution.
- scale: Standard deviation (spread or "width").
- size: Output shape.
- unit: The physical dimension to attach.
- """
- if not HAS_NUMPY: raise ImportError("NumPy is required.")
- if unit is None: raise ValueError("A physical unit must be specified.")
-
- raw_arr = np.random.normal(loc, scale, size)
- UnitClass = _resolve_unit(unit)
- return UnitClass(raw_arr)
+ def normal(self, loc=0.0, scale=1.0, size=None, unit=None) -> BaseUnit:
+ """
+ Draws random samples from a normal (Gaussian) distribution.
+
+ Args:
+ loc: Mean ("centre") of the distribution.
+ scale: Standard deviation (spread or "width").
+ size: Output shape.
+ unit: The physical dimension to attach.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.normal(loc, scale, size)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
-@overload
-def poisson(lam: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
-@overload
-def poisson(lam: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+ def poisson(self, lam=1.0, size=None, unit=None) -> BaseUnit:
+ """
+ Draws samples from a Poisson distribution.
+
+ Extremely useful in Phaethon for modeling discrete physical events over
+ a continuous interval, such as radioactive decays (u.Becquerel) or
+ photon strikes (u.Photon).
+
+ Args:
+ lam: Expected number of events occurring in a fixed-time interval.
+ size: Output shape.
+ unit: The physical dimension to attach.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.poisson(lam, size)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
-def poisson(lam: float = 1.0, size: Any = None, unit: UnitLike | None = None) -> BaseUnit:
- """
- Draws samples from a Poisson distribution.
+ def exponential(self, scale=1.0, size=None, unit=None) -> BaseUnit:
+ """
+ Draws samples from an exponential distribution.
+
+ Ideal for simulating the time between independent physics events,
+ such as the decay time of radioactive isotopes or thermodynamic
+ relaxation times.
+
+ Args:
+ scale: The scale parameter, β = 1/λ. Must be non-negative.
+ size: Output shape.
+ unit: The physical dimension to attach (typically u.Second).
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.exponential(scale, size)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
- Extremely useful in Phaethon for modeling discrete physical events over
- a continuous interval, such as radioactive decays (u.Becquerel) or
- photon strikes (u.Photon).
+ def randint(self, low: int, high: int | None = None, size=None, unit=None) -> BaseUnit:
+ """
+ Draws random integers from a discrete uniform distribution.
+
+ Crucial for quantum mechanics, statistical grids, or any physical domain
+ where magnitudes are strictly quantized.
+
+ Args:
+ low: Lowest (signed) integer to be drawn from the distribution.
+ high: One above the largest (signed) integer to be drawn.
+ size: Output shape.
+ unit: The physical dimension to attach.
+
+ Returns:
+ A BaseUnit tensor containing discrete, uniformly distributed integers.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.integers(low, high, size=size)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
+
+ def choice(self, a, size=None, replace=True, p=None, unit=None) -> BaseUnit:
+ """
+ Generates a random sample from a given 1-D array of physical states.
+
+ Allows physical entities to randomly collapse into a predefined set of
+ allowed states. Ideal for simulating quantum state measurements or
+ drawing specific velocity vectors in a Monte Carlo gas simulation.
+
+ Args:
+ a: A 1-D array-like of allowed magnitudes.
+ size: Output shape.
+ replace: Whether the sample is with or without replacement.
+ p: The probabilities associated with each entry in 'a'.
+ unit: The physical dimension to attach.
+
+ Returns:
+ A BaseUnit tensor representing the collapsed random states.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if unit is None: raise ValueError("A physical unit must be specified.")
+
+ raw_arr = self._rng.choice(a, size=size, replace=replace, p=p)
+ UnitClass = _resolve_unit(unit)
+ return UnitClass(raw_arr)
- Args:
- lam: Expected number of events occurring in a fixed-time interval.
- size: Output shape.
- unit: The physical dimension to attach.
- """
- if not HAS_NUMPY: raise ImportError("NumPy is required.")
- if unit is None: raise ValueError("A physical unit must be specified.")
-
- raw_arr = np.random.poisson(lam, size)
- UnitClass = _resolve_unit(unit)
- return UnitClass(raw_arr)
+ def shuffle(self, x: BaseUnit) -> None:
+ """
+ Modifies a physical tensor sequence in-place by shuffling its contents.
+
+ Randomizes the distribution of physical magnitudes along the first axis
+ without destroying or altering the underlying dimensional DNA.
+ Vital for dataset splitting or cross-validation in `phaethon.ml`.
+
+ Args:
+ x: The Phaethon BaseUnit array to be shuffled in-place.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+ if not isinstance(x, BaseUnit):
+ raise TypeError("The input to shuffle must be a Phaethon BaseUnit.")
+
+ self._rng.shuffle(x._value)
-@overload
-def exponential(scale: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
-@overload
-def exponential(scale: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+ def permutation(self, x: BaseUnit | int) -> BaseUnit | Any:
+ """
+ Randomly permutes a physical tensor, returning a completely NEW copy.
+
+ Unlike `shuffle`, this method does not mutate the original tensor.
+ If an integer is passed, it returns a permuted range of dimensionless integers.
+
+ Args:
+ x: A Phaethon BaseUnit array, or an integer to define a range.
+
+ Returns:
+ A new BaseUnit tensor with randomly permuted elements along the first axis.
+ """
+ if not HAS_NUMPY: raise ImportError("NumPy is required.")
+
+ if isinstance(x, int):
+ from .units.scalar import Dimensionless
+ return Dimensionless(self._rng.permutation(x))
+
+ if not isinstance(x, BaseUnit):
+ raise TypeError("The input must be an integer or a Phaethon BaseUnit.")
+
+ raw_perm = self._rng.permutation(x.mag)
+ return x.__class__(raw_perm, context=x.context)
-def exponential(scale: float = 1.0, size: Any = None, unit: UnitLike | None = None) -> BaseUnit:
- """
- Draws samples from an exponential distribution.
-
- Ideal for simulating the time between independent physics events,
- such as the decay time of radioactive isotopes or thermodynamic
- relaxation times.
-
- Args:
- scale: The scale parameter, \u03b2 = 1/\u03bb. Must be non-negative.
- size: Output shape.
- unit: The physical dimension to attach (typically u.Second).
- """
- if not HAS_NUMPY: raise ImportError("NumPy is required.")
- if unit is None: raise ValueError("A physical unit must be specified.")
-
- raw_arr = np.random.exponential(scale, size)
- UnitClass = _resolve_unit(unit)
- return UnitClass(raw_arr)
\ No newline at end of file
+_rand = RandomState()
+
+seed = _rand.seed
+uniform = _rand.uniform
+normal = _rand.normal
+poisson = _rand.poisson
+exponential = _rand.exponential
+randint = _rand.randint
+choice = _rand.choice
+shuffle = _rand.shuffle
+permutation = _rand.permutation
\ No newline at end of file
diff --git a/src/phaethon/core/random.pyi b/src/phaethon/core/random.pyi
new file mode 100644
index 0000000..f120b9a
--- /dev/null
+++ b/src/phaethon/core/random.pyi
@@ -0,0 +1,59 @@
+import numpy as np
+import numpy.typing as npt
+from typing import Any, overload
+from .._typing import _UnitT
+from .base import BaseUnit
+
+class RandomState:
+ def __init__(self, seed: int | None = ...) -> None: ...
+
+ def seed(self, seed: int | None = ...) -> None: ...
+
+ @overload
+ def uniform(self, low: float = ..., high: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def uniform(self, low: float = ..., high: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ @overload
+ def normal(self, loc: float = ..., scale: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def normal(self, loc: float = ..., scale: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ @overload
+ def poisson(self, lam: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def poisson(self, lam: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ @overload
+ def exponential(self, scale: float = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def exponential(self, scale: float = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ @overload
+ def randint(self, low: int, high: int | None = ..., size: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def randint(self, low: int, high: int | None = ..., size: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ @overload
+ def choice(self, a: Any, size: Any = ..., replace: bool = ..., p: Any = ..., unit: type[_UnitT] = ...) -> _UnitT: ...
+ @overload
+ def choice(self, a: Any, size: Any = ..., replace: bool = ..., p: Any = ..., unit: str = ...) -> BaseUnit: ...
+
+ def shuffle(self, x: BaseUnit) -> None: ...
+
+ @overload
+ def permutation(self, x: int) -> BaseUnit: ...
+ @overload
+ def permutation(self, x: _UnitT) -> _UnitT: ...
+
+_rand: RandomState
+
+seed = _rand.seed
+uniform = _rand.uniform
+normal = _rand.normal
+poisson = _rand.poisson
+exponential = _rand.exponential
+randint = _rand.randint
+choice = _rand.choice
+shuffle = _rand.shuffle
+permutation = _rand.permutation
\ No newline at end of file
diff --git a/src/phaethon/core/registry.py b/src/phaethon/core/registry.py
index 076d948..bab089a 100644
--- a/src/phaethon/core/registry.py
+++ b/src/phaethon/core/registry.py
@@ -3,7 +3,7 @@
import math
from typing import TYPE_CHECKING, overload, Literal
from ..exceptions import UnitNotFoundError, DimensionMismatchError, AmbiguousUnitError
-from .compat import UnitLike, _Signature, _UnitT
+from .._typing import UnitLike, _Signature
if TYPE_CHECKING:
from .base import BaseUnit
diff --git a/src/phaethon/core/schema.py b/src/phaethon/core/schema.py
index 2ed9293..4f7a28f 100644
--- a/src/phaethon/core/schema.py
+++ b/src/phaethon/core/schema.py
@@ -8,11 +8,10 @@
from .registry import ureg
from .dataset import Dataset
-from .compat import (
- DataFrameLike, is_pandas_df, is_polars_df, _DataFrameT, ErrorAction, InterpolationMethod,
- UnitLike, ColumnTarget, AliasRegistry, ContextDict, ImputeMethod, StrictnessLevel,
- NumericLike, _UnitT_co, GradTarget, TensorLikeTuple, HAS_RAPIDFUZZ,
- require_torch
+from .compat import is_pandas_df, is_polars_df, HAS_RAPIDFUZZ, require_torch
+from .._typing import (
+ DataFrameLike, _DataFrameT, ErrorAction, InterpolationMethod, UnitLike, ColumnTarget, AliasRegistry,
+ ImputeMethod, StrictnessLevel, NumericLike, _UnitT_co, GradTarget, TensorLikeTuple, ContextDict
)
if TYPE_CHECKING:
diff --git a/src/phaethon/core/semantics.py b/src/phaethon/core/semantics.py
index 734299b..84116e1 100644
--- a/src/phaethon/core/semantics.py
+++ b/src/phaethon/core/semantics.py
@@ -7,7 +7,7 @@
if TYPE_CHECKING:
from .base import BaseUnit
- from .compat import ContextDict, NumericLike
+ from .._typing import ContextDict, NumericLike
from ..exceptions import DimensionMismatchError
from .compat import HAS_RAPIDFUZZ
diff --git a/src/phaethon/core/tensor.py b/src/phaethon/core/tensor.py
index 5d334d1..5c99c31 100644
--- a/src/phaethon/core/tensor.py
+++ b/src/phaethon/core/tensor.py
@@ -5,7 +5,8 @@
from __future__ import annotations
from typing import Any, overload
-from .compat import UnitLike, HAS_NUMPY, _UnitT
+from .compat import HAS_NUMPY
+from .._typing import UnitLike, _UnitT
from .registry import ureg
from .base import BaseUnit
diff --git a/src/phaethon/core/units/currency.py b/src/phaethon/core/units/currency.py
index 6fb44b3..00ad750 100644
--- a/src/phaethon/core/units/currency.py
+++ b/src/phaethon/core/units/currency.py
@@ -12,7 +12,7 @@
from ..base import BaseUnit
from .. import axioms as _axiom
from ..axioms import CtxProxy
-from ..compat import ContextDict
+from ..._typing import ContextDict
def fx_rate(symbol: str, default: float) -> CtxProxy:
"""
diff --git a/src/phaethon/core/vmath.py b/src/phaethon/core/vmath.py
index 3f6bdb9..116bdc5 100644
--- a/src/phaethon/core/vmath.py
+++ b/src/phaethon/core/vmath.py
@@ -11,7 +11,7 @@
import builtins
from typing import Any
-from .compat import NumericLike, _NumericT
+from .._typing import NumericLike, _NumericT
try:
import numpy as np
diff --git a/src/phaethon/linalg.py b/src/phaethon/linalg.py
index 465ca8b..21e2e14 100644
--- a/src/phaethon/linalg.py
+++ b/src/phaethon/linalg.py
@@ -1,6 +1,7 @@
"""
-Phaethon Linear Algebra Module.
-Physics-aware wrappers around numpy.linalg operations.
+Dimensional Linear Algebra Module.
+Provides strict, physics-aware matrix operations preserving
+dimensional integrity and isotropic scaling.
"""
from .core.linalg import inv, det, solve, norm
diff --git a/src/phaethon/random.py b/src/phaethon/random.py
index 7726c9d..55e2dee 100644
--- a/src/phaethon/random.py
+++ b/src/phaethon/random.py
@@ -2,6 +2,11 @@
Random Physics Module.
Generates stochastic tensors strictly bounded by physical dimensions.
"""
-from .core.random import uniform, normal, poisson, exponential
+from .core.random import (
+ seed, uniform, normal, poisson, exponential, randint, choice, shuffle, permutation
+)
-__all__ = ["uniform", "normal", "poisson", "exponential"]
\ No newline at end of file
+__all__ = [
+ "seed", "uniform", "normal", "poisson", "exponential",
+ "randint", "choice", "shuffle", "permutation"
+]
\ No newline at end of file
diff --git a/tests/test_physics/test_compute.py b/tests/test_physics/test_compute.py
index 9398240..5310678 100644
--- a/tests/test_physics/test_compute.py
+++ b/tests/test_physics/test_compute.py
@@ -6,7 +6,7 @@
import phaethon as ptn
import phaethon.units as u
-from phaethon.exceptions import AxiomViolationError, DimensionMismatchError
+from phaethon.exceptions import AxiomViolationError
def test_native_scientific_compute():
@@ -17,79 +17,118 @@ def test_native_scientific_compute():
raw_matrix = [[1.0, 2.0], [3.0, 4.0]]
arr_f32 = ptn.array(raw_matrix, u.Meter, dtype=np.float32, ndmin=3, order="F")
- assert arr_f32.shape == (1, 2, 2)
- assert arr_f32.mag.dtype == np.float32
- assert arr_f32.mag.flags["F_CONTIGUOUS"] is True
- assert arr_f32.dimension == "length"
+ assert arr_f32.shape == (1, 2, 2), "Array reshaping (ndmin) failed."
+ assert arr_f32.mag.dtype == np.float32, "Dtype casting failed."
+ assert arr_f32.mag.flags["F_CONTIGUOUS"] is True, "Fortran memory order was not preserved."
+ assert arr_f32.dimension == "length", "Dimension resolution failed."
massive_data = np.arange(1000, dtype=np.float64)
arr_zero_copy = ptn.asarray(massive_data, u.Joule)
- assert np.shares_memory(massive_data, arr_zero_copy.mag)
- assert arr_zero_copy.dimension == "energy"
+ assert np.shares_memory(massive_data, arr_zero_copy.mag), "asarray must not copy memory."
+ assert arr_zero_copy.dimension == "energy", "Dimension resolution failed."
masked_raw = ma.masked_array([10.0, -999.0, 30.0], mask=[0, 1, 0])
safe_temp = ptn.asanyarray(masked_raw, u.Kelvin)
- assert isinstance(safe_temp.mag, ma.MaskedArray)
- assert safe_temp.mag.mask[1] == True
+ assert isinstance(safe_temp.mag, ma.MaskedArray), "Masked array identity lost during initialization."
+ assert safe_temp.mag.mask[1] == True, "Masked values were corrupted."
A_mat = ptn.array([[4.0, 7.0], [2.0, 6.0]], u.Meter)
A_inv = ptn.linalg.inv(A_mat)
- assert A_inv.dimension == "linear_attenuation"
+ assert A_inv.dimension == "linear_attenuation", "Inverse matrix dimension must be 1/L."
+
identity = A_mat @ A_inv
- assert identity.dimension == "dimensionless"
- assert np.allclose(identity.mag, np.eye(2))
+ assert identity.dimension == "dimensionless", "Matrix multiplied by its inverse must be dimensionless."
+ assert np.allclose(identity.mag, np.eye(2)), "Inverse calculation yielded incorrect magnitudes."
A_3x3 = ptn.array([[1, 2, 3], [0, 1, 4], [5, 6, 0]], u.Meter)
det_vol = ptn.linalg.det(A_3x3)
- assert det_vol.dimension == "volume"
- assert np.isclose(det_vol.mag, 1.0)
+ assert det_vol.dimension == "volume", "Determinant of 3x3 Length matrix must yield Volume."
+ assert np.isclose(det_vol.mag, 1.0), "Determinant magnitude calculation failed."
M_mass = ptn.array([[10.0, 2.0], [3.0, 5.0]], u.Kilogram)
F_force = ptn.array([50.0, 25.0], u.Newton)
accel_x = ptn.linalg.solve(M_mass, F_force)
- assert accel_x.dimension == "acceleration"
- assert isinstance(accel_x, u.MeterPerSecondSquared)
+ assert accel_x.dimension == "acceleration", "Solving M*a = F must yield Acceleration."
+ assert isinstance(accel_x, u.MeterPerSecondSquared), "Incorrect base unit resolution for acceleration."
F_mat = ptn.array([[50.0, 10.0], [10.0, 25.0]], u.Newton)
F_vec = ptn.array([100.0, 50.0], u.Newton)
ratio_x = ptn.linalg.solve(F_mat, F_vec)
- assert ratio_x.dimension == "dimensionless"
+ assert ratio_x.dimension == "dimensionless", "Solving Force/Force must yield dimensionless scalar."
vec_v = ptn.array([3.0, 4.0], u.MeterPerSecond)
mag_v = ptn.linalg.norm(vec_v)
- assert mag_v.dimension == "speed"
- assert np.isclose(mag_v.mag, 5.0)
+ assert mag_v.dimension == "speed", "Vector norm must preserve the original dimension."
+ assert np.isclose(mag_v.mag, 5.0), "L2 Norm calculation failed."
mat_norm = ptn.linalg.norm(M_mass, ord="fro", keepdims=True)
- assert mat_norm.dimension == "mass"
- assert mat_norm.shape == (1, 1)
+ assert mat_norm.dimension == "mass", "Frobenius norm must preserve the original dimension."
+ assert mat_norm.shape == (1, 1), "keepdims argument was ignored in linalg.norm."
+
+ ptn.random.seed(42)
+ rep_1 = ptn.random.uniform(0, 100, size=5, unit=u.Volt)
+ ptn.random.seed(42)
+ rep_2 = ptn.random.uniform(0, 100, size=5, unit=u.Volt)
+ assert np.allclose(rep_1.mag, rep_2.mag), "Isolated RNG seed failed to produce deterministic results."
+ assert rep_1.dimension == "electric_potential", "Unit injection failed in random.uniform."
p_uni = ptn.random.uniform(low=10.5, high=20.5, size=(5, 5), unit=u.Pascal)
- assert p_uni.shape == (5, 5)
- assert p_uni.dimension == "pressure"
- assert np.all((p_uni.mag >= 10.5) & (p_uni.mag <= 20.5))
+ assert p_uni.shape == (5, 5), "Random tensor shape mismatch."
+ assert p_uni.dimension == "pressure", "Dimension mapping failed."
+ assert np.all((p_uni.mag >= 10.5) & (p_uni.mag <= 20.5)), "Uniform distribution bounds violated."
mass_norm = ptn.random.normal(loc=100.0, scale=2.5, size=10, unit="kg")
- assert mass_norm.dimension == "mass"
- assert mass_norm.shape == (10,)
-
- bq_decay = ptn.random.poisson(lam=50.0, size=(100,), unit=u.Becquerel)
- assert bq_decay.dimension == "radioactivity"
- assert bq_decay.mag.dtype in (np.int32, np.int64)
+ assert mass_norm.dimension == "mass", "String alias mapping failed in random.normal."
+ assert mass_norm.shape == (10,), "Output shape mismatch in normal distribution."
t_half = ptn.random.exponential(scale=1.5, size=(3, 3, 3), unit=u.Second)
- assert t_half.dimension == "time"
- assert t_half.ndim == 3
+ assert t_half.dimension == "time", "Dimension mapping failed."
+ assert np.all(t_half.mag >= 0), "Exponential distribution generated impossible negative values."
+
+ bq_decay = ptn.random.poisson(lam=50.0, size=(100,), unit=u.Becquerel)
+ assert bq_decay.dimension == "radioactivity", "Dimension mapping failed."
+ assert np.issubdtype(bq_decay.mag.dtype, np.integer), "Poisson events must be discrete integers."
+
+ spin_states = ptn.random.randint(-1, 2, size=(50,), unit=u.Dimensionless)
+ assert np.issubdtype(spin_states.mag.dtype, np.integer), "Randint failed to produce integers."
+ assert np.all((spin_states.mag >= -1) & (spin_states.mag < 2)), "Randint boundary violation."
+
+ allowed_velocities = [300.0, 400.0, 500.0]
+ gas_particles = ptn.random.choice(allowed_velocities, size=100, unit=u.MeterPerSecond)
+ assert gas_particles.dimension == "speed", "Choice dimension mapping failed."
+ assert np.all(np.isin(gas_particles.mag, allowed_velocities)), "Choice generated illegal states."
+
+ ptn.random.seed(99)
+ ordered_energy = ptn.array(np.arange(100.0), u.Joule)
+ ordered_copy = ordered_energy.mag.copy()
+
+ ptn.random.shuffle(ordered_energy)
+ assert not np.array_equal(ordered_energy.mag, ordered_copy), "Shuffle failed to randomize array."
+ assert np.array_equal(np.sort(ordered_energy.mag), ordered_copy), "Shuffle destroyed array elements."
+ assert ordered_energy.dimension == "energy", "Shuffle stripped physical dimensions."
+
+ original_force = ptn.array([10.0, 20.0, 30.0], u.Newton)
+ permuted_force = ptn.random.permutation(original_force)
+ assert np.array_equal(original_force.mag, [10.0, 20.0, 30.0]), "Permutation mutated original tensor."
+ assert np.array_equal(np.sort(permuted_force.mag), [10.0, 20.0, 30.0]), "Permutation lost elements."
+ assert permuted_force.dimension == "force", "Permutation stripped physical dimensions."
+
+ idx_range = ptn.random.permutation(10)
+ assert idx_range.dimension == "dimensionless", "Integer permutation must return Dimensionless unit."
+ assert set(idx_range.mag) == set(range(10)), "Integer permutation failed to generate valid range."
with pytest.raises(np.linalg.LinAlgError, match="square"):
ptn.linalg.inv(ptn.array([[1, 2, 3], [4, 5, 6]], u.Meter))
+ with pytest.raises(np.linalg.LinAlgError, match="at least two-dimensional"):
+ ptn.linalg.inv(ptn.array([1, 2, 3], u.Meter))
+
mat_db = ptn.array([[30, 30], [30, 30]], u.Decibel)
with pytest.raises(AxiomViolationError, match="You cannot exponentiate"):
ptn.linalg.det(mat_db)
with pytest.raises(ValueError, match="physical unit must be specified"):
ptn.random.uniform(size=5)
-
- with pytest.raises(np.linalg.LinAlgError, match="at least two-dimensional"):
- ptn.linalg.inv(ptn.array([1, 2, 3], u.Meter))
\ No newline at end of file
+
+ with pytest.raises(TypeError, match="must be a Phaethon BaseUnit"):
+ ptn.random.shuffle([1, 2, 3])
\ No newline at end of file