Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incomplete shot handling #154

Merged
merged 17 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1c278e8
Addition of recording of the trajectory interruption point for RangeE…
serhiy-yevtushenko Jan 27, 2025
6e71070
Changed TestDangerSpace to pass.
serhiy-yevtushenko Jan 27, 2025
d78797f
Added test for checking the addition of last point to HitResult.traje…
serhiy-yevtushenko Jan 27, 2025
ec45133
Refactored test and added test_vertical_shot
serhiy-yevtushenko Jan 27, 2025
44a0268
Added changes as well to cython version
serhiy-yevtushenko Jan 27, 2025
da7e6b1
Changed to cythonic version of call of create_trajectory_row
serhiy-yevtushenko Jan 27, 2025
b227024
Added hash method to AbstractDimension. This allows using TrajectoryD…
serhiy-yevtushenko Jan 27, 2025
c00f180
Removed check for time point being different from last point time, as…
serhiy-yevtushenko Jan 27, 2025
f8c5e58
Fix for https://github.com/o-murphy/py-ballisticcalc/issues/150
serhiy-yevtushenko Jan 27, 2025
81317ea
Change handling of end_point inclusion through changing logic of rang…
serhiy-yevtushenko Jan 28, 2025
fd7218e
Fix of cython errors
serhiy-yevtushenko Jan 28, 2025
61f56c7
Removed commented out print statement and dependency on numpy call
serhiy-yevtushenko Jan 28, 2025
4860ced
_TrajectoryDataFilter patch
o-murphy Jan 28, 2025
7fb59b6
Renamed step to record_step, added comment
serhiy-yevtushenko Jan 28, 2025
a382947
Merge branch 'incomplete_shot_handling' of https://github.com/serhiy-…
serhiy-yevtushenko Jan 28, 2025
f47a427
Removed ranges_length and current_item members in _TrajectoryDataFilt…
serhiy-yevtushenko Jan 29, 2025
806bb7c
bump to v2.1.0b5
o-murphy Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 18 additions & 19 deletions py_ballisticcalc.exts/py_ballisticcalc_exts/trajectory_calc.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,16 @@ __all__ = (
cdef class _TrajectoryDataFilter:
cdef:
int filter, current_flag, seen_zero
int current_item, ranges_length
double previous_mach, previous_time, next_range_distance, time_step, look_angle
int current_item
double previous_mach, previous_time, next_range_distance, range_step, time_step, look_angle

def __cinit__(_TrajectoryDataFilter self,
int filter_flags, int ranges_length, double time_step = 0.0) -> None:
int filter_flags, double range_step, double time_step = 0.0) -> None:
self.filter = filter_flags
self.current_flag = CTrajFlag.NONE
self.seen_zero = CTrajFlag.NONE
self.time_step = time_step
self.current_item = 0
self.ranges_length = ranges_length
self.range_step = range_step
self.previous_mach = 0.0
self.previous_time = 0.0
self.next_range_distance = 0.0
Expand All @@ -82,26 +81,21 @@ cdef class _TrajectoryDataFilter:
CVector range_vector,
double velocity,
double mach,
double step,
double time,
):
self.check_zero_crossing(range_vector)
self.check_mach_crossing(velocity, mach)
if self.check_next_range(range_vector.x, step):
if self.check_next_range(range_vector.x, self.range_step):
self.previous_time = time
elif self.time_step > 0:
self.check_next_time(time)
return (self.current_flag & self.filter) != 0

cdef bint should_break(_TrajectoryDataFilter self):
return self.current_item == self.ranges_length

cdef bint check_next_range(_TrajectoryDataFilter self, double next_range, double step):
# Next range check
if next_range >= self.next_range_distance:
self.current_flag |= CTrajFlag.RANGE
self.next_range_distance += step
self.current_item += 1
return True
return False

Expand Down Expand Up @@ -314,7 +308,7 @@ cdef class TrajectoryCalc:


cdef list[object] _integrate(TrajectoryCalc self,
double maximum_range, double step, int filter_flags, double time_step = 0.0):
double maximum_range, double record_step, int filter_flags, double time_step = 0.0):
cdef:
double velocity, delta_time
double density_factor = .0
Expand Down Expand Up @@ -353,16 +347,17 @@ cdef class TrajectoryCalc:
velocity_vector = mul_c(&_dir_vector, velocity)
# endregion

