Skip to content

Commit

Permalink
refactoring, details:
Browse files Browse the repository at this point in the history
* updated Unit's __call__ method
* removed is_unit function
* trajectory step removed from the shot attributes? now it required for the Calculator.fire()
  • Loading branch information
o-murphy committed Oct 12, 2023
1 parent 99ffed0 commit f9079ac
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 85 deletions.
10 changes: 4 additions & 6 deletions py_ballisticcalc/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .munition import Weapon, Ammo
# pylint: disable=import-error,no-name-in-module
from .trajectory_calc import TrajectoryCalc
from .trajectory_data import HitResult, TrajFlag
from .trajectory_data import HitResult
from .unit import Angular, Distance
from .settings import Settings

Expand Down Expand Up @@ -42,21 +42,19 @@ def calculate_elevation(self):
self._elevation = self._calc.zero_angle(self.weapon, self.zero_atmo)

def fire(self, shot: Shot, trajectory_step: [float, Distance],
filter_flags: TrajFlag = TrajFlag.RANGE) -> HitResult:
extra_data: bool = False) -> HitResult:
"""Calculates trajectory with current conditions
:param shot: shot parameters
:param trajectory_step: step between trajectory points
:param filter_flags: filter trajectory points
:return: trajectory table
"""
step = Settings.Units.distance(trajectory_step)
if filter_flags & (TrajFlag.ZERO | TrajFlag.MACH):
step = Distance.Foot(0.2)
self._calc = TrajectoryCalc(self.ammo)
if not shot.zero_angle:
shot.zero_angle = self._elevation
data = self._calc.trajectory(self.weapon, shot, step, filter_flags.value)
return HitResult(data)
data = self._calc.trajectory(self.weapon, shot, step, extra_data)
return HitResult(data, extra_data)

# @staticmethod
# def danger_space(trajectory: TrajectoryData, target_height: [float, Distance]) -> Distance:
Expand Down
4 changes: 4 additions & 0 deletions py_ballisticcalc/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Global settings of the py_ballisticcalc library"""
import logging

from .unit import Unit, Distance

Expand Down Expand Up @@ -35,6 +36,9 @@ def set_max_calc_step_size(cls, value: [float, Distance]):
"""_MAX_CALC_STEP_SIZE setter
:param value: [float, Distance] maximum calculation step (used internally)
"""
logging.warning("Settings._MAX_CALC_STEP_SIZE: change this property "
"only if you know what you are doing "
"to big step can corrupt calculation accuracy")
if not isinstance(value, (Distance, float, int)):
raise ValueError("MIN_CALC_STEP_SIZE have to be a type of 'Distance'")
cls._MAX_CALC_STEP_SIZE = cls.Units.distance(value) >> Distance.Foot
13 changes: 11 additions & 2 deletions py_ballisticcalc/trajectory_calc.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cimport cython
from .conditions import *
from .munition import *
from .settings import Settings
from .trajectory_data import TrajectoryData
from .trajectory_data import TrajectoryData, TrajFlag
from .unit import *

__all__ = ('TrajectoryCalc',)
Expand All @@ -24,6 +24,9 @@ cdef enum CTrajFlag:
ZERO_DOWN = 2
MACH = 4
RANGE = 8
DANGER = 16
ZERO = ZERO_UP | ZERO_DOWN
ALL = RANGE | ZERO_UP | ZERO_DOWN | MACH | DANGER

cdef class Vector:
cdef double x
Expand Down Expand Up @@ -125,11 +128,17 @@ cdef class TrajectoryCalc:
return self._zero_angle(self.ammo, weapon, atmo)

def trajectory(self, weapon: Weapon, shot_info: Shot, step: [float, Distance],
filter_flags: int = CTrajFlag.RANGE):
extra_data: bool = False):
cdef:
object dist_step = Settings.Units.distance(step)
object atmo = shot_info.atmo
list winds = shot_info.winds
CTrajFlag filter_flags = CTrajFlag.RANGE

if extra_data:
print('ext', extra_data)
dist_step = Distance.Foot(0.2)
filter_flags = CTrajFlag.ALL
return self._trajectory(self.ammo, weapon, atmo, shot_info, winds, dist_step, filter_flags)

cdef _zero_angle(TrajectoryCalc self, object ammo, object weapon, object atmo):
Expand Down
168 changes: 113 additions & 55 deletions py_ballisticcalc/trajectory_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Implements a point of trajectory class in applicable data types"""
import logging
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Flag
from typing import NamedTuple

