Skip to content

Engines

BaseIntegrationEngine

BaseIntegrationEngine(_config: _BaseEngineConfigDictT)

Bases: ABC, EngineProtocol

All calculations are done in imperial units (feet and fps).

Parameters:

Name Type Description Default
_config _BaseEngineConfigDictT

The configuration object.

required

Methods:

Name Description
get_calc_step

Get step size for integration.

find_max_range

Find the maximum range along shot_info.look_angle, and the launch angle to reach it.

find_apex

Find the apex of the trajectory.

find_zero_angle

Find the barrel elevation needed to hit sight line at a specific distance.

zero_angle

Find the barrel elevation needed to hit sight line at a specific distance.

integrate

Compute the trajectory for the given shot.

Source code in py_ballisticcalc/engines/base_engine.py
541
542
543
544
545
546
547
548
def __init__(self, _config: _BaseEngineConfigDictT):
    """Initialize the class.

    Args:
        _config: The configuration object.
    """
    self._config: BaseEngineConfig = create_base_engine_config(_config)
    self.gravity_vector: Vector = Vector(0.0, self._config.cGravityConstant, 0.0)

get_calc_step

get_calc_step() -> float

Get step size for integration.

Source code in py_ballisticcalc/engines/base_engine.py
550
551
552
def get_calc_step(self) -> float:
    """Get step size for integration."""
    return self._config.cStepMultiplier

find_max_range

find_max_range(
    shot_info: Shot,
    angle_bracket_deg: Tuple[float, float] = (0, 90),
) -> Tuple[Distance, Angular]

Find the maximum range along shot_info.look_angle, and the launch angle to reach it.

Parameters:

Name Type Description Default
shot_info Shot

The shot information: gun, ammo, environment, look_angle.

required
angle_bracket_deg Tuple[float, float]

The angle bracket in degrees to search for max range. Defaults to (0, 90).

(0, 90)

Returns:

Type Description
Tuple[Distance, Angular]

The maximum slant-range and the launch angle to reach it.

Raises:

Type Description
ValueError

If the angle bracket excludes the look_angle.

Source code in py_ballisticcalc/engines/base_engine.py
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
def find_max_range(
    self, shot_info: Shot, angle_bracket_deg: Tuple[float, float] = (0, 90)
) -> Tuple[Distance, Angular]:
    """Find the maximum range along shot_info.look_angle, and the launch angle to reach it.

    Args:
        shot_info: The shot information: gun, ammo, environment, look_angle.
        angle_bracket_deg: The angle bracket in degrees to search for max range. Defaults to (0, 90).

    Returns:
        The maximum slant-range and the launch angle to reach it.

    Raises:
        ValueError: If the angle bracket excludes the look_angle.
    """
    """
    TODO: Make sure user hasn't restricted angle bracket to exclude the look_angle.
        ... and check for weird situations, like backward-bending trajectories,
        where the max range occurs with launch angle less than the look angle.
    """
    props = self._init_trajectory(shot_info)
    return self._find_max_range(props, angle_bracket_deg)

find_apex

find_apex(shot_info: Shot) -> TrajectoryData

Find the apex of the trajectory.

Apex is defined as the point where the vertical component of velocity goes from positive to negative.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required

Returns:

Name Type Description
TrajectoryData TrajectoryData

The trajectory data at the apex of the trajectory.

Raises:

Type Description
SolverRuntimeError

If no apex is found in the trajectory data.

ValueError

If barrel elevation is not > 0.

Source code in py_ballisticcalc/engines/base_engine.py
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
def find_apex(self, shot_info: Shot) -> TrajectoryData:
    """Find the apex of the trajectory.

    Apex is defined as the point where the vertical component of velocity goes from positive to negative.

    Args:
        shot_info: The shot information.

    Returns:
        TrajectoryData: The trajectory data at the apex of the trajectory.

    Raises:
        SolverRuntimeError: If no apex is found in the trajectory data.
        ValueError: If barrel elevation is not > 0.
    """
    props = self._init_trajectory(shot_info)
    return self._find_apex(props)

find_zero_angle

find_zero_angle(
    shot_info: Shot,
    distance: Distance,
    lofted: bool = False,
) -> Angular

Find the barrel elevation needed to hit sight line at a specific distance.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
distance Distance

Slant distance to the target.

required
lofted bool

If True, find the higher angle that hits the zero point.

False

Returns:

Type Description
Angular

Barrel elevation needed to hit the zero point.

Source code in py_ballisticcalc/engines/base_engine.py
730
731
732
733
734
735
736
737
738
739
740
741
742
def find_zero_angle(self, shot_info: Shot, distance: Distance, lofted: bool = False) -> Angular:
    """Find the barrel elevation needed to hit sight line at a specific distance.

    Args:
        shot_info: The shot information.
        distance: Slant distance to the target.
        lofted: If True, find the higher angle that hits the zero point.

    Returns:
        Barrel elevation needed to hit the zero point.
    """
    props = self._init_trajectory(shot_info)
    return self._find_zero_angle(props, distance, lofted)

