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
491
492
493
494
495
496
497
498
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, self._config.cGravityConstant, .0)

Functions

get_calc_step
get_calc_step() -> float

Get step size for integration.

Source code in py_ballisticcalc/engines/base_engine.py
500
501
502
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
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
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
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
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
675
676
677
678
679
680
681
682
683
684
685
686
687
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
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
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
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
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
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
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
@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.
    """
    self._config: SciPyEngineConfig = create_scipy_engine_config(_config)  # type: ignore
    self.gravity_vector: Vector = Vector(.0, self._config.cGravityConstant, .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