Skip to content

Shotprops

ShotProps dataclass

ShotProps(
    shot: Shot,
    bc: float,
    curve: List[CurvePoint],
    mach_list: List[float],
    look_angle_rad: float,
    twist_inch: float,
    length_inch: float,
    diameter_inch: float,
    weight_grains: float,
    barrel_elevation_rad: float,
    barrel_azimuth_rad: float,
    sight_height_ft: float,
    cant_cosine: float,
    cant_sine: float,
    alt0_ft: float,
    muzzle_velocity_fps: float,
)

Shot configuration and parameters for ballistic trajectory calculations.

Contains all shot-specific data converted to internal units for high-performance ballistic calculations. This class serves as the computational interface between user-friendly Shot objects and the numerical integration engines.

The class pre-computes expensive calculations (ballistic coefficient curves, atmospheric data, projectile properties) and stores them in optimized formats for repeated use during trajectory integration. All values are converted to internal units (feet, seconds, grains) for computational efficiency.

Examples:

from py_ballisticcalc import Shot, ShotProps

# Create shot configuration
shot = Shot(weapon=weapon, ammo=ammo, atmo=atmo)

# Convert to ShotProps
shot_props = ShotProps.from_shot(shot)

# Access pre-computed values
print(f"Stability coefficient: {shot_props.stability_coefficient}")

# Get drag coefficient at specific Mach number
drag = shot_props.drag_by_mach(1.5)

# Calculate spin drift at flight time
time = 1.2  # seconds
drift = shot_props.spin_drift(time)  # inches

# Get atmospheric conditions at altitude
altitude = shot_props.alt0_ft + 100  # 100 feet above initial altitude
density_ratio, mach_fps = shot_props.get_density_and_mach_for_altitude(altitude)
Computational Optimizations
  • Drag coefficient curves pre-computed for fast interpolation
  • Trigonometric values (cant_cosine, cant_sine) pre-calculated
  • Atmospheric parameters cached for repeated altitude lookups
  • Miller stability coefficient computed once during initialization
Note

This class is designed for internal use by ballistic calculation engines. User code should typically work with Shot objects and let the Calculator handle the conversion to ShotProps automatically.

The original Shot object is retained for reference, but modifications to it after ShotProps creation will not affect the stored calculations. Create a new ShotProps instance if Shot parameters change.

Methods:

Name Description
from_shot

Initialize a ShotProps instance from a Shot instance.

get_density_and_mach_for_altitude

Get the air density and Mach number for a given altitude.

drag_by_mach

Calculate a standard drag factor (SDF) for the given Mach number.

spin_drift

Litz spin-drift approximation.

calculate_curve

Piecewise quadratic interpolation of drag curve.

Functions

from_shot classmethod
from_shot(shot: Shot) -> ShotProps

Initialize a ShotProps instance from a Shot instance.

Source code in py_ballisticcalc/conditions.py
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
@classmethod
def from_shot(cls, shot: Shot) -> ShotProps:
    """Initialize a ShotProps instance from a Shot instance."""
    return cls(
        shot=shot,
        bc=shot.ammo.dm.BC,
        curve=cls.calculate_curve(shot.ammo.dm.drag_table),
        mach_list=cls._get_only_mach_data(shot.ammo.dm.drag_table),
        look_angle_rad=shot.look_angle >> Angular.Radian,
        twist_inch=shot.weapon.twist >> Distance.Inch,
        length_inch=shot.ammo.dm.length >> Distance.Inch,
        diameter_inch=shot.ammo.dm.diameter >> Distance.Inch,
        weight_grains=shot.ammo.dm.weight >> Weight.Grain,
        barrel_elevation_rad=shot.barrel_elevation >> Angular.Radian,
        barrel_azimuth_rad=shot.barrel_azimuth >> Angular.Radian,
        sight_height_ft=shot.weapon.sight_height >> Distance.Foot,
        cant_cosine=math.cos(shot.cant_angle >> Angular.Radian),
        cant_sine=math.sin(shot.cant_angle >> Angular.Radian),
        alt0_ft=shot.atmo.altitude >> Distance.Foot,
        muzzle_velocity_fps=shot.ammo.get_velocity_for_temp(shot.atmo.powder_temp) >> Velocity.FPS,
    )
get_density_and_mach_for_altitude
get_density_and_mach_for_altitude(
    drop: float,
) -> Tuple[float, float]

Get the air density and Mach number for a given altitude.

Parameters:

Name Type Description Default
drop float

The change in feet from the initial altitude.

required

Returns:

Type Description
Tuple[float, float]

A tuple containing the air density (in lb/ft³) and Mach number at the specified altitude.

Source code in py_ballisticcalc/conditions.py
767
768
769
770
771
772
773
774
775
776
def get_density_and_mach_for_altitude(self, drop: float) -> Tuple[float, float]:
    """Get the air density and Mach number for a given altitude.

    Args:
        drop: The change in feet from the initial altitude.

    Returns:
        A tuple containing the air density (in lb/ft³) and Mach number at the specified altitude.
    """
    return self.shot.atmo.get_density_and_mach_for_altitude(self.alt0_ft + drop)