min_step = min(calc_step, record_step)
# With non-zero look_angle, rounding can suggest multiple adjacent zero-crossings
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags,
ranges_length=<int> ((maximum_range / step) + 1),
range_step=record_step,
time_step=time_step)
data_filter.setup_seen_zero(range_vector.y, self.__shot.barrel_elevation, self.__shot.look_angle)

#region Trajectory Loop
warnings.simplefilter("once") # used to avoid multiple warnings in a loop
cdef int it = 0
while range_vector.x <= maximum_range + calc_step:
while range_vector.x <= maximum_range + min_step:
it += 1
data_filter.current_flag = CTrajFlag.NONE

Expand All @@ -377,15 +372,13 @@ cdef class TrajectoryCalc:
if filter_flags:

# Record TrajectoryData row
# if data_filter.should_record(range_vector, velocity, mach, step, self.look_angle, time):
if data_filter.should_record(range_vector, velocity, mach, step, time):
# if data_filter.should_record(range_vector, velocity, mach, record_step, self.look_angle, time):
if data_filter.should_record(range_vector, velocity, mach, time):
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, cy_spin_drift(&self.__shot, time), self.__shot.look_angle,
density_factor, drag, self.__shot.weight, data_filter.current_flag
))
if data_filter.should_break():
break

#region Ballistic calculation step
# use just cdef methods to
Expand All @@ -412,6 +405,12 @@ cdef class TrajectoryCalc:
or range_vector.y < _cMaximumDrop
or self.__shot.alt0 + range_vector.y < _cMinimumAltitude
):
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, cy_spin_drift(&self.__shot, time), self.__shot.look_angle,
density_factor, drag, self.__shot.weight, data_filter.current_flag
))

if velocity < _cMinimumVelocity:
reason = RangeError.MinimumVelocityReached
elif range_vector.y < _cMaximumDrop:
Expand All @@ -423,7 +422,7 @@ cdef class TrajectoryCalc:

#endregion
# If filter_flags == 0 then all we want is the ending value
if not filter_flags:
if len(ranges)==0:
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, cy_spin_drift(&self.__shot, time), self.__shot.look_angle,
Expand Down
2 changes: 1 addition & 1 deletion py_ballisticcalc.exts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "py_ballisticcalc.exts"
version = "2.1.0b4"
version = "2.1.0b5"

