Skip to content

API Reference¤

attractors.Solver dataclass ¤

Data class representing a numerical ODE solver with JIT compilation support.

Attributes:

Name Type Description
func SolverCallable

Original solver function

jitted_func SolverCallable

JIT-compiled solver function

name str

Solver identifier

Source code in src/attractors/solvers/registry.py
@dataclass
class Solver:
    """
    Data class representing a numerical ODE solver with JIT compilation support.

    Attributes:
        func (SolverCallable): Original solver function
        jitted_func (SolverCallable): JIT-compiled solver function
        name (str): Solver identifier
    """

    func: SolverCallable
    jitted_func: SolverCallable
    name: str

    def get_func(self, jitted: bool = True) -> SolverCallable:
        """
        Get solver function.

        Args:
            jitted (bool, optional): Whether to return JIT-compiled version. Defaults to True.

        Returns:
            SolverCallable: Solver function (JIT-compiled or original)
        """
        return self.jitted_func if jitted else self.func

    def __repr__(self) -> str:
        return f"Solver(name={self.name})"

attractors.Solver.get_func(jitted=True) ¤

Get solver function.

Parameters:

Name Type Description Default
jitted bool

Whether to return JIT-compiled version. Defaults to True.

True

Returns:

Name Type Description
SolverCallable SolverCallable

Solver function (JIT-compiled or original)

Source code in src/attractors/solvers/registry.py
def get_func(self, jitted: bool = True) -> SolverCallable:
    """
    Get solver function.

    Args:
        jitted (bool, optional): Whether to return JIT-compiled version. Defaults to True.

    Returns:
        SolverCallable: Solver function (JIT-compiled or original)
    """
    return self.jitted_func if jitted else self.func

attractors.SolverRegistry ¤

Registry for numerical ODE solvers with JIT compilation support.

Each solver must be registered with a unique name and follow the interface
  • Input: (system_func, state, params, dt)
  • Output: next state vector

Solvers are automatically JIT-compiled during registration.

Attributes:

Name Type Description
_solvers dict[str, Solver]

Internal dict mapping solver names to Solver instances

Examples:

>>> @SolverRegistry.register("rk4")
>>> def rk4(system_func, state, params, dt):
...     # RK4 implementation
...     return next_state
>>> solver = SolverRegistry.get("rk4")
Source code in src/attractors/solvers/registry.py
class SolverRegistry:
    """
    Registry for numerical ODE solvers with JIT compilation support.

    Each solver must be registered with a unique name and follow the interface:
        - Input: (system_func, state, params, dt)
        - Output: next state vector

    Solvers are automatically JIT-compiled during registration.

    Attributes:
        _solvers: Internal dict mapping solver names to Solver instances

    Examples:
        >>> @SolverRegistry.register("rk4")
        >>> def rk4(system_func, state, params, dt):
        ...     # RK4 implementation
        ...     return next_state

        >>> solver = SolverRegistry.get("rk4")
    """

    _solvers: ClassVar[dict[str, Solver]] = {}

    @classmethod
    def register(cls, name: str) -> Callable[[F], F]:
        """Register a solver function in the SolverRegistry.

        Decorator that registers a solver function and creates a JIT-compiled version.
        The registered solver must take (system_func, state, params, dt) arguments and
        return the next state vector.

        Args:
            name (str): Unique identifier for the solver

        Returns:
            Callable[[F], F]: Decorator function that registers and JIT-compiles the solver

        Raises:
            TypeError: If name is not a string or decorated object is not callable
            ValueError: If solver name is already registered

        Examples:
            >>> @SolverRegistry.register("solver_name")
            >>> def custom_solver(system_func, state, params, dt):
            ...     # Solver implementation
            ...     return next_state
        """

        def decorator(f: F) -> F:
            if not isinstance(name, str):
                raise TypeError("Name must be string")
            if not callable(f):
                raise TypeError("Must register callable")
            if name in cls._solvers:
                msg = f"Solver {name} already registered"
                raise ValueError(msg)

            jitted_f = njit(f)
            cls._solvers[name] = Solver(f, jitted_f, name)
            return f

        logger.debug("Registered solver: %s", name)
        return decorator

    @classmethod
    def get(cls, name: str) -> Solver:
        """
        Retrieve a registered solver by name

        Args:
            name (str): Name of solver to retrieve

        Raises:
            KeyError: If solver name not found

        Returns:
            Solver: Registered Solver instance
        """
        if name not in cls._solvers:
            msg = f"Solver {name} not found"
            raise KeyError(msg)
        logger.debug("Getting solver: %s", name)
        return cls._solvers[name]

    @classmethod
    def list_solvers(cls) -> list[str]:
        """
        Get list of all registered solver names.

        Returns:
            list[str]: List of solver names
        """
        return list(cls._solvers.keys())

    @staticmethod
    def is_jitted(func: Callable[..., Any]) -> bool:
        """
        Check if a function is JIT-compiled.

        Args:
            func (Callable[..., Any]): Function to check

        Returns:
            bool: True if function is JIT-compiled
        """
        return isinstance(func, Dispatcher)

attractors.SolverRegistry.register(name) classmethod ¤

Register a solver function in the SolverRegistry.

Decorator that registers a solver function and creates a JIT-compiled version. The registered solver must take (system_func, state, params, dt) arguments and return the next state vector.

Parameters:

Name Type Description Default
name str

Unique identifier for the solver

required

Returns:

Type Description
Callable[[F], F]

Callable[[F], F]: Decorator function that registers and JIT-compiles the solver

Raises:

Type Description
TypeError

If name is not a string or decorated object is not callable

ValueError

If solver name is already registered

Examples:

>>> @SolverRegistry.register("solver_name")
>>> def custom_solver(system_func, state, params, dt):
...     # Solver implementation
...     return next_state
Source code in src/attractors/solvers/registry.py
@classmethod
def register(cls, name: str) -> Callable[[F], F]:
    """Register a solver function in the SolverRegistry.

    Decorator that registers a solver function and creates a JIT-compiled version.
    The registered solver must take (system_func, state, params, dt) arguments and
    return the next state vector.

    Args:
        name (str): Unique identifier for the solver

    Returns:
        Callable[[F], F]: Decorator function that registers and JIT-compiles the solver

    Raises:
        TypeError: If name is not a string or decorated object is not callable
        ValueError: If solver name is already registered

    Examples:
        >>> @SolverRegistry.register("solver_name")
        >>> def custom_solver(system_func, state, params, dt):
        ...     # Solver implementation
        ...     return next_state
    """

    def decorator(f: F) -> F:
        if not isinstance(name, str):
            raise TypeError("Name must be string")
        if not callable(f):
            raise TypeError("Must register callable")
        if name in cls._solvers:
            msg = f"Solver {name} already registered"
            raise ValueError(msg)

        jitted_f = njit(f)
        cls._solvers[name] = Solver(f, jitted_f, name)
        return f

    logger.debug("Registered solver: %s", name)
    return decorator