drag_by_mach
drag_by_mach(mach: float) -> float

Calculate a standard drag factor (SDF) for the given Mach number.

Formula:
    Drag force = V^2 * AirDensity * C_d * S / 2m
               = V^2 * density_ratio * SDF
Where:
    - density_ratio = LocalAirDensity / StandardDensity = rho / rho_0
    - StandardDensity of Air = rho_0 = 0.076474 lb/ft^3
    - S is cross-section = d^2 pi/4, where d is bullet diameter in inches
    - m is bullet mass in pounds
    - bc contains m/d^2 in units lb/in^2, which is multiplied by 144 to convert to lb/ft^2
Thus:
    - The magic constant found here = StandardDensity * pi / (4 * 2 * 144)

Parameters:

Name Type Description Default
mach float

The Mach number.

required

Returns:

Type Description
float

The standard drag factor at the given Mach number.

Source code in py_ballisticcalc/conditions.py
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
def drag_by_mach(self, mach: float) -> float:
    """Calculate a standard drag factor (SDF) for the given Mach number.
    ```
    Formula:
        Drag force = V^2 * AirDensity * C_d * S / 2m
                   = V^2 * density_ratio * SDF
    Where:
        - density_ratio = LocalAirDensity / StandardDensity = rho / rho_0
        - StandardDensity of Air = rho_0 = 0.076474 lb/ft^3
        - S is cross-section = d^2 pi/4, where d is bullet diameter in inches
        - m is bullet mass in pounds
        - bc contains m/d^2 in units lb/in^2, which is multiplied by 144 to convert to lb/ft^2
    Thus:
        - The magic constant found here = StandardDensity * pi / (4 * 2 * 144)
    ```

    Args:
        mach: The Mach number.

    Returns:
        The standard drag factor at the given Mach number.
    """
    # cd = calculate_by_curve(self._table_data, self._curve, mach)
    # use calculation over list[double] instead of list[DragDataPoint]
    cd = self._calculate_by_curve_and_mach_list(self.mach_list, self.curve, mach)
    return cd * 2.08551e-04 / self.bc
spin_drift
spin_drift(time: float) -> float

Litz spin-drift approximation.

Parameters:

Name Type Description Default
time float

Time of flight

required

Returns:

Name Type Description
float float

Windage due to spin drift, in inches

Source code in py_ballisticcalc/conditions.py
805
806
807
808
809
810
811
812
813
814
815
816
817
818
def spin_drift(self, time: float) -> float:
    """Litz spin-drift approximation.

    Args:
        time: Time of flight

    Returns:
        float: Windage due to spin drift, in inches
    """
    if (self.stability_coefficient != 0) and (self.twist_inch != 0):
        sign = 1 if self.twist_inch > 0 else -1
        return sign * (1.25 * (self.stability_coefficient + 1.2)
                       * math.pow(time, 1.83)) / 12
    return 0
calculate_curve staticmethod
calculate_curve(
    data_points: List[DragDataPoint],
) -> List[CurvePoint]

Piecewise quadratic interpolation of drag curve.

Parameters:

Name Type Description Default
data_points List[DragDataPoint]

List[{Mach, CD}] data_points in ascending Mach order

required

Returns:

Type Description
List[CurvePoint]

List[CurvePoints] to interpolate drag coefficient

Source code in py_ballisticcalc/conditions.py
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
@staticmethod
def calculate_curve(data_points: List[DragDataPoint]) -> List[CurvePoint]:
    """Piecewise quadratic interpolation of drag curve.

    Args:
        data_points: List[{Mach, CD}] data_points in ascending Mach order

    Returns:
        List[CurvePoints] to interpolate drag coefficient
    """
    rate = (data_points[1].CD - data_points[0].CD) / (data_points[1].Mach - data_points[0].Mach)
    curve = [CurvePoint(0, rate, data_points[0].CD - data_points[0].Mach * rate)]
    len_data_points = int(len(data_points))
    len_data_range = len_data_points - 1

    for i in range(1, len_data_range):
        x1 = data_points[i - 1].Mach
        x2 = data_points[i].Mach
        x3 = data_points[i + 1].Mach
        y1 = data_points[i - 1].CD
        y2 = data_points[i].CD
        y3 = data_points[i + 1].CD
        a = ((y3 - y1) * (x2 - x1) - (y2 - y1) * (x3 - x1)) / (
            (x3 * x3 - x1 * x1) * (x2 - x1) - (x2 * x2 - x1 * x1) * (x3 - x1))
        b = (y2 - y1 - a * (x2 * x2 - x1 * x1)) / (x2 - x1)
        c = y1 - (a * x1 * x1 + b * x1)
        curve_point = CurvePoint(a, b, c)
        curve.append(curve_point)

    num_points = len_data_points
    rate = (data_points[num_points - 1].CD - data_points[num_points - 2].CD) / \
        (data_points[num_points - 1].Mach - data_points[num_points - 2].Mach)
    curve_point = CurvePoint(
        0, rate, data_points[num_points - 1].CD - data_points[num_points - 2].Mach * rate
    )
    curve.append(curve_point)
    return curve