authors = [
{ name="o-murphy", email="[email protected]" },
Expand Down
50 changes: 25 additions & 25 deletions py_ballisticcalc/trajectory_calc/_trajectory_calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,19 @@ class _TrajectoryDataFilter:
filter: Union[TrajFlag, int]
current_flag: Union[TrajFlag, int]
seen_zero: Union[TrajFlag, int]
current_item: int
ranges_length: int
time_step: float
range_step: float
previous_mach: float
next_range_distance: float
look_angle: float

def __init__(self, filter_flags: Union[TrajFlag, int],
ranges_length: int, time_step: float = 0.0):
def __init__(self, filter_flags: Union[TrajFlag, int], range_step: float, time_step: float = 0.0):
"""If a time_step is indicated, then we will record a row at least that often in the trajectory"""
self.filter: Union[TrajFlag, int] = filter_flags
self.current_flag: Union[TrajFlag, int] = TrajFlag.NONE
self.seen_zero: Union[TrajFlag, int] = TrajFlag.NONE
self.time_step = time_step
self.current_item: int = 0
self.ranges_length: int = ranges_length
self.time_step: float = time_step
self.range_step: float = range_step
self.previous_mach: float = 0.0
self.previous_time: float = 0.0
self.next_range_distance: float = 0.0
Expand All @@ -86,19 +84,15 @@ def should_record(self,
range_vector: Vector,
velocity: float,
mach: float,
step: float,
time: float) -> bool:
self.check_zero_crossing(range_vector)
self.check_mach_crossing(velocity, mach)
if self.check_next_range(range_vector.x, step):
if self.check_next_range(range_vector.x, self.range_step):
self.previous_time = time
elif self.time_step > 0:
self.check_next_time(time)
return bool(self.current_flag & self.filter)

def should_break(self) -> bool:
return self.current_item == self.ranges_length

def check_next_range(self, next_range: float, step: float) -> bool:
"""
If we passed the next_range point, set the RANGE flag and update the next_range_distance
Expand All @@ -107,7 +101,6 @@ def check_next_range(self, next_range: float, step: float) -> bool:
if next_range >= self.next_range_distance:
self.current_flag |= TrajFlag.RANGE
self.next_range_distance += step
self.current_item += 1
return True
return False

Expand Down Expand Up @@ -221,7 +214,7 @@ def trajectory(self, shot_info: Shot, max_range: Distance, dist_step: Distance,
filter_flags = TrajFlag.RANGE

if extra_data:
dist_step = Distance.Foot(self._config.chart_resolution)
# dist_step = Distance.Foot(self._config.chart_resolution)
filter_flags = TrajFlag.ALL

self._init_trajectory(shot_info)
Expand Down Expand Up @@ -286,11 +279,11 @@ def zero_angle(self, shot_info: Shot, distance: Distance) -> Angular:
raise ZeroFindingError(zero_finding_error, iterations_count, Angular.Radian(self.barrel_elevation))
return Angular.Radian(self.barrel_elevation)

def _integrate(self, shot_info: Shot, maximum_range: float, step: float,
def _integrate(self, shot_info: Shot, maximum_range: float, record_step: float,
filter_flags: Union[TrajFlag, int], time_step: float = 0.0) -> List[TrajectoryData]:
"""Calculate trajectory for specified shot
:param maximum_range: Feet down range to stop calculation
:param step: Frequency (in feet down range) to record TrajectoryData
:param record_step: Frequency (in feet down range) to record TrajectoryData
:param time_step: If > 0 then record TrajectoryData after this many seconds elapse
since last record, as could happen when trajectory is nearly vertical
and there is too little movement downrange to trigger a record based on range.
Expand Down Expand Up @@ -325,16 +318,17 @@ def _integrate(self, shot_info: Shot, maximum_range: float, step: float,
).mul_by_const(velocity) # type: ignore
# endregion

# min step is used to handle situation, when record step is smaller than calc_step
# in order to prevent range breaking too early
min_step = min(self.calc_step, record_step)
# With non-zero look_angle, rounding can suggest multiple adjacent zero-crossings
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags,
ranges_length=int(maximum_range / step) + 1,
time_step=time_step)
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags, range_step=record_step, time_step=time_step)
data_filter.setup_seen_zero(range_vector.y, self.barrel_elevation, self.look_angle)

# region Trajectory Loop
warnings.simplefilter("once") # used to avoid multiple warnings in a loop
it = 0
while range_vector.x <= maximum_range + self.calc_step:
while range_vector.x <= maximum_range + min_step:
it += 1
data_filter.clear_current_flag()

Expand All @@ -350,14 +344,13 @@ def _integrate(self, shot_info: Shot, maximum_range: float, step: float,
if filter_flags: # require check before call to improve performance

# Record TrajectoryData row
if data_filter.should_record(range_vector, velocity, mach, step, time):
if data_filter.should_record(range_vector, velocity, mach, time):
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, self.spin_drift(time), self.look_angle,
density_factor, drag, self.weight, data_filter.current_flag
))
if data_filter.should_break():
break

# endregion