attractors.SolverRegistry.get(name) classmethod ¤

Retrieve a registered solver by name

Parameters:

Name Type Description Default
name str

Name of solver to retrieve

required

Raises:

Type Description
KeyError

If solver name not found

Returns:

Name Type Description
Solver Solver

Registered Solver instance

Source code in src/attractors/solvers/registry.py
@classmethod
def get(cls, name: str) -> Solver:
    """
    Retrieve a registered solver by name

    Args:
        name (str): Name of solver to retrieve

    Raises:
        KeyError: If solver name not found

    Returns:
        Solver: Registered Solver instance
    """
    if name not in cls._solvers:
        msg = f"Solver {name} not found"
        raise KeyError(msg)
    logger.debug("Getting solver: %s", name)
    return cls._solvers[name]

attractors.SolverRegistry.list_solvers() classmethod ¤

Get list of all registered solver names.

Returns:

Type Description
list[str]

list[str]: List of solver names

Source code in src/attractors/solvers/registry.py
@classmethod
def list_solvers(cls) -> list[str]:
    """
    Get list of all registered solver names.

    Returns:
        list[str]: List of solver names
    """
    return list(cls._solvers.keys())

attractors.SolverRegistry.is_jitted(func) staticmethod ¤

Check if a function is JIT-compiled.

Parameters:

Name Type Description Default
func Callable[..., Any]

Function to check

required

Returns:

Name Type Description
bool bool

True if function is JIT-compiled

Source code in src/attractors/solvers/registry.py
@staticmethod
def is_jitted(func: Callable[..., Any]) -> bool:
    """
    Check if a function is JIT-compiled.

    Args:
        func (Callable[..., Any]): Function to check

    Returns:
        bool: True if function is JIT-compiled
    """
    return isinstance(func, Dispatcher)

attractors.System dataclass ¤

Data class representing a dynamical system with JIT compilation support.

Attributes:

Name Type Description
func SystemCallable

Original system function

jitted_func SystemCallable

JIT-compiled system function

name str

System identifier

params Vector

System parameters vector

param_names list[str]

List of parameter names

reference str

Academic reference

init_coord Vector

Initial state vector

plot_lims PlotLimits | None

Optional plotting limits

Source code in src/attractors/systems/registry.py
@dataclass
class System:
    """
    Data class representing a dynamical system with JIT compilation support.

    Attributes:
        func (SystemCallable): Original system function
        jitted_func (SystemCallable): JIT-compiled system function
        name (str): System identifier
        params (Vector): System parameters vector
        param_names (list[str]): List of parameter names
        reference (str): Academic reference
        init_coord (Vector): Initial state vector
        plot_lims (PlotLimits | None): Optional plotting limits
    """

    func: SystemCallable
    jitted_func: SystemCallable
    name: str
    params: Vector
    param_names: list[str]
    reference: str
    init_coord: Vector
    plot_lims: PlotLimits | None = None

    def set_params(self, params: Vector) -> None:
        """
        Set system parameters.

        Args:
            params (Vector): New parameter vector

        Raises:
            ValueError: If parameter count doesn't match expected number of parameters
        """
        if len(params) != len(self.param_names):
            msg = f"Expected {len(self.param_names)} parameters"
            raise ValueError(msg)
        logger.debug("Setting parameters: %s for system: %s", params, self.name)
        self.params = params

    def set_init_coord(self, coord: Vector) -> None:
        """
        Set initial state coordinates.

        Args:
            coord (Vector): Initial state vector (must have length 3)

        Raises:
            ValueError: If coordinate vector length is not 3
        """
        if len(coord) != 3:
            raise ValueError("State vector must have length 3")
        logger.debug("Setting initial coord: %s for system: %s", coord, self.name)
        self.init_coord = coord

    def get_func(self, jitted: bool = True) -> SystemCallable:
        """
        Get system function.

        Args:
            jitted (bool, optional): Whether to return JIT-compiled version. Defaults to True.

        Returns:
            SystemCallable: System function (JIT-compiled or original)
        """
        return self.jitted_func if jitted else self.func

attractors.System.set_params(params) ¤

Set system parameters.

Parameters:

Name Type Description Default
params Vector

New parameter vector

required

Raises:

Type Description
ValueError

If parameter count doesn't match expected number of parameters

Source code in src/attractors/systems/registry.py
def set_params(self, params: Vector) -> None:
    """
    Set system parameters.

    Args:
        params (Vector): New parameter vector

    Raises:
        ValueError: If parameter count doesn't match expected number of parameters
    """
    if len(params) != len(self.param_names):
        msg = f"Expected {len(self.param_names)} parameters"
        raise ValueError(msg)
    logger.debug("Setting parameters: %s for system: %s", params, self.name)
    self.params = params

attractors.System.set_init_coord(coord) ¤

Set initial state coordinates.

Parameters:

Name Type Description Default
coord Vector

Initial state vector (must have length 3)

required

Raises:

Type Description
ValueError

If coordinate vector length is not 3

Source code in src/attractors/systems/registry.py
def set_init_coord(self, coord: Vector) -> None:
    """
    Set initial state coordinates.

    Args:
        coord (Vector): Initial state vector (must have length 3)

    Raises:
        ValueError: If coordinate vector length is not 3
    """
    if len(coord) != 3:
        raise ValueError("State vector must have length 3")
    logger.debug("Setting initial coord: %s for system: %s", coord, self.name)
    self.init_coord = coord

attractors.System.get_func(jitted=True) ¤

Get system function.

Parameters:

Name Type Description Default
jitted bool

Whether to return JIT-compiled version. Defaults to True.

True

Returns:

Name Type Description
SystemCallable SystemCallable

System function (JIT-compiled or original)

Source code in src/attractors/systems/registry.py
def get_func(self, jitted: bool = True) -> SystemCallable:
    """
    Get system function.

    Args:
        jitted (bool, optional): Whether to return JIT-compiled version. Defaults to True.

    Returns:
        SystemCallable: System function (JIT-compiled or original)
    """
    return self.jitted_func if jitted else self.func