Expand All @@ -21,7 +21,8 @@
matplotlib = None

__all__ = ('TrajectoryData', 'HitResult', 'TrajFlag',
'trajectory_plot', 'trajectory_dataframe')
# 'trajectory_plot', 'trajectory_dataframe'
)


class TrajFlag(Flag):
Expand All @@ -33,8 +34,9 @@ class TrajFlag(Flag):
ZERO_DOWN = 2
MACH = 4
RANGE = 8
DANGER = 16
ZERO = ZERO_UP | ZERO_DOWN
ALL = RANGE | ZERO_UP | ZERO_DOWN | MACH
ALL = RANGE | ZERO_UP | ZERO_DOWN | MACH | DANGER


class TrajectoryData(NamedTuple):
Expand Down Expand Up @@ -115,66 +117,122 @@ def in_def_units(self) -> tuple:
)


@dataclass
@dataclass(frozen=True)
class HitResult:
"""Results of the shot"""
_trajectory: list[TrajectoryData]
trajectory: list[TrajectoryData] = field(repr=False)
extra: bool = False

def __iter__(self):
for row in self._trajectory:
for row in self.trajectory:
yield row

def __getitem__(self, item):
return self._trajectory[item]

def zero_given_elevation(self) -> list[TrajectoryData]:
"""Find the zero distance for a given barrel elevation"""
data = [row for row in self._trajectory if row.flag & TrajFlag.ZERO.value]
return self.trajectory[item]

def __check_extra__(self):
if not self.extra:
raise AttributeError(
f"{object.__repr__(self)} has no extra data. "
f"Use Calculator.fire(..., extra_data=True)"
)

def zeros(self) -> list[TrajectoryData]:
""":return: zero crossing points"""
self.__check_extra__()
data = [row for row in self.trajectory if row.flag & TrajFlag.ZERO.value]
if len(data) < 1:
raise ArithmeticError("Can't found zero crossing points")
return data


def trajectory_dataframe(shot_result: 'HitResult') -> 'DataFrame':
""":return: the trajectory table as a DataFrame"""
if pd is None:
raise ImportError("Install pandas to convert trajectory as dataframe")

col_names = TrajectoryData._fields
trajectory = [p.in_def_units() for p in shot_result]
return pd.DataFrame(trajectory, columns=col_names)


def trajectory_plot(calc: 'Calculator', shot: 'Shot') -> 'plot':
""":return: the graph of the trajectory"""

if matplotlib is None:
raise ImportError("Install matplotlib to get results as a plot")

matplotlib.use('TkAgg')
shot_result = calc.fire(shot, Distance.Foot(0.2), TrajFlag.ALL)
df = trajectory_dataframe(shot_result)
ax = df.plot(x='distance', y=['drop'], ylabel=Set.Units.drop.symbol)

for p in shot_result:

if TrajFlag(p.flag) & TrajFlag.ZERO:
ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
[df['drop'].min(), p.drop >> Set.Units.drop], linestyle=':')
if TrajFlag(p.flag) & TrajFlag.MACH:
ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
[df['drop'].min(), p.drop >> Set.Units.drop], linestyle='--', label='mach')
ax.text(p.distance >> Set.Units.distance, df['drop'].min(), " Mach")

# # scope line
x_values = [0, df.distance.max()] # Adjust these as needed
y_values = [0, 0] # Adjust these as needed
ax.plot(x_values, y_values, linestyle='--', label='scope line')
ax.text(df.distance.max() - 100, -100, "Scope")