# region Ballistic calculation step (point-mass)
Expand All @@ -382,6 +375,11 @@ def _integrate(self, shot_info: Shot, maximum_range: float, step: float,
or range_vector.y < _cMaximumDrop
or self.alt0 + range_vector.y < _cMinimumAltitude
):
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, self.spin_drift(time), self.look_angle,
density_factor, drag, self.weight, data_filter.current_flag
))
if velocity < _cMinimumVelocity:
reason = RangeError.MinimumVelocityReached
elif range_vector.y < _cMaximumDrop:
Expand All @@ -393,8 +391,10 @@ def _integrate(self, shot_info: Shot, maximum_range: float, step: float,
# endregion

# endregion
# If filter_flags == 0 then all we want is the ending value
if not filter_flags:
# this check supercedes not filter_flags - as it will be true in any condition
# if not filter_flags:
if len(ranges)==0:
# we need to add end value in case of not filter flag and in case of usual trajectory
ranges.append(create_trajectory_row(
time, range_vector, velocity_vector,
velocity, mach, self.spin_drift(time), self.look_angle,
Expand Down
3 changes: 3 additions & 0 deletions py_ballisticcalc/unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ def __float__(self):
def __eq__(self, other):
return float(self) == other

def __hash__(self):
return hash((self._value, self._defined_units))

def __lt__(self, other):
return float(self) < other

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "py_ballisticcalc"
version = "2.1.0b4"
version = "2.1.0b5"

authors = [
{ name="o-murphy", email="[email protected]" },
Expand Down Expand Up @@ -50,7 +50,7 @@ include = ["py_ballisticcalc*"]


[project.optional-dependencies]
exts = ['py_ballisticcalc.exts==2.1.0b4']
exts = ['py_ballisticcalc.exts==2.1.0b5']
charts = ['matplotlib', 'pandas']
visualize = ['matplotlib', 'pandas']
dev = [
Expand Down
72 changes: 72 additions & 0 deletions tests/fixtures_and_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pytest

from py_ballisticcalc import HitResult, Distance, InterfaceConfigDict, Calculator, DragModel, TableG1, Weight, Ammo, \
Velocity, Weapon, Shot, Angular


def print_out_trajectory_compact(hit_result: HitResult, label="", distance_unit: Distance = Distance.Meter,
top_k: int = 5):
trajectory_length = len(hit_result.trajectory)
if label:
print(f'{label}: Length of trajectory: { trajectory_length=}')
else:
print(f'Length of trajectory: { trajectory_length=}')


trajectory = hit_result.trajectory
if top_k<trajectory_length:
end_start_top_k = top_k
start_end_top_k = trajectory_length - top_k-1
if end_start_top_k<start_end_top_k:
trajectory = trajectory[:end_start_top_k]+trajectory[start_end_top_k:]

for i, p in enumerate(trajectory):
if i<top_k:
index_to_print = i+1
else:
index_to_print = (trajectory_length-top_k)+i-top_k
if i==top_k and i!=trajectory_length-(top_k+1):
print("...")
print(f'{index_to_print}. ({p.distance>>distance_unit}, {p.height>>distance_unit})')


@pytest.fixture()
def zero_height_calc():
config = InterfaceConfigDict(
cMinimumVelocity=0,
cMinimumAltitude=Distance.Meter(0),
cMaximumDrop=Distance.Meter(0),
)
calc = Calculator(_config=config)
return calc


def create_shot():
drag_model = DragModel(
bc=0.759,
drag_table=TableG1,
weight=Weight.Gram(108),
diameter=Distance.Millimeter(23),
length=Distance.Millimeter(108.2),
)
ammo = Ammo(drag_model, Velocity.MPS(930))
gun = Weapon()
shot = Shot(
weapon=gun,
ammo=ammo,
)
return shot


def shot_with_relative_angle_in_degrees(angle_in_degrees: float):
shot = create_shot()
shot.relative_angle = Angular.Degree(angle_in_degrees)
return shot


@pytest.fixture()
def zero_min_velocity_calc():
config = InterfaceConfigDict(
cMinimumVelocity=0,
)
return Calculator(_config=config)
2 changes: 1 addition & 1 deletion tests/test_danger_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def setUp(self) -> None:
shot = Shot(weapon=Weapon(), ammo=ammo, winds=current_winds)
calc = Calculator()
calc.set_weapon_zero(shot, Distance.Foot(300))
self.shot_result = calc.fire(shot, trajectory_range=Distance.Yard(1000), trajectory_step=Distance.Yard(100), extra_data=True)
self.shot_result = calc.fire(shot, trajectory_range=Distance.Yard(1000), trajectory_step=Distance.Yard(1), extra_data=True)

def test_danger_space(self):
danger_space = self.shot_result.danger_space(
Expand Down
2 changes: 1 addition & 1 deletion tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def test_find_index_for_distance(one_degree_shot):
)
# for reproducibility
random.seed(42)
random_indices = random.sample(range(len(shot.trajectory)), 100)
random_indices = random.sample(range(len(shot.trajectory)), min(100, len(shot.trajectory)))
start_time = time.time()

for i in random_indices:
Expand Down
Loading
Loading