attractors.SystemRegistry ¤

Registry for dynamical systems with JIT compilation support.

Each system must be registered with
  • Unique name
  • Default parameters and their names
  • Initial coordinates
  • Optional plotting limits and academic reference

Systems are automatically JIT-compiled during registration.

Attributes:

Name Type Description
_systems dict[str, System]

Internal dict mapping system names to System instances

Examples:

>>> @SystemRegistry.register(
...     "lorenz",
...     default_params=np.array([10.0, 28.0, 8 / 3]),
...     param_names=["sigma", "rho", "beta"],
...     init_coord=np.array([0.0, 1.0, 0.0]),
... )
... def lorenz(state: Vector, params: Vector) -> Vector:
...     x, y, z = state
...     sigma, rho, beta = params
...     return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z])
Source code in src/attractors/systems/registry.py
class SystemRegistry:
    """
    Registry for dynamical systems with JIT compilation support.

    Each system must be registered with:
        - Unique name
        - Default parameters and their names
        - Initial coordinates
        - Optional plotting limits and academic reference

    Systems are automatically JIT-compiled during registration.

    Attributes:
        _systems: Internal dict mapping system names to System instances

    Examples:
        >>> @SystemRegistry.register(
        ...     "lorenz",
        ...     default_params=np.array([10.0, 28.0, 8 / 3]),
        ...     param_names=["sigma", "rho", "beta"],
        ...     init_coord=np.array([0.0, 1.0, 0.0]),
        ... )
        ... def lorenz(state: Vector, params: Vector) -> Vector:
        ...     x, y, z = state
        ...     sigma, rho, beta = params
        ...     return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z])
    """

    _systems: ClassVar[dict[str, System]] = {}

    @classmethod
    def register(
        cls,
        name: str,
        *,
        default_params: Vector,
        param_names: list[str],
        reference: str = "",
        init_coord: Vector,
        plot_lims: PlotLimits | None = None,
    ) -> Callable[[F], F]:
        """
        Register a system function in the SystemRegistry.

        Decorator that registers a system function and creates a JIT-compiled version.
        The registered system must take (state, params) Vector type arguments and
        return the next state vector.

        Args:
            name (str): Unique identifier for the system
            default_params (Vector): Default parameter values
            param_names (list[str]): Names of parameters
            reference (str, optional): Academic reference. Defaults to "".
            init_coord (Vector): Initial state vector
            plot_lims (PlotLimits | None, optional): Plotting limits. Defaults to None.

        Returns:
            Callable[[F], F]: Decorator function that registers and JIT-compiles the system

        Raises:
            TypeError: If name is not a string or decorated object is not callable
            ValueError: If system name is already registered

        Examples:
            >>> @SystemRegistry.register("lorenz")
            ... def lorenz(state: Vector, params: Vector) -> Vector:
            ...     x, y, z = state
            ...     sigma, rho, beta = params
            ...     return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z])
        """

        def decorator(f: F) -> F:
            if not isinstance(name, str):
                raise TypeError("Name must be string")
            if not callable(f):
                raise TypeError("Must register callable")
            if name in cls._systems:
                msg = f"System {name} already registered"
                raise ValueError(msg)

            jitted_f = f if cls.is_jitted(f) else njit(f)
            cls._systems[name] = System(
                func=f,
                jitted_func=jitted_f,
                name=name,
                params=default_params,
                param_names=param_names,
                reference=reference,
                init_coord=init_coord,
                plot_lims=plot_lims,
            )
            return f

        logger.debug("Registered system: %s", name)
        return decorator

    @classmethod
    def get(cls, name: str) -> System:
        """
        Get registered system by name.

        Args:
            name (str): Name of system to retrieve

        Returns:
            System: Registered System instance

        Raises:
            KeyError: If system name is not found
        """
        if name not in cls._systems:
            msg = f"System {name} not found"
            raise KeyError(msg)
        logger.debug("Getting system: %s", name)
        return cls._systems[name]

    @classmethod
    def list_systems(cls) -> list[str]:
        """
        Get list of all registered system names.

        Returns:
            list[str]: List of registered system names
        """
        return list(cls._systems.keys())

    @staticmethod
    def is_jitted(func: Callable[..., Any]) -> bool:
        """
        Check if a function is JIT-compiled.

        Args:
            func (Callable[..., Any]): Function to check

        Returns:
            bool: True if function is JIT-compiled
        """
        return isinstance(func, Dispatcher)

attractors.SystemRegistry.register(name, *, default_params, param_names, reference='', init_coord, plot_lims=None) classmethod ¤

Register a system function in the SystemRegistry.

Decorator that registers a system function and creates a JIT-compiled version. The registered system must take (state, params) Vector type arguments and return the next state vector.

Parameters:

Name Type Description Default
name str

Unique identifier for the system

required
default_params Vector

Default parameter values

required
param_names list[str]

Names of parameters

required
reference str

Academic reference. Defaults to "".

''
init_coord Vector

Initial state vector

required
plot_lims PlotLimits | None

Plotting limits. Defaults to None.

None

Returns:

Type Description
Callable[[F], F]

Callable[[F], F]: Decorator function that registers and JIT-compiles the system

Raises:

Type Description
TypeError

If name is not a string or decorated object is not callable

ValueError

If system name is already registered

Examples:

>>> @SystemRegistry.register("lorenz")
... def lorenz(state: Vector, params: Vector) -> Vector:
...     x, y, z = state
...     sigma, rho, beta = params
...     return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z])
Source code in src/attractors/systems/registry.py
@classmethod
def register(
    cls,
    name: str,
    *,
    default_params: Vector,
    param_names: list[str],
    reference: str = "",
    init_coord: Vector,
    plot_lims: PlotLimits | None = None,
) -> Callable[[F], F]:
    """
    Register a system function in the SystemRegistry.

    Decorator that registers a system function and creates a JIT-compiled version.
    The registered system must take (state, params) Vector type arguments and
    return the next state vector.

    Args:
        name (str): Unique identifier for the system
        default_params (Vector): Default parameter values
        param_names (list[str]): Names of parameters
        reference (str, optional): Academic reference. Defaults to "".
        init_coord (Vector): Initial state vector
        plot_lims (PlotLimits | None, optional): Plotting limits. Defaults to None.

    Returns:
        Callable[[F], F]: Decorator function that registers and JIT-compiles the system

    Raises:
        TypeError: If name is not a string or decorated object is not callable
        ValueError: If system name is already registered

    Examples:
        >>> @SystemRegistry.register("lorenz")
        ... def lorenz(state: Vector, params: Vector) -> Vector:
        ...     x, y, z = state
        ...     sigma, rho, beta = params
        ...     return np.array([sigma * (y - x), x * (rho - z) - y, x * y - beta * z])
    """

    def decorator(f: F) -> F:
        if not isinstance(name, str):
            raise TypeError("Name must be string")
        if not callable(f):
            raise TypeError("Must register callable")
        if name in cls._systems:
            msg = f"System {name} already registered"
            raise ValueError(msg)

        jitted_f = f if cls.is_jitted(f) else njit(f)
        cls._systems[name] = System(
            func=f,
            jitted_func=jitted_f,
            name=name,
            params=default_params,
            param_names=param_names,
            reference=reference,
            init_coord=init_coord,
            plot_lims=plot_lims,
        )
        return f

    logger.debug("Registered system: %s", name)
    return decorator

attractors.SystemRegistry.get(name) classmethod ¤

Get registered system by name.

Parameters:

Name Type Description Default
name str

Name of system to retrieve

required

Returns:

Name Type Description
System System

Registered System instance

Raises:

Type Description
KeyError

If system name is not found

Source code in src/attractors/systems/registry.py
@classmethod
def get(cls, name: str) -> System:
    """
    Get registered system by name.

    Args:
        name (str): Name of system to retrieve

    Returns:
        System: Registered System instance

    Raises:
        KeyError: If system name is not found
    """
    if name not in cls._systems:
        msg = f"System {name} not found"
        raise KeyError(msg)
    logger.debug("Getting system: %s", name)
    return cls._systems[name]

attractors.SystemRegistry.list_systems() classmethod ¤

Get list of all registered system names.

Returns:

Type Description
list[str]

list[str]: List of registered system names

Source code in src/attractors/systems/registry.py
@classmethod
def list_systems(cls) -> list[str]:
    """
    Get list of all registered system names.

    Returns:
        list[str]: List of registered system names
    """
    return list(cls._systems.keys())

attractors.SystemRegistry.is_jitted(func) staticmethod ¤

Check if a function is JIT-compiled.

Parameters:

Name Type Description Default
func Callable[..., Any]

Function to check

required

Returns:

Name Type Description
bool bool

True if function is JIT-compiled

Source code in src/attractors/systems/registry.py
@staticmethod
def is_jitted(func: Callable[..., Any]) -> bool:
    """
    Check if a function is JIT-compiled.

    Args:
        func (Callable[..., Any]): Function to check

    Returns:
        bool: True if function is JIT-compiled
    """
    return isinstance(func, Dispatcher)

attractors.ThemeManager ¤

Manager for loading and accessing visualization themes.

Themes can be loaded from JSON files, added/removed programmatically, and accessed by name or randomly. A default theme is always available.

Examples:

>>> ThemeManager.load("themes.json")
>>> theme = ThemeManager.get("dark")
>>> ThemeManager.set_default("light")
>>> random_theme = ThemeManager.random()
Source code in src/attractors/themes/manager.py
class ThemeManager:
    """
    Manager for loading and accessing visualization themes.

    Themes can be loaded from JSON files, added/removed programmatically,
    and accessed by name or randomly. A default theme is always available.

    Examples:
        >>> ThemeManager.load("themes.json")
        >>> theme = ThemeManager.get("dark")
        >>> ThemeManager.set_default("light")
        >>> random_theme = ThemeManager.random()
    """

    _themes: ClassVar[dict[str, Theme]] = {}
    _default_theme: ClassVar[str] = "ayu"

    @classmethod
    def load(cls, path: str | Path) -> None:
        """
        Load themes from JSON file.

        Args:
            path (str | Path): Path to JSON theme file
        """
        with open(path) as f:
            theme_data = json.load(f)

        for name, data in theme_data.items():
            cls._themes[name] = Theme(
                name=name,
                background=data["background"],
                colors=data["colors"],
                foreground=data.get("foreground", "#FFFFFF"),
            )

    @classmethod
    def get(cls, name: str | None = None) -> Theme:
        """
        Get theme by name or default.

        Args:
            name (str | None, optional): Theme name. Uses default if None. Defaults to None.

        Returns:
            Theme: Requested theme instance

        Raises:
            KeyError: If theme name not found
        """
        name = name or cls._default_theme
        if name not in cls._themes:
            msg = f"Theme '{name}' not found"
            raise KeyError(msg)
        return cls._themes[name]

    @classmethod
    def list_themes(cls) -> list[str]:
        """
        List available themes.

        Returns:
            list[str]: List of theme names
        """
        return list(cls._themes.keys())

    @classmethod
    def set_default(cls, name: str) -> None:
        """
        Set default theme.

        Args:
            name (str): Name of theme to set as default

        Raises:
            KeyError: If theme name not found
        """
        if name not in cls._themes:
            msg = f"Theme '{name}' not found"
            raise KeyError(msg)
        cls._default_theme = name

    @classmethod
    def add(cls, theme: Theme) -> None:
        """
        Add new theme.

        Args:
            theme (Theme): Theme instance to add
        """
        cls._themes[theme.name] = theme

    @classmethod
    def remove(cls, name: str) -> None:
        """
        Remove theme.

        Args:
            name (str): Name of theme to remove

        Raises:
            ValueError: If attempting to remove default theme
        """
        if name == cls._default_theme:
            raise ValueError("Cannot remove default theme")
        cls._themes.pop(name)

    @classmethod
    def random(cls) -> Theme:
        """
        Get random theme.

        Returns:
            Theme: Randomly selected theme instance
        """
        return random.choice(list(cls._themes.values()))  # noqa: S311

attractors.ThemeManager.load(path) classmethod ¤

Load themes from JSON file.

Parameters:

Name Type Description Default
path str | Path

Path to JSON theme file

required
Source code in src/attractors/themes/manager.py
@classmethod
def load(cls, path: str | Path) -> None:
    """
    Load themes from JSON file.

    Args:
        path (str | Path): Path to JSON theme file
    """
    with open(path) as f:
        theme_data = json.load(f)

    for name, data in theme_data.items():
        cls._themes[name] = Theme(
            name=name,
            background=data["background"],
            colors=data["colors"],
            foreground=data.get("foreground", "#FFFFFF"),
        )

attractors.ThemeManager.get(name=None) classmethod ¤