df.plot(x='distance', xlabel=Set.Units.distance.symbol,
y=['velocity'], ylabel=Set.Units.velocity.symbol,
secondary_y=True,
ylim=[0, df['velocity'].max()], ax=ax)

return plt
@property
def dataframe(self):
""":return: the trajectory table as a DataFrame"""
if pd is None:
raise ImportError("Install pandas to convert trajectory as dataframe")
self.__check_extra__()
col_names = list(TrajectoryData._fields)
trajectory = [p.in_def_units() for p in self]
return pd.DataFrame(trajectory, columns=col_names)

@property
def plot(self):
""":return: the graph of the trajectory"""

if matplotlib is None:
raise ImportError("Install matplotlib to get results as a plot")
if not self.extra:
logging.warning("HitResult.plot: To show extended data"
"Use Calculator.fire(..., extra_data=True)")
matplotlib.use('TkAgg')

df = self.dataframe
ax = df.plot(x='distance', y=['drop'], ylabel=Set.Units.drop.symbol)

for p in self:

if TrajFlag(p.flag) & TrajFlag.ZERO:
ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
[df['drop'].min(), p.drop >> Set.Units.drop], linestyle=':')
if TrajFlag(p.flag) & TrajFlag.MACH:
ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
[df['drop'].min(), p.drop >> Set.Units.drop], linestyle='--', label='mach')
ax.text(p.distance >> Set.Units.distance, df['drop'].min(), " Mach")

# # scope line
x_values = [0, df.distance.max()] # Adjust these as needed
y_values = [0, 0] # Adjust these as needed
ax.plot(x_values, y_values, linestyle='--', label='scope line')
ax.text(df.distance.max() - 100, -100, "Scope")

df.plot(x='distance', xlabel=Set.Units.distance.symbol,
y=['velocity'], ylabel=Set.Units.velocity.symbol,
secondary_y=True,
ylim=[0, df['velocity'].max()], ax=ax)

return plt


# def trajectory_dataframe(shot_result: 'HitResult') -> 'DataFrame':
# """:return: the trajectory table as a DataFrame"""
# if pd is None:
# raise ImportError("Install pandas to convert trajectory as dataframe")
#
# col_names = TrajectoryData._fields
# trajectory = [p.in_def_units() for p in shot_result]
# return pd.DataFrame(trajectory, columns=col_names)


# def trajectory_plot(calc: 'Calculator', shot: 'Shot') -> 'plot':
# """:return: the graph of the trajectory"""
#
# if matplotlib is None:
# raise ImportError("Install matplotlib to get results as a plot")
#
# matplotlib.use('TkAgg')
# shot_result = calc.fire(shot, Distance.Foot(0.2), True)
# df = trajectory_dataframe(shot_result)
# ax = df.plot(x='distance', y=['drop'], ylabel=Set.Units.drop.symbol)
#
# for p in shot_result:
#
# if TrajFlag(p.flag) & TrajFlag.ZERO:
# ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
# [df['drop'].min(), p.drop >> Set.Units.drop], linestyle=':')
# if TrajFlag(p.flag) & TrajFlag.MACH:
# ax.plot([p.distance >> Set.Units.distance, p.distance >> Set.Units.distance],
# [df['drop'].min(), p.drop >> Set.Units.drop], linestyle='--', label='mach')
# ax.text(p.distance >> Set.Units.distance, df['drop'].min(), " Mach")
#
# # # scope line
# x_values = [0, df.distance.max()] # Adjust these as needed
# y_values = [0, 0] # Adjust these as needed
# ax.plot(x_values, y_values, linestyle='--', label='scope line')
# ax.text(df.distance.max() - 100, -100, "Scope")
#
# df.plot(x='distance', xlabel=Set.Units.distance.symbol,
# y=['velocity'], ylabel=Set.Units.velocity.symbol,
# secondary_y=True,
# ylim=[0, df['velocity'].max()], ax=ax)
#
# return plt
3 changes: 1 addition & 2 deletions tests/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@
calc.calculate_elevation()