zero_angle

zero_angle(shot_info: Shot, distance: Distance) -> Angular

Find the barrel elevation needed to hit sight line at a specific distance.

First tries iterative approach; if that fails then falls back on _find_zero_angle.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
distance Distance

The distance to the target.

required

Returns:

Type Description
Angular

Barrel elevation to hit height zero at zero distance along sight line

Source code in py_ballisticcalc/engines/base_engine.py
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
def zero_angle(self, shot_info: Shot, distance: Distance) -> Angular:
    """Find the barrel elevation needed to hit sight line at a specific distance.

    First tries iterative approach; if that fails then falls back on `_find_zero_angle`.

    Args:
        shot_info: The shot information.
        distance: The distance to the target.

    Returns:
        Barrel elevation to hit height zero at zero distance along sight line
    """
    props = self._init_trajectory(shot_info)
    try:
        return self._zero_angle(props, distance)
    except ZeroFindingError as e:
        logger.warning(f"Failed to find zero angle using base iterative method: {e}")
        # Fallback to guaranteed method
        return self._find_zero_angle(props, distance)

integrate

integrate(
    shot_info: Shot,
    max_range: Distance,
    dist_step: Optional[Distance] = None,
    time_step: float = 0.0,
    filter_flags: Union[TrajFlag, int] = NONE,
    dense_output: bool = False,
    **kwargs,
) -> HitResult

Compute the trajectory for the given shot.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
max_range Distance

Maximum range of the trajectory (if float then treated as feet).

required
dist_step Optional[Distance]

Distance step for recording RANGE TrajectoryData rows.

None
time_step float

Time step for recording trajectory data. Defaults to 0.0.

0.0
filter_flags Union[TrajFlag, int]

Flags to filter trajectory data. Defaults to TrajFlag.RANGE.

NONE
dense_output bool

If True, HitResult will save BaseTrajData for interpolating TrajectoryData.

False

Returns:

Type Description
HitResult

HitResult object for describing the trajectory.

Source code in py_ballisticcalc/engines/base_engine.py
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
def integrate(
    self,
    shot_info: Shot,
    max_range: Distance,
    dist_step: Optional[Distance] = None,
    time_step: float = 0.0,
    filter_flags: Union[TrajFlag, int] = TrajFlag.NONE,
    dense_output: bool = False,
    **kwargs,
) -> HitResult:
    """Compute the trajectory for the given shot.

    Args:
        shot_info: The shot information.
        max_range: Maximum range of the trajectory (if float then treated as feet).
        dist_step: Distance step for recording RANGE TrajectoryData rows.
        time_step: Time step for recording trajectory data. Defaults to 0.0.
        filter_flags: Flags to filter trajectory data. Defaults to TrajFlag.RANGE.
        dense_output: If True, HitResult will save BaseTrajData for interpolating TrajectoryData.

    Returns:
        HitResult object for describing the trajectory.
    """
    props = self._init_trajectory(shot_info)
    props.filter_flags = filter_flags
    range_limit_ft = max_range >> Distance.Foot
    if dist_step is None:
        range_step_ft = range_limit_ft
    else:
        range_step_ft = dist_step >> Distance.Foot
    return self._integrate(props, range_limit_ft, range_step_ft, time_step, filter_flags, dense_output, **kwargs)

BaseEngineConfigDict

Bases: TypedDict

TypedDict for flexible engine configuration from dictionaries.

This TypedDict provides a flexible way to configure ballistic calculation engines using dictionary syntax. All fields are optional, allowing partial configuration where only specific parameters need to be overridden.

When used with create_base_engine_config(), any unspecified fields will use their default values from DEFAULT_BASE_ENGINE_CONFIG.

Note: All fields are Optional to support partial configuration.

Fields
  • cZeroFindingAccuracy: Maximum slant-error in feet for zero-finding precision.
  • cMaxIterations: Maximum iterations for convergence algorithms.
  • cMinimumAltitude: Minimum altitude in feet to continue calculation.
  • cMaximumDrop: Maximum drop in feet from muzzle to continue.
  • cMinimumVelocity: Minimum velocity in fps to continue calculation.
  • cGravityConstant: Gravitational acceleration in ft/s².
  • cStepMultiplier: Integration step size multiplier.

Examples:

>>> config_dict: BaseEngineConfigDict = {
...     'cMinimumVelocity': 100.0,
...     'cStepMultiplier': 0.8
... }
>>> config = create_base_engine_config(config_dict)
>>> # Using with Calculator
>>> from py_ballisticcalc import Calculator
>>> calc = Calculator(config=config_dict)
See Also
  • BaseEngineConfig: Type-safe dataclass version
  • create_base_engine_config: Factory function for BaseEngineConfig creation

RK4IntegrationEngine

RK4IntegrationEngine(config: BaseEngineConfigDict)

Bases: BaseIntegrationEngine

Runge-Kutta 4th order integration engine for ballistic trajectory calculations.

Attributes:

Name Type Description
integration_step_count int

Number of integration steps performed.

Examples:

>>> config = BaseEngineConfigDict(cMinimumVelocity=0.0)
>>> engine = RK4IntegrationEngine(config)

Parameters:

Name Type Description Default
config BaseEngineConfigDict

Configuration dictionary containing engine parameters. See BaseEngineConfigDict for available options. Common settings include cStepMultiplier for accuracy control and cMinimumVelocity for termination conditions.

required

Examples:

>>> precise_config = BaseEngineConfigDict(
...     cStepMultiplier=0.5,  # Smaller steps
...     cMinimumVelocity=20.0  # Continue to lower velocities
... )
>>> precise_engine = RK4IntegrationEngine(precise_config)

Methods:

Name Description
get_calc_step

Get the calculation step size for RK4 integration.

Source code in py_ballisticcalc/engines/rk4.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __init__(self, config: BaseEngineConfigDict) -> None:
    """Initialize the RK4 integration engine.

    Args:
        config: Configuration dictionary containing engine parameters.
               See [`BaseEngineConfigDict`](py_ballisticcalc.base_engine.BaseEngineConfigDict) for available options.
               Common settings include cStepMultiplier for accuracy control
               and cMinimumVelocity for termination conditions.

    Examples:
        >>> precise_config = BaseEngineConfigDict(
        ...     cStepMultiplier=0.5,  # Smaller steps
        ...     cMinimumVelocity=20.0  # Continue to lower velocities
        ... )
        >>> precise_engine = RK4IntegrationEngine(precise_config)
    """
    super().__init__(config)
    self.integration_step_count: int = 0
    self.trajectory_count = 0  # Number of trajectories calculated

get_calc_step

get_calc_step() -> float

Get the calculation step size for RK4 integration.

Returns:

Type Description
float

Time-step size (in seconds) for integration calculations.

Mathematical Context

The step size directly affects the accuracy and computational cost: - Smaller steps: Higher accuracy, more computation - Larger steps: Lower accuracy, faster computation - RK4's O(h⁵) error means accuracy improves rapidly with smaller h

Examples:

>>> config = BaseEngineConfigDict(cStepMultiplier=0.5)
>>> engine = RK4IntegrationEngine(config)
>>> engine.get_calc_step()
0.00125
Note

For RK4, the relationship between step size and accuracy is: - Halving the step size reduces error by ~32× (2⁵) - Default step size is sufficient to pass unit tests.

Source code in py_ballisticcalc/engines/rk4.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@override
def get_calc_step(self) -> float:
    """Get the calculation step size for RK4 integration.

    Returns:
        Time-step size (in seconds) for integration calculations.

    Mathematical Context:
        The step size directly affects the accuracy and computational cost:
        - Smaller steps: Higher accuracy, more computation
        - Larger steps: Lower accuracy, faster computation
        - RK4's O(h⁵) error means accuracy improves rapidly with smaller h

    Examples:
        >>> config = BaseEngineConfigDict(cStepMultiplier=0.5)
        >>> engine = RK4IntegrationEngine(config)
        >>> engine.get_calc_step()
        0.00125

    Note:
        For RK4, the relationship between step size and accuracy is:
        - Halving the step size reduces error by ~32× (2⁵)
        - Default step size is sufficient to pass unit tests.
    """
    return super().get_calc_step() * self.DEFAULT_TIME_STEP

EulerIntegrationEngine

EulerIntegrationEngine(config: BaseEngineConfigDict)

Bases: BaseIntegrationEngine

Euler integration engine for ballistic trajectory calculations.

Attributes:

Name Type Description
DEFAULT_STEP

Default step size multiplier for integration (0.5).

integration_step_count int

Number of integration steps performed.

Examples:

>>> config = BaseEngineConfigDict(cMinimumVelocity=100.0)
>>> engine = EulerIntegrationEngine(config)

Parameters:

Name Type Description Default
config BaseEngineConfigDict

Configuration dictionary containing engine parameters. See [BaseEngineConfigDict][py_ballisticcalc.base_engine.BaseEngineConfigDict] for available options.

required

Methods:

Name Description
get_calc_step

Get the base calculation step size for Euler integration.

time_step

Calculate adaptive time step based on current projectile velocity.

Source code in py_ballisticcalc/engines/euler.py
67
68
69
70
71
72
73
74
75
76
def __init__(self, config: BaseEngineConfigDict) -> None:
    """Initialize the Euler integration engine.

    Args:
        config: Configuration dictionary containing engine parameters.
               See [`BaseEngineConfigDict`][py_ballisticcalc.base_engine.BaseEngineConfigDict] for available options.
    """
    super().__init__(config)
    self.integration_step_count: int = 0
    self.trajectory_count = 0  # Number of trajectories calculated

get_calc_step

get_calc_step() -> float

Get the base calculation step size for Euler integration.

Calculates the effective step size by combining the base engine step multiplier with the Euler-specific DEFAULT_STEP constant. The step size directly affects accuracy and performance trade-offs. This is a distance-like quantity that is subsequently scaled by velocity to produce a time-like integration step.

Returns:

Type Description
float

Base step size for integration calculations.

Note

The step size is calculated as: cStepMultiplier * DEFAULT_STEP. Smaller step sizes increase accuracy but require more computation. The DEFAULT_STEP is sufficient to pass all unit tests.

Source code in py_ballisticcalc/engines/euler.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@override
def get_calc_step(self) -> float:
    """Get the base calculation step size for Euler integration.

    Calculates the effective step size by combining the base engine
    step multiplier with the Euler-specific DEFAULT_STEP constant.
    The step size directly affects accuracy and performance trade-offs.
    This is a distance-like quantity that is subsequently scaled by velocity
    to produce a time-like integration step.

    Returns:
        Base step size for integration calculations.

    Note:
        The step size is calculated as: `cStepMultiplier * DEFAULT_STEP`.
        Smaller step sizes increase accuracy but require more computation.
        The DEFAULT_STEP is sufficient to pass all unit tests.
    """
    return super().get_calc_step() * self.DEFAULT_TIME_STEP

time_step

time_step(base_step: float, velocity: float) -> float

Calculate adaptive time step based on current projectile velocity.

Implements adaptive time stepping where the time step is inversely related to projectile velocity. This helps maintain numerical stability and accuracy as the projectile slows down or speeds up.

Parameters:

Name Type Description Default
base_step float

Base step size from the integration engine.

required
velocity float

Current projectile velocity in fps.

required

Returns:

Type Description
float

Adaptive time step for the current integration step.

Formula

time_step = base_step / max(1.0, velocity)

Examples:

>>> config = BaseEngineConfigDict(cStepMultiplier=0.5)
>>> engine = EulerIntegrationEngine(config)
>>> engine.time_step(0.5, 2000.0)
0.00025
>>> engine.time_step(0.5, 100.0)
0.005
Note

The max(1.0, velocity) ensures that the time step never becomes excessively large, maintaining numerical stability even at very low velocities.

Source code in py_ballisticcalc/engines/euler.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def time_step(self, base_step: float, velocity: float) -> float:
    """Calculate adaptive time step based on current projectile velocity.

    Implements adaptive time stepping where the time step is inversely
    related to projectile velocity. This helps maintain numerical stability
    and accuracy as the projectile slows down or speeds up.

    Args:
        base_step: Base step size from the integration engine.
        velocity: Current projectile velocity in fps.

    Returns:
        Adaptive time step for the current integration step.

    Formula:
        time_step = base_step / max(1.0, velocity)

    Examples:
        >>> config = BaseEngineConfigDict(cStepMultiplier=0.5)
        >>> engine = EulerIntegrationEngine(config)
        >>> engine.time_step(0.5, 2000.0)
        0.00025
        >>> engine.time_step(0.5, 100.0)
        0.005

    Note:
        The max(1.0, velocity) ensures that the time step never becomes
        excessively large, maintaining numerical stability even at very
        low velocities.
    """
    return base_step / max(1.0, velocity)

VelocityVerletIntegrationEngine

VelocityVerletIntegrationEngine(
    config: BaseEngineConfigDict,
)

Bases: BaseIntegrationEngine

Velocity Verlet integration engine for ballistic trajectory calculations.

Algorithm Details

The method uses a two-stage approach: 1. Update position using current velocity and acceleration. 2. Update velocity using average of current and new acceleration. This ensures velocity and position remain properly synchronized and conserves the total energy of the system.

Attributes:

Name Type Description
DEFAULT_TIME_STEP

Default time step multiplier.

integration_step_count int

Number of integration steps performed.

See Also
  • RK4IntegrationEngine: Higher accuracy alternative
  • EulerIntegrationEngine: Simpler alternative
  • SciPyIntegrationEngine: Adaptive methods

Parameters:

Name Type Description Default
config BaseEngineConfigDict

Configuration dictionary containing engine parameters. See BaseEngineConfigDict for available options.

required

Examples:

>>> config = BaseEngineConfigDict(
...     cStepMultiplier=0.5,
...     cMinimumVelocity=10.0
... )
>>> engine = VelocityVerletIntegrationEngine(config)

Methods:

Name Description
get_calc_step

Get the calculation step size for Velocity Verlet integration.

Source code in py_ballisticcalc/engines/velocity_verlet.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def __init__(self, config: BaseEngineConfigDict) -> None:
    """Initialize the Velocity Verlet integration engine.

    Args:
        config: Configuration dictionary containing engine parameters.
               See [`BaseEngineConfigDict`](py_ballisticcalc.base_engine.BaseEngineConfigDict) for available options.

    Examples:
        >>> config = BaseEngineConfigDict(
        ...     cStepMultiplier=0.5,
        ...     cMinimumVelocity=10.0
        ... )
        >>> engine = VelocityVerletIntegrationEngine(config)
    """
    super().__init__(config)
    self.integration_step_count: int = 0

get_calc_step

get_calc_step() -> float

Get the calculation step size for Velocity Verlet integration.

Combines the base engine step multiplier with the Verlet-specific DEFAULT_TIME_STEP to determine the effective integration step size.

Returns:

Type Description
float

Effective step size for Velocity Verlet integration.

Formula

step_size = base_step_multiplier × DEFAULT_TIME_STEP

Note

The small DEFAULT_TIME_STEP value is chosen to ensure that this engine can pass all unit tests, despite most of them being highly dissipative rather than conservative of energy.

Source code in py_ballisticcalc/engines/velocity_verlet.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
@override
def get_calc_step(self) -> float:
    """Get the calculation step size for Velocity Verlet integration.

    Combines the base engine step multiplier with the Verlet-specific
    DEFAULT_TIME_STEP to determine the effective integration step size.

    Returns:
        Effective step size for Velocity Verlet integration.

    Formula:
        step_size = base_step_multiplier × DEFAULT_TIME_STEP

    Note:
        The small DEFAULT_TIME_STEP value is chosen to ensure
        that this engine can pass all unit tests, despite most of them
        being highly dissipative rather than conservative of energy.
    """
    return super().get_calc_step() * self.DEFAULT_TIME_STEP

SciPyIntegrationEngine

SciPyIntegrationEngine(_config: SciPyEngineConfigDict)

Bases: BaseIntegrationEngine

High-performance ballistic trajectory integration engine using SciPy's solve_ivp.

Examples:

>>> from py_ballisticcalc.engines.scipy_engine import SciPyIntegrationEngine, SciPyEngineConfigDict
>>>
>>> # High-precision configuration
>>> config = SciPyEngineConfigDict(
...     integration_method='DOP853',
...     relative_tolerance=1e-10,
...     absolute_tolerance=1e-12
... )
>>> engine = SciPyIntegrationEngine(config)
>>>
>>> # Using with Calculator
>>> from py_ballisticcalc import Calculator
>>> calc = Calculator(engine='scipy_engine')
Note

Requires scipy and numpy packages. Install with: pip install py_ballisticcalc[scipy] or pip install scipy numpy

Sets up the engine with the provided configuration dictionary, initializing all necessary parameters for high-precision ballistic trajectory calculations. The configuration is converted to a structured format with appropriate defaults for any unspecified parameters.

Parameters:

Name Type Description Default
_config SciPyEngineConfigDict

Configuration dictionary containing engine parameters. Can include SciPy-specific options (integration_method, tolerances, max_time) as well as all standard BaseEngineConfigDict.

parameters (cMinimumVelocity, cStepMultiplier, etc.).

SciPy-specific parameters:
- integration_method: SciPy method ('RK45', 'DOP853', etc.)
- relative_tolerance: Relative error tolerance (rtol)
- absolute_tolerance: Absolute error tolerance (atol)
- max_time: Maximum simulation time in seconds

Standard ballistic parameters:
- cMinimumVelocity: Minimum velocity to continue calculation
- cStepMultiplier: Integration step size multiplier
- cGravityConstant: Gravitational acceleration
- And other BaseEngineConfigDict parameters
required

Raises:

Type Description
ImportError

If scipy or numpy packages are not available.

ValueError

If configuration contains invalid parameters.

Examples:

>>> config = SciPyEngineConfigDict(
...     integration_method='DOP853',
...     relative_tolerance=1e-10,
...     cMinimumVelocity=50.0
... )
>>> engine = SciPyIntegrationEngine(config)
Attributes Initialized
  • _config: Complete configuration with defaults applied
  • gravity_vector: Gravitational acceleration vector
  • integration_step_count: Counter for integration steps (debugging)
  • trajectory_count: Counter for calculated trajectories (debugging)
  • eval_points: List of evaluation points (debugging/analysis)
Note

The configuration is processed through create_scipy_engine_config() which applies defaults for any unspecified parameters. This ensures the engine always has a complete, valid configuration.

Source code in py_ballisticcalc/engines/scipy_engine.py
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
@override
def __init__(self, _config: SciPyEngineConfigDict) -> None:
    """Initialize the SciPy integration engine with configuration.

    Sets up the engine with the provided configuration dictionary, initializing
    all necessary parameters for high-precision ballistic trajectory calculations.
    The configuration is converted to a structured format with appropriate
    defaults for any unspecified parameters.

    Args:
        _config: Configuration dictionary containing engine parameters.
                Can include SciPy-specific options (integration_method,
                tolerances, max_time) as well as all standard [`BaseEngineConfigDict`](py_ballisticcalc.base_engine.BaseEngineConfigDict).

                parameters (cMinimumVelocity, cStepMultiplier, etc.).

                SciPy-specific parameters:
                - integration_method: SciPy method ('RK45', 'DOP853', etc.)
                - relative_tolerance: Relative error tolerance (rtol)
                - absolute_tolerance: Absolute error tolerance (atol)
                - max_time: Maximum simulation time in seconds

                Standard ballistic parameters:
                - cMinimumVelocity: Minimum velocity to continue calculation
                - cStepMultiplier: Integration step size multiplier
                - cGravityConstant: Gravitational acceleration
                - And other BaseEngineConfigDict parameters

    Raises:
        ImportError: If scipy or numpy packages are not available.
        ValueError: If configuration contains invalid parameters.

    Examples:
        >>> config = SciPyEngineConfigDict(
        ...     integration_method='DOP853',
        ...     relative_tolerance=1e-10,
        ...     cMinimumVelocity=50.0
        ... )
        >>> engine = SciPyIntegrationEngine(config)

    Attributes Initialized:
        - _config: Complete configuration with defaults applied
        - gravity_vector: Gravitational acceleration vector
        - integration_step_count: Counter for integration steps (debugging)
        - trajectory_count: Counter for calculated trajectories (debugging)
        - eval_points: List of evaluation points (debugging/analysis)

    Note:
        The configuration is processed through create_scipy_engine_config()
        which applies defaults for any unspecified parameters. This ensures
        the engine always has a complete, valid configuration.
    """

    # dependencies guard
    if not _HAS_NUMPY:
        raise ImportError("Numpy is required for SciPyIntegrationEngine.")
    if not _HAS_SCIPY:
        raise ImportError("SciPy is required for SciPyIntegrationEngine.")

    self._config: SciPyEngineConfig = create_scipy_engine_config(_config)  # type: ignore
    self.gravity_vector: Vector = Vector(0.0, self._config.cGravityConstant, 0.0)
    self.integration_step_count = 0  # Number of evaluations of diff_eq during ._integrate()
    self.trajectory_count = 0  # Number of trajectories calculated
    self.eval_points: List[float] = []  # Points at which diff_eq is called

SciPyEngineConfigDict

Bases: BaseEngineConfigDict

TypedDict for flexible SciPy integration engine configuration.

This TypedDict provides a flexible dictionary-based interface for configuring the SciPy integration engine. All fields are optional (total=False), allowing partial configuration with automatic fallback to default values.

Attributes:

Name Type Description
max_time float

Maximum simulation time in seconds before terminating integration. Prevents runaway calculations in edge cases. If not specified, uses DEFAULT_MAX_TIME (90.0 seconds).

relative_tolerance float

Relative tolerance for integration error control (rtol). Controls the relative accuracy of the numerical solution. Smaller values provide higher precision at computational cost. If not specified, uses DEFAULT_RELATIVE_TOLERANCE (1e-8).

absolute_tolerance float

Absolute tolerance for integration error control (atol). Controls absolute accuracy, particularly important near zero. Smaller values improve precision for small quantities. If not specified, uses DEFAULT_ABSOLUTE_TOLERANCE (1e-6).

integration_method INTEGRATION_METHOD

SciPy solve_ivp integration method selection. If not specified, uses DEFAULT_INTEGRATION_METHOD ('RK45').

Examples:

>>> # Minimal configuration - uses defaults for unspecified fields
>>> config: SciPyEngineConfigDict = {
...     'integration_method': 'DOP853'  # High precision
... }

CythonizedBaseIntegrationEngine

CythonizedBaseIntegrationEngine(
    _config: BaseEngineConfigDict,
)

Bases: EngineProtocol[BaseEngineConfigDict]

Base Cython wrapper for C/С++ based binary engine. Implements EngineProtocol

Parameters:

Name Type Description Default
_config BaseEngineConfig

The engine configuration.

required
IMPORTANT

Avoid calling Python functions inside __init__! __init__ is called after __cinit__, so any memory allocated in __cinit__ that is not referenced in Python will be leaked if __init__ raises an exception.

Methods:

Name Description
find_max_range

Finds the maximum range along shot_info.look_angle,

find_zero_angle

Finds the barrel elevation needed to hit sight line at a specific distance,

find_apex

Finds the apex of the trajectory, where apex is defined as the point

zero_angle

Finds the barrel elevation needed to hit sight line at a specific distance.

integrate

Integrates the trajectory for the given shot.

integrate_raw_at

Integrates the trajectory until a specified attribute reaches a target value

Attributes:

Name Type Description
integration_step_count int

Gets the number of integration steps performed in the last integration.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
28
29
30
31
32
33
34
35
36
37
38
39
40
def __init__(self, _config: BaseEngineConfigDict) -> None:
    """
    Initializes the engine with the given configuration.

    Args:
        _config (BaseEngineConfig): The engine configuration.

    IMPORTANT:
        Avoid calling Python functions inside `__init__`!
        `__init__` is called after `__cinit__`, so any memory allocated in `__cinit__`
        that is not referenced in Python will be leaked if `__init__` raises an exception.
    """
    ...

integration_step_count property

integration_step_count: int

Gets the number of integration steps performed in the last integration.

Returns:

Name Type Description
int int

The number of integration steps.

find_max_range

find_max_range(
    shot_info: Shot,
    angle_bracket_deg: Tuple[float, float] = (0, 90),
) -> Tuple[Distance, Angular]

Finds the maximum range along shot_info.look_angle, and the launch angle to reach it.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
angle_bracket_deg Tuple[float, float]

The angle bracket in degrees to search for max range. Defaults to (0, 90).

(0, 90)

Returns:

Type Description
Tuple[Distance, Angular]

Tuple[Distance, Angular]: The maximum slant range and the launch angle to reach it.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def find_max_range(
    self, shot_info: Shot, angle_bracket_deg: Tuple[float, float] = (0, 90)
) -> Tuple[Distance, Angular]:
    """
    Finds the maximum range along shot_info.look_angle,
    and the launch angle to reach it.

    Args:
        shot_info (Shot): The shot information.
        angle_bracket_deg (Tuple[float, float], optional):
            The angle bracket in degrees to search for max range. Defaults to (0, 90).

    Returns:
        Tuple[Distance, Angular]: The maximum slant range and the launch angle to reach it.
    """
    ...

find_zero_angle

find_zero_angle(
    shot_info: Shot,
    distance: Distance,
    lofted: bool = False,
) -> Angular

Finds the barrel elevation needed to hit sight line at a specific distance, using unimodal root-finding that is guaranteed to succeed if a solution exists.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
distance Distance

The distance to the target.

required
lofted bool

Whether the shot is lofted.

False

Returns:

Name Type Description
Angular Angular

The required barrel elevation angle.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def find_zero_angle(self, shot_info: Shot, distance: Distance, lofted: bool = False) -> Angular:
    """
    Finds the barrel elevation needed to hit sight line at a specific distance,
    using unimodal root-finding that is guaranteed to succeed if a solution exists.

    Args:
        shot_info (Shot): The shot information.
        distance (Distance): The distance to the target.
        lofted (bool): Whether the shot is lofted.

    Returns:
        Angular: The required barrel elevation angle.
    """
    ...

find_apex

find_apex(shot_info: Shot) -> TrajectoryData

Finds the apex of the trajectory, where apex is defined as the point where the vertical component of velocity goes from positive to negative.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required

Returns:

Name Type Description
TrajectoryData TrajectoryData

The trajectory data at the apex.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
103
104
105
106
107
108
109
110
111
112
113
114
def find_apex(self, shot_info: Shot) -> TrajectoryData:
    """
    Finds the apex of the trajectory, where apex is defined as the point
    where the vertical component of velocity goes from positive to negative.

    Args:
        shot_info (Shot): The shot information.

    Returns:
        TrajectoryData: The trajectory data at the apex.
    """
    ...

zero_angle

zero_angle(shot_info: Shot, distance: Distance) -> Angular

Finds the barrel elevation needed to hit sight line at a specific distance. First tries iterative approach; if that fails falls back on _find_zero_angle.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
distance Distance

The distance to the target.

required

Returns:

Name Type Description
Angular Angular

Barrel elevation to hit height zero at zero distance along sight line

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
116
117
118
119
120
121
122
123
124
125
126
127
128
def zero_angle(self, shot_info: Shot, distance: Distance) -> Angular:
    """
    Finds the barrel elevation needed to hit sight line at a specific distance.
    First tries iterative approach; if that fails falls back on _find_zero_angle.

    Args:
        shot_info (Shot): The shot information.
        distance (Distance): The distance to the target.

    Returns:
        Angular: Barrel elevation to hit height zero at zero distance along sight line
    """
    ...

integrate

integrate(
    shot_info: Shot,
    max_range: Distance,
    dist_step: Distance | None = None,
    time_step: float = 0.0,
    filter_flags: TrajFlag | int = 0,
    dense_output: bool = False,
    **kwargs: Any,
) -> HitResult

Integrates the trajectory for the given shot.

Parameters:

Name Type Description Default
shot_info Shot

The shot information.

required
max_range Distance

Maximum range of the trajectory (if float then treated as feet).

required
dist_step Optional[Distance]

Distance step for recording RANGE TrajectoryData rows.

None
time_step float

Time step for recording trajectory data. Defaults to 0.0.

0.0
filter_flags Union[TrajFlag, int]

Flags to filter trajectory data. Defaults to TrajFlag.RANGE.

0
dense_output bool

If True, HitResult will save BaseTrajData for interpolating TrajectoryData.

False

Returns:

Name Type Description
HitResult HitResult

Object for describing the trajectory.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def integrate(
    self,
    shot_info: Shot,
    max_range: Distance,
    dist_step: Distance | None = None,
    time_step: float = 0.0,
    filter_flags: TrajFlag | int = 0,
    dense_output: bool = False,
    **kwargs: Any,
) -> HitResult:
    """
    Integrates the trajectory for the given shot.

    Args:
        shot_info (Shot): The shot information.
        max_range (Distance):
            Maximum range of the trajectory (if float then treated as feet).
        dist_step (Optional[Distance]):
            Distance step for recording RANGE TrajectoryData rows.
        time_step (float, optional):
            Time step for recording trajectory data. Defaults to 0.0.
        filter_flags (Union[TrajFlag, int], optional):
            Flags to filter trajectory data. Defaults to TrajFlag.RANGE.
        dense_output (bool, optional):
            If True, HitResult will save BaseTrajData for interpolating TrajectoryData.

    Returns:
        HitResult: Object for describing the trajectory.
    """
    ...

integrate_raw_at

integrate_raw_at(
    shot_info: Shot, key_attribute: str, target_value: float
) -> tuple[CythonizedBaseTrajData, TrajectoryData]

Integrates the trajectory until a specified attribute reaches a target value and returns the interpolated data point.

This method initializes the trajectory using the provided shot information, performs integration using the underlying C++ engine's 'integrate_at' function, and handles the conversion of C++ results back to Python objects.

Parameters:

Name Type Description Default
shot_info object

Information required to initialize the trajectory (e.g., muzzle velocity, drag model).

required
key_attribute str

The name of the attribute to track, such as 'time', 'mach', or a vector component like 'position.z'.

required
target_value float

The value the 'key_attribute' must reach for the integration to stop and interpolation to occur.

required

Returns:

Type Description
tuple[CythonizedBaseTrajData, TrajectoryData]

tuple[CythonizedBaseTrajData, TrajectoryData]: A tuple containing: - CythonizedBaseTrajData: The interpolated raw data point. - TrajectoryData: The fully processed trajectory data point.

Raises:

Type Description
InterceptionError

If the underlying C++ integration fails to find the target point (e.g., due to insufficient range or data issues).

SolverRuntimeError

If some other internal error occured

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def integrate_raw_at(
    self, shot_info: Shot, key_attribute: str, target_value: float
) -> tuple[CythonizedBaseTrajData, TrajectoryData]:
    """
    Integrates the trajectory until a specified attribute reaches a target value
    and returns the interpolated data point.

    This method initializes the trajectory using the provided shot information,
    performs integration using the underlying C++ engine's 'integrate_at' function,
    and handles the conversion of C++ results back to Python objects.

    Args:
        shot_info (object): Information required to initialize the trajectory
            (e.g., muzzle velocity, drag model).
        key_attribute (str): The name of the attribute to track, such as
            'time', 'mach', or a vector component like 'position.z'.
        target_value (float): The value the 'key_attribute' must reach for
            the integration to stop and interpolation to occur.

    Returns:
        tuple[CythonizedBaseTrajData, TrajectoryData]:
            A tuple containing:
            - CythonizedBaseTrajData: The interpolated raw data point.
            - TrajectoryData: The fully processed trajectory data point.

    Raises:
        InterceptionError: If the underlying C++ integration fails to find
            the target point (e.g., due to insufficient range or data issues).
        SolverRuntimeError: If some other internal error occured
    """
    ...

CythonizedRK4IntegrationEngine

CythonizedRK4IntegrationEngine(
    _config: BaseEngineConfigDict,
)

Bases: CythonizedBaseIntegrationEngine[BaseEngineConfigDict]

Cythonized RK4 (Runge-Kutta 4th order) integration engine for ballistic calculations.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
28
29
30
31
32
33
34
35
36
37
38
39
40
def __init__(self, _config: BaseEngineConfigDict) -> None:
    """
    Initializes the engine with the given configuration.

    Args:
        _config (BaseEngineConfig): The engine configuration.

    IMPORTANT:
        Avoid calling Python functions inside `__init__`!
        `__init__` is called after `__cinit__`, so any memory allocated in `__cinit__`
        that is not referenced in Python will be leaked if `__init__` raises an exception.
    """
    ...

CythonizedEulerIntegrationEngine

CythonizedEulerIntegrationEngine(
    _config: BaseEngineConfigDict,
)

Bases: CythonizedBaseIntegrationEngine[BaseEngineConfigDict]

Cythonized Euler integration engine for ballistic calculations.

Source code in py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyi
28
29
30
31
32
33
34
35
36
37
38
39
40
def __init__(self, _config: BaseEngineConfigDict) -> None:
    """
    Initializes the engine with the given configuration.

    Args:
        _config (BaseEngineConfig): The engine configuration.

    IMPORTANT:
        Avoid calling Python functions inside `__init__`!
        `__init__` is called after `__cinit__`, so any memory allocated in `__cinit__`
        that is not referenced in Python will be leaked if `__init__` raises an exception.
    """
    ...