Get theme by name or default.

Parameters:

Name Type Description Default
name str | None

Theme name. Uses default if None. Defaults to None.

None

Returns:

Name Type Description
Theme Theme

Requested theme instance

Raises:

Type Description
KeyError

If theme name not found

Source code in src/attractors/themes/manager.py
@classmethod
def get(cls, name: str | None = None) -> Theme:
    """
    Get theme by name or default.

    Args:
        name (str | None, optional): Theme name. Uses default if None. Defaults to None.

    Returns:
        Theme: Requested theme instance

    Raises:
        KeyError: If theme name not found
    """
    name = name or cls._default_theme
    if name not in cls._themes:
        msg = f"Theme '{name}' not found"
        raise KeyError(msg)
    return cls._themes[name]

attractors.ThemeManager.list_themes() classmethod ¤

List available themes.

Returns:

Type Description
list[str]

list[str]: List of theme names

Source code in src/attractors/themes/manager.py
@classmethod
def list_themes(cls) -> list[str]:
    """
    List available themes.

    Returns:
        list[str]: List of theme names
    """
    return list(cls._themes.keys())

attractors.ThemeManager.set_default(name) classmethod ¤

Set default theme.

Parameters:

Name Type Description Default
name str

Name of theme to set as default

required

Raises:

Type Description
KeyError

If theme name not found

Source code in src/attractors/themes/manager.py
@classmethod
def set_default(cls, name: str) -> None:
    """
    Set default theme.

    Args:
        name (str): Name of theme to set as default

    Raises:
        KeyError: If theme name not found
    """
    if name not in cls._themes:
        msg = f"Theme '{name}' not found"
        raise KeyError(msg)
    cls._default_theme = name

attractors.ThemeManager.add(theme) classmethod ¤

Add new theme.

Parameters:

Name Type Description Default
theme Theme

Theme instance to add

required
Source code in src/attractors/themes/manager.py
@classmethod
def add(cls, theme: Theme) -> None:
    """
    Add new theme.

    Args:
        theme (Theme): Theme instance to add
    """
    cls._themes[theme.name] = theme

attractors.ThemeManager.remove(name) classmethod ¤

Remove theme.

Parameters:

Name Type Description Default
name str

Name of theme to remove

required

Raises:

Type Description
ValueError

If attempting to remove default theme

Source code in src/attractors/themes/manager.py
@classmethod
def remove(cls, name: str) -> None:
    """
    Remove theme.

    Args:
        name (str): Name of theme to remove

    Raises:
        ValueError: If attempting to remove default theme
    """
    if name == cls._default_theme:
        raise ValueError("Cannot remove default theme")
    cls._themes.pop(name)

attractors.ThemeManager.random() classmethod ¤

Get random theme.

Returns:

Name Type Description
Theme Theme

Randomly selected theme instance

Source code in src/attractors/themes/manager.py
@classmethod
def random(cls) -> Theme:
    """
    Get random theme.

    Returns:
        Theme: Randomly selected theme instance
    """
    return random.choice(list(cls._themes.values()))  # noqa: S311

attractors.Theme dataclass ¤

Immutable theme data class for visualization styling.

Attributes:

Name Type Description
name str

Theme identifier

background str

Background color in hex format

foreground str

Foreground color in hex format

colors str | list[str]

Either matplotlib colormap name or list of hex colors

Source code in src/attractors/themes/theme.py
@dataclass(frozen=True)
class Theme:
    """
    Immutable theme data class for visualization styling.

    Attributes:
        name (str): Theme identifier
        background (str): Background color in hex format
        foreground (str): Foreground color in hex format
        colors (str | list[str]): Either matplotlib colormap name or list of hex colors
    """

    name: str
    background: str
    foreground: str
    colors: str | list[str]

    @property
    def colormap(self) -> matplotlib.colors.Colormap:
        """
        Get matplotlib colormap for theme colors.

        Returns:
            matplotlib.colors.Colormap: Generated colormap from theme colors
        """
        if isinstance(self.colors, str):
            return matplotlib.colormaps[self.colors]
        rgb_colors = [
            tuple(int(c.lstrip("#")[i : i + 2], 16) / 255 for i in (0, 2, 4)) for c in self.colors
        ]
        return matplotlib.colors.LinearSegmentedColormap.from_list(self.name, rgb_colors)

attractors.Theme.colormap property ¤

Get matplotlib colormap for theme colors.

Returns:

Type Description
Colormap

matplotlib.colors.Colormap: Generated colormap from theme colors

attractors.AnimatedPlotter ¤

Bases: BasePlotter