shot = Shot(1200, zero_angle=calc.elevation, relative_angle=Angular.Mil(0))
shot_results = calc.fire(shot, Distance.Foot(0.2), TrajFlag.ALL)
trajectory_plot(calc, shot).show()
calc.fire(shot, 0, extra_data=True).plot.show()

41 changes: 21 additions & 20 deletions tests/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def setUp(self) -> None:
self.ammo = Ammo(dm, 1.22, Velocity(2600, Velocity.FPS))
self.atmosphere = Atmo.icao()

@unittest.skip(reason="Deprecated: zero_given_elevation")
def test_zero_given(self):
# pylint: disable=consider-using-f-string
output_fmt = "elev: {}\tscope: {}\tzero: {} {}\ttarget: {}\tdistance: {}\tdrop: {}"
Expand Down Expand Up @@ -113,24 +114,24 @@ def print_output(data, at_elevation):
if err == "Can't found zero crossing points":
pass

# # @unittest.skip(reason="Not implemented: danger_space")
# def test_danger_space(self):
# zero_distance = Distance.Yard(100)
# weapon = Weapon(Distance.Inch(4), zero_distance, 11.24)
# calc = Calculator(weapon, self.ammo, self.atmosphere)
# calc.calculate_elevation()
# shot = Shot(1000, Distance.Foot(0.2), zero_angle=calc.elevation)
# shot_result = calc.fire(shot)
# zero_given_elevation = shot_result.zero_given_elevation()
# if len(zero_given_elevation) > 0:
# zero = [p for p in zero_given_elevation if abs(
# (p.distance >> Distance.Yard) - (zero_distance >> Distance.Yard)
# ) <= 1e7][0]
# else:
# raise ArithmeticError
# print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Meter(1.7)) << Distance.Meter)
# print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Meter(1.5)) << Distance.Meter)
# print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Inch(10)) << Distance.Yard)
@unittest.skip(reason="Not implemented: danger_space")
def test_danger_space(self):
zero_distance = Distance.Yard(100)
weapon = Weapon(Distance.Inch(4), zero_distance, 11.24)
calc = Calculator(weapon, self.ammo, self.atmosphere)
calc.calculate_elevation()
shot = Shot(1000, Distance.Foot(0.2), zero_angle=calc.elevation)
shot_result = calc.fire(shot)
zero_given_elevation = shot_result.zero_given_elevation()
if len(zero_given_elevation) > 0:
zero = [p for p in zero_given_elevation if abs(
(p.distance >> Distance.Yard) - (zero_distance >> Distance.Yard)
) <= 1e7][0]
else:
raise ArithmeticError
print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Meter(1.7)) << Distance.Meter)
print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Meter(1.5)) << Distance.Meter)
print(zero.distance << Distance.Yard, calc.danger_space(zero, Distance.Inch(10)) << Distance.Yard)


class TestTrajectory(unittest.TestCase):
Expand Down Expand Up @@ -206,7 +207,7 @@ def test_path_g1(self):
atmo=atmosphere,
winds=[Wind(Velocity(5, Velocity.MPH), Angular(10.5, Angular.OClock))])
calc = TrajectoryCalc(ammo)
data = calc.trajectory(weapon, shot_info, Distance.Yard(100), TrajFlag.RANGE.value)
data = calc.trajectory(weapon, shot_info, Distance.Yard(100))

self.custom_assert_equal(len(data), 11, 0.1, "Length")

Expand All @@ -230,7 +231,7 @@ def test_path_g7(self):
winds=[Wind(Velocity(5, Velocity.MPH), -45)])

calc = TrajectoryCalc(ammo)
data = calc.trajectory(weapon, shot_info, Distance.Yard(100), TrajFlag.RANGE.value)
data = calc.trajectory(weapon, shot_info, Distance.Yard(100))

self.custom_assert_equal(len(data), 11, 0.1, "Length")

Expand Down

0 comments on commit f9079ac

Please sign in to comment.