Skip to content

Engines

BaseIntegrationEngine

BaseIntegrationEngine(_config: _BaseEngineConfigDictT)

Bases: ABC, EngineProtocol[_BaseEngineConfigDictT]

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
521
522
523
524
525
526
527
528
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)

Functions

get_calc_step
get_calc_step() -> float

Get step size for integration.

Source code in py_ballisticcalc/engines/base_engine.py
530
531
532
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
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
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
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
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
710
711
712
713
714
715
716
717
718
719
720
721
722
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
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
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
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
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[BaseEngineConfigDict]

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

Parameters:

  • config (BaseEngineConfigDict) –

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

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 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

EulerIntegrationEngine

EulerIntegrationEngine(config: BaseEngineConfigDict)

Bases: BaseIntegrationEngine[BaseEngineConfigDict]

Euler integration engine for ballistic trajectory calculations.

Parameters:

  • config (BaseEngineConfigDict) –

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

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

    Args:
        config: Configuration dictionary containing engine parameters.
               See BaseEngineConfigDict for available options.
    """
    super().__init__(config)
    self.integration_step_count: int = 0

VelocityVerletIntegrationEngine

VelocityVerletIntegrationEngine(
    config: BaseEngineConfigDict,
)

Bases: BaseIntegrationEngine[BaseEngineConfigDict]

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.

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

Parameters:

  • config (BaseEngineConfigDict) –

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

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 for available options.

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

SciPyIntegrationEngine

SciPyIntegrationEngine(_config: SciPyEngineConfigDict)

Bases: BaseIntegrationEngine[SciPyEngineConfigDict]

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

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:

  • _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
    

Raises:

  • ImportError

    If scipy or numpy packages are not available.

  • ValueError

    If configuration contains invalid parameters.

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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
@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
                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