Source code in src/attractors/visualizers/animate.py
class AnimatedPlotter(BasePlotter):
    def visualize_impl(
        self,
        trajectory: Vector,
        **kwargs: AnimatedVisualizeKwargs,
    ) -> "AnimatedPlotter":
        """
        Create an animation showing trajectory evolution over time with
        colored segments.

        Args:
            trajectory (Vector): Trajectory points to visualize
            **kwargs (AnimatedVisualizeKwargs): Animation parameters. Refer to attributes of AnimatedVisualizeKwargs.
        Returns:
            AnimatedPlotter: Self reference for method chaining
        """  # noqa: E501
        self.speed = typing.cast(int, kwargs.get("speed", 20))
        self.line_kwargs = typing.cast(dict[str, Any], kwargs.get("line_kwargs", {})) or {}
        self.anim_kwargs = typing.cast(dict[str, Any], kwargs.get("anim_kwargs", {})) or {}
        interval = typing.cast(int, kwargs.get("interval", 1))
        rotate_view = typing.cast(Callable[[Axes3D], None] | None, kwargs.get("rotate_view"))

        self._setup_plot()
        segment_length = len(trajectory) // self.num_segments
        colors = self.theme.colormap(np.linspace(0, 1, self.num_segments))
        lines: list[Line3D] = [
            self.ax.plot([], [], [], "-", c=color, **self.line_kwargs)[0] for color in colors
        ]

        def init() -> list[Line3D]:
            for line in lines:
                line.set_data_3d([], [], [])
            return lines

        def update(frame: int) -> Sequence[Line3D]:
            frame = frame * self.speed
            active_segments = min(frame // segment_length + 1, self.num_segments)

            for i in range(self.num_segments):
                if i < active_segments:
                    start_idx = i * segment_length
                    end_idx = min(start_idx + segment_length, frame + 1)
                    lines[i].set_data_3d(
                        trajectory[start_idx:end_idx, 0],
                        trajectory[start_idx:end_idx, 1],
                        trajectory[start_idx:end_idx, 2],
                    )

                ax: Axes3D = self.ax
                if rotate_view is not None:
                    rotate_view(ax)

            return lines

        self.anim = animation.FuncAnimation(
            self.fig,
            update,
            init_func=init,
            frames=len(trajectory) // self.speed,
            interval=interval,
            **self.anim_kwargs,
        )

        return self

attractors.AnimatedPlotter.visualize_impl(trajectory, **kwargs) ¤

Create an animation showing trajectory evolution over time with colored segments.

Parameters:

Name Type Description Default
trajectory Vector

Trajectory points to visualize

required
**kwargs AnimatedVisualizeKwargs

Animation parameters. Refer to attributes of AnimatedVisualizeKwargs.

{}

Returns: AnimatedPlotter: Self reference for method chaining

Source code in src/attractors/visualizers/animate.py
def visualize_impl(
    self,
    trajectory: Vector,
    **kwargs: AnimatedVisualizeKwargs,
) -> "AnimatedPlotter":
    """
    Create an animation showing trajectory evolution over time with
    colored segments.

    Args:
        trajectory (Vector): Trajectory points to visualize
        **kwargs (AnimatedVisualizeKwargs): Animation parameters. Refer to attributes of AnimatedVisualizeKwargs.
    Returns:
        AnimatedPlotter: Self reference for method chaining
    """  # noqa: E501
    self.speed = typing.cast(int, kwargs.get("speed", 20))
    self.line_kwargs = typing.cast(dict[str, Any], kwargs.get("line_kwargs", {})) or {}
    self.anim_kwargs = typing.cast(dict[str, Any], kwargs.get("anim_kwargs", {})) or {}
    interval = typing.cast(int, kwargs.get("interval", 1))
    rotate_view = typing.cast(Callable[[Axes3D], None] | None, kwargs.get("rotate_view"))

    self._setup_plot()
    segment_length = len(trajectory) // self.num_segments
    colors = self.theme.colormap(np.linspace(0, 1, self.num_segments))
    lines: list[Line3D] = [
        self.ax.plot([], [], [], "-", c=color, **self.line_kwargs)[0] for color in colors
    ]

    def init() -> list[Line3D]:
        for line in lines:
            line.set_data_3d([], [], [])
        return lines

    def update(frame: int) -> Sequence[Line3D]:
        frame = frame * self.speed
        active_segments = min(frame // segment_length + 1, self.num_segments)

        for i in range(self.num_segments):
            if i < active_segments:
                start_idx = i * segment_length
                end_idx = min(start_idx + segment_length, frame + 1)
                lines[i].set_data_3d(
                    trajectory[start_idx:end_idx, 0],
                    trajectory[start_idx:end_idx, 1],
                    trajectory[start_idx:end_idx, 2],
                )

            ax: Axes3D = self.ax
            if rotate_view is not None:
                rotate_view(ax)

        return lines

    self.anim = animation.FuncAnimation(
        self.fig,
        update,
        init_func=init,
        frames=len(trajectory) // self.speed,
        interval=interval,
        **self.anim_kwargs,
    )

    return self

attractors.AnimatedVisualizeKwargs ¤

Bases: TypedDict

Type definition for animation visualization parameters.

Attributes:

Name Type Description
speed int

Speed multiplier for animation

interval int

Animation interval in milliseconds

rotate_view Callable[[Axes3D], None]

Function to rotate view in each frame

line_kwargs dict[str, Any]

Additional arguments for matplotlib line plots

anim_kwargs dict[str, Any]

Additional arguments for matplotlib animation

Source code in src/attractors/visualizers/animate.py
class AnimatedVisualizeKwargs(TypedDict, total=False):
    """
    Type definition for animation visualization parameters.

    Attributes:
        speed (int): Speed multiplier for animation
        interval (int): Animation interval in milliseconds
        rotate_view (Callable[[Axes3D], None]): Function to rotate view in each frame
        line_kwargs (dict[str, Any]): Additional arguments for matplotlib line plots
        anim_kwargs (dict[str, Any]): Additional arguments for matplotlib animation
    """

    speed: int
    interval: int
    rotate_view: Callable[[Axes3D], None] | None
    line_kwargs: dict[str, Any] | None
    anim_kwargs: dict[str, Any] | None

attractors.BasePlotter ¤

Bases: ABC

Abstract base class for visualization of dynamical systems.

An abstract base class that handles the common functionality for plotting and visualizing dynamical systems trajectories, including color mapping and plot setup.

Attributes:

Name Type Description
VALID_COLOR_OPTIONS tuple[str, ...]

Valid color mapping options ("time", "x", "y", "z", "velocity")

Source code in src/attractors/visualizers/base.py
class BasePlotter(ABC):
    """
    Abstract base class for visualization of dynamical systems.

    An abstract base class that handles the common functionality for plotting and visualizing
    dynamical systems trajectories, including color mapping and plot setup.

    Attributes:
        VALID_COLOR_OPTIONS (tuple[str, ...]): Valid color mapping options ("time", "x", "y", "z", "velocity")
    """  # noqa: E501

    VALID_COLOR_OPTIONS = ("time", "x", "y", "z", "velocity")

    def __init__(
        self,
        system: System,
        theme: Theme,
        num_segments: int = 50,
        color_by: str | ColorMapper = "time",
        color_cycles: float = 1.0,
        fig_kwargs: dict[str, Any] | None = None,
    ) -> None:
        """
        Initialize the plotter.

        Args:
            system (System): Dynamical system to visualize
            theme (Theme): Visual theme for plots
            num_segments (int): Number of segments for visualization
            color_by (str | ColorMapper): Color mapping strategy ("time", "x", "y", "z", "velocity") or ColorMapper instance
            color_cycles (float): Number of color cycles through the palette
            fig_kwargs (dict[str, Any] | None): Additional arguments for matplotlib figure

        Raises:
            ValueError: If color_by is not a valid option or ColorMapper instance
        """  # noqa: E501
        self.color_mapper: ColorMapper
        self.system = system
        self.theme = theme
        self.num_segments = num_segments
        self.color_cycles = color_cycles
        self.fig_kwargs = fig_kwargs or {}

        if isinstance(color_by, str):
            if color_by not in self.VALID_COLOR_OPTIONS:
                msg = f"color_by must be one of {self.VALID_COLOR_OPTIONS} or a ColorMapper"
                raise ValueError(msg)

            if color_by == "time":
                self.color_mapper = TimeColorMapper()
            elif color_by == "velocity":
                self.color_mapper = VelocityColorMapper()
            else:  # x, y, z
                self.color_mapper = CoordinateColorMapper({"x": 0, "y": 1, "z": 2}[color_by])
        else:
            self.color_mapper = color_by

    def _validate_inputs(
        self, num_segments: int, color_cycles: float, color_by: str | Callable[[Vector], Vector]
    ) -> None:
        """
        Validate initialization parameters.

        Args:
            num_segments (int): Number of segments for visualization
            color_cycles (float): Number of color cycles through the palette
            color_by (str | Callable[[Vector], Vector]): Color mapping strategy

        Raises:
            ValueError: If parameters are invalid
        """
        if num_segments <= 0:
            raise ValueError("num_segments must be positive")
        if color_cycles <= 0:
            raise ValueError("color_cycles must be positive")
        if isinstance(color_by, str) and color_by not in self.VALID_COLOR_OPTIONS:
            error_message = f"color_by must be one of {self.VALID_COLOR_OPTIONS} or a callable"
            raise ValueError(error_message)

    def _setup_plot(self) -> None:
        """Configure matplotlib figure and 3D axes with theme settings."""
        self.fig = plt.figure(facecolor=self.theme.background, **self.fig_kwargs)
        self.ax = self.fig.add_subplot(111, projection="3d")
        self.ax.set_facecolor(self.theme.background)
        self.ax.set_axis_off()

        for axis in ["x", "y", "z"]:
            if self.system.plot_lims:
                getattr(self.ax, f"set_{axis}lim")(*self.system.plot_lims[f"{axis}lim"])  # type: ignore[literal-required]

    def _get_color_values(self, trajectory: Vector) -> Vector:
        """
        Map trajectory points to color values.

        Args:
            trajectory (Vector): Trajectory points to map to colors

        Returns:
            Color values for each trajectory point

        Raises:
            ValueError: If color mapping fails
        """
        try:
            values = self.color_mapper.map(trajectory)
            return (values * self.color_cycles) % 1.0
        except Exception as e:
            msg = f"Error in color mapping: {e!s}"
            raise ValueError(msg) from e

    def visualize(
        self,
        trajectory: Vector,
        compression: float = 0.0,
        compression_method: CompressionMethod = CompressionMethod.VELOCITY,
        **kwargs: Any,
    ) -> "BasePlotter":
        """
        Process and visualize trajectory data.

        Args:
            trajectory (Vector): Trajectory points to visualize
            compression (float): Compression ratio (0.0 to 1.0)
            compression_method (CompressionMethod): Method for trajectory compression
            **kwargs (Any): Additional visualization parameters

        Returns:
            BasePlotter: Self reference for method chaining
        """
        processed = _downsample_trajectory(trajectory, compression, compression_method)
        return self.visualize_impl(processed, **kwargs)

    @abstractmethod
    def visualize_impl(self, trajectory: Vector, **kwargs: Any) -> "BasePlotter":
        """Implementation specific visualization logic"""

attractors.BasePlotter.__init__(system, theme, num_segments=50, color_by='time', color_cycles=1.0, fig_kwargs=None) ¤

Initialize the plotter.

Parameters:

Name Type Description Default
system System

Dynamical system to visualize

required
theme Theme

Visual theme for plots

required
num_segments int

Number of segments for visualization

50
color_by str | ColorMapper

Color mapping strategy ("time", "x", "y", "z", "velocity") or ColorMapper instance

'time'
color_cycles float

Number of color cycles through the palette

1.0
fig_kwargs dict[str, Any] | None

Additional arguments for matplotlib figure

None

Raises:

Type Description
ValueError

If color_by is not a valid option or ColorMapper instance

Source code in src/attractors/visualizers/base.py
def __init__(
    self,
    system: System,
    theme: Theme,
    num_segments: int = 50,
    color_by: str | ColorMapper = "time",
    color_cycles: float = 1.0,
    fig_kwargs: dict[str, Any] | None = None,
) -> None:
    """
    Initialize the plotter.

    Args:
        system (System): Dynamical system to visualize
        theme (Theme): Visual theme for plots
        num_segments (int): Number of segments for visualization
        color_by (str | ColorMapper): Color mapping strategy ("time", "x", "y", "z", "velocity") or ColorMapper instance
        color_cycles (float): Number of color cycles through the palette
        fig_kwargs (dict[str, Any] | None): Additional arguments for matplotlib figure

    Raises:
        ValueError: If color_by is not a valid option or ColorMapper instance
    """  # noqa: E501
    self.color_mapper: ColorMapper
    self.system = system
    self.theme = theme
    self.num_segments = num_segments
    self.color_cycles = color_cycles
    self.fig_kwargs = fig_kwargs or {}

    if isinstance(color_by, str):
        if color_by not in self.VALID_COLOR_OPTIONS:
            msg = f"color_by must be one of {self.VALID_COLOR_OPTIONS} or a ColorMapper"
            raise ValueError(msg)

        if color_by == "time":
            self.color_mapper = TimeColorMapper()
        elif color_by == "velocity":
            self.color_mapper = VelocityColorMapper()
        else:  # x, y, z
            self.color_mapper = CoordinateColorMapper({"x": 0, "y": 1, "z": 2}[color_by])
    else:
        self.color_mapper = color_by

attractors.BasePlotter.visualize(trajectory, compression=0.0, compression_method=CompressionMethod.VELOCITY, **kwargs) ¤

Process and visualize trajectory data.

Parameters:

Name Type Description Default
trajectory Vector

Trajectory points to visualize

required
compression float

Compression ratio (0.0 to 1.0)

0.0
compression_method CompressionMethod

Method for trajectory compression

VELOCITY
**kwargs Any

Additional visualization parameters

{}

Returns:

Name Type Description
BasePlotter BasePlotter

Self reference for method chaining

Source code in src/attractors/visualizers/base.py
def visualize(
    self,
    trajectory: Vector,
    compression: float = 0.0,
    compression_method: CompressionMethod = CompressionMethod.VELOCITY,
    **kwargs: Any,
) -> "BasePlotter":
    """
    Process and visualize trajectory data.

    Args:
        trajectory (Vector): Trajectory points to visualize
        compression (float): Compression ratio (0.0 to 1.0)
        compression_method (CompressionMethod): Method for trajectory compression
        **kwargs (Any): Additional visualization parameters

    Returns:
        BasePlotter: Self reference for method chaining
    """
    processed = _downsample_trajectory(trajectory, compression, compression_method)
    return self.visualize_impl(processed, **kwargs)

attractors.BasePlotter.visualize_impl(trajectory, **kwargs) abstractmethod ¤

Implementation specific visualization logic

Source code in src/attractors/visualizers/base.py
@abstractmethod
def visualize_impl(self, trajectory: Vector, **kwargs: Any) -> "BasePlotter":
    """Implementation specific visualization logic"""

attractors.StaticPlotter ¤

Bases: BasePlotter

Plotter for static visualization of dynamical system trajectories.

Source code in src/attractors/visualizers/static.py
class StaticPlotter(BasePlotter):
    """Plotter for static visualization of dynamical system trajectories."""

    def visualize_impl(
        self,
        trajectory: Vector,
        line_kwargs: dict[str, Any] | None = None,
        segment_overlap: int = 1,
        **kwargs: Any,
    ) -> "StaticPlotter":
        """
        Create a static plot of trajectory segments with color mapping.

        Args:
            trajectory (Vector): Trajectory points to visualize
            line_kwargs (dict[str, Any] | None): Additional arguments for matplotlib line plots. Defaults to None.
            segment_overlap (int): Number of points to overlap between segments. Defaults to 1.
            **kwargs (Any): Additional visualization parameters

        Returns:
            StaticPlotter: Self reference for method chaining
        """  # noqa: E501
        self._setup_plot()

        line_kwargs = line_kwargs or {}

        n = len(trajectory)
        segment_size = n // self.num_segments
        overlap = segment_overlap

        segments = []
        color_values = self._get_color_values(trajectory)
        color_segments = []

        for i in range(self.num_segments):
            start_idx = max(0, i * segment_size - overlap)
            end_idx = min(n, (i + 1) * segment_size + overlap)
            segments.append(trajectory[start_idx:end_idx])
            color_segments.append(color_values[start_idx:end_idx])

        for segment, color_segment in zip(segments, color_segments, strict=False):
            self.ax.plot(
                segment[:, 0],
                segment[:, 1],
                segment[:, 2],
                "-",
                c=self.theme.colormap(color_segment.mean()),
                **line_kwargs,
            )
        return self

attractors.StaticPlotter.visualize_impl(trajectory, line_kwargs=None, segment_overlap=1, **kwargs) ¤

Create a static plot of trajectory segments with color mapping.

Parameters:

Name Type Description Default
trajectory Vector

Trajectory points to visualize

required
line_kwargs dict[str, Any] | None

Additional arguments for matplotlib line plots. Defaults to None.

None
segment_overlap int

Number of points to overlap between segments. Defaults to 1.

1
**kwargs Any

Additional visualization parameters

{}

Returns:

Name Type Description
StaticPlotter StaticPlotter

Self reference for method chaining

Source code in src/attractors/visualizers/static.py
def visualize_impl(
    self,
    trajectory: Vector,
    line_kwargs: dict[str, Any] | None = None,
    segment_overlap: int = 1,
    **kwargs: Any,
) -> "StaticPlotter":
    """
    Create a static plot of trajectory segments with color mapping.

    Args:
        trajectory (Vector): Trajectory points to visualize
        line_kwargs (dict[str, Any] | None): Additional arguments for matplotlib line plots. Defaults to None.
        segment_overlap (int): Number of points to overlap between segments. Defaults to 1.
        **kwargs (Any): Additional visualization parameters

    Returns:
        StaticPlotter: Self reference for method chaining
    """  # noqa: E501
    self._setup_plot()

    line_kwargs = line_kwargs or {}

    n = len(trajectory)
    segment_size = n // self.num_segments
    overlap = segment_overlap

    segments = []
    color_values = self._get_color_values(trajectory)
    color_segments = []

    for i in range(self.num_segments):
        start_idx = max(0, i * segment_size - overlap)
        end_idx = min(n, (i + 1) * segment_size + overlap)
        segments.append(trajectory[start_idx:end_idx])
        color_segments.append(color_values[start_idx:end_idx])

    for segment, color_segment in zip(segments, color_segments, strict=False):
        self.ax.plot(
            segment[:, 0],
            segment[:, 1],
            segment[:, 2],
            "-",
            c=self.theme.colormap(color_segment.mean()),
            **line_kwargs,
        )
    return self

attractors.integrate_system(system, solver, steps, dt, use_jit=None) ¤

Integrates a dynamical system using the specified numerical solver.

Parameters:

Name Type Description Default
system System

System to integrate

required
solver Solver

Numerical solver to use for integration

required
steps int

Number of integration steps

required
dt float

Time step size

required
use_jit bool | None

Whether to use Numba JIT compilation. Defaults to True.

None

Raises:

Type Description
ValueError

If steps <= 0 or dt <= 0

Returns:

Type Description
tuple[Vector, Vector]

tuple[Vector, Vector]: A tuple containing: - Vector: System state trajectory at each time step - Vector: Time points corresponding to trajectory

Source code in src/attractors/solvers/core.py
def integrate_system(
    system: System, solver: Solver, steps: int, dt: float, use_jit: bool | None = None
) -> tuple[Vector, Vector]:
    """Integrates a dynamical system using the specified numerical solver.

    Args:
        system (System): System to integrate
        solver (Solver): Numerical solver to use for integration
        steps (int): Number of integration steps
        dt (float): Time step size
        use_jit (bool | None): Whether to use Numba JIT compilation. Defaults to True.

    Raises:
        ValueError: If steps <= 0 or dt <= 0

    Returns:
        tuple[Vector, Vector]: A tuple containing:
            - Vector: System state trajectory at each time step
            - Vector: Time points corresponding to trajectory

    """
    if steps <= 0:
        raise ValueError("Number of steps must be positive")
    if dt <= 0:
        raise ValueError("Time step must be positive")

    jit_enabled = True if use_jit is None else use_jit
    logger.debug("JIT enabled: %s", jit_enabled)
    logger.info("Integrating system: %s with solver: %s", system, solver)
    logger.info("Steps: %d, dt: %.6g", steps, dt)

    if jit_enabled is True:
        integrate_func = _integrate_trajectory_jitted
    else:
        integrate_func = _integrate_trajectory_impl

    system_func = system.get_func(jit_enabled)
    solver_func = solver.get_func(jit_enabled)

    return integrate_func(  # type: ignore[no-any-return]
        system_func, solver_func, system.init_coord, system.params, steps, dt
    )