Skip to content

Commit db1bd51

Browse files
committed
Deprecating extra_data flag.
* TrajectoryDataFilter always records initial TrajectoryData row. * Engines always return at least 2 points if they can calculate even one step.
1 parent 6ace930 commit db1bd51

10 files changed

Lines changed: 43 additions & 38 deletions

File tree

py_ballisticcalc.exts/py_ballisticcalc_exts/base_engine.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ cdef BaseTrajData TrajDataFilter_t_should_record(TrajDataFilter_t * tdf, const V
100100
_check_mach_crossing(tdf, mag(velocity_ptr), mach)
101101
if tdf.filter & TrajFlag_t.APEX:
102102
_check_apex(tdf, velocity_ptr)
103-
if (tdf.current_flag & tdf.filter) != 0 and data is None:
103+
if ((tdf.current_flag & tdf.filter) != 0 and data is None) or time==0:
104104
data = BaseTrajData(time=time, position=position_ptr[0],
105105
velocity=velocity_ptr[0], mach=mach)
106106
tdf.previous_time = time

py_ballisticcalc.exts/py_ballisticcalc_exts/euler_engine.pyx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ cdef class CythonizedEulerIntegrationEngine(CythonizedBaseIntegrationEngine):
9494
velocity_vector = mulS(&_tv, velocity)
9595
# endregion
9696

97-
min_step = fmin(calc_step, range_step_ft)
9897
# With non-zero look_angle, rounding can suggest multiple adjacent zero-crossings
9998
data_filter = TrajDataFilter_t_create(filter_flags=filter_flags, range_step=range_step_ft,
10099
initial_position_ptr=&range_vector,
@@ -107,7 +106,7 @@ cdef class CythonizedEulerIntegrationEngine(CythonizedBaseIntegrationEngine):
107106
termination_reason = None
108107
last_recorded_range = 0.0
109108

110-
while (range_vector.x <= range_limit_ft + min_step) or (
109+
while (range_vector.x <= range_limit_ft) or (
111110
last_recorded_range <= range_limit_ft - 1e-6):
112111
self.integration_step_count += 1
113112

@@ -169,9 +168,9 @@ cdef class CythonizedEulerIntegrationEngine(CythonizedBaseIntegrationEngine):
169168
data.time, &data.position, &data.velocity, data.mach,
170169
&self._shot_s, density_ratio, drag, data_filter.current_flag
171170
))
172-
# Ensure that we have at least two data points in trajectory, or 1 if filter_flags==NONE
171+
# Ensure that we have at least two data points in trajectory
173172
# ... as well as last point if we had an incomplete trajectory
174-
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 0:
173+
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 1:
175174
if len(ranges) > 0 and ranges[-1].time == time: # But don't duplicate the last point.
176175
pass
177176
else:

py_ballisticcalc.exts/py_ballisticcalc_exts/rk4_engine.pyx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ cdef class CythonizedRK4IntegrationEngine(CythonizedBaseIntegrationEngine):
9898
velocity_vector = mulS(&_dir_vector, velocity)
9999
# endregion
100100

101-
min_step = fmin(calc_step, range_step_ft)
102101
# With non-zero look_angle, rounding can suggest multiple adjacent zero-crossings
103102
data_filter = TrajDataFilter_t_create(filter_flags=filter_flags, range_step=range_step_ft,
104103
initial_position_ptr=&range_vector,
@@ -111,7 +110,7 @@ cdef class CythonizedRK4IntegrationEngine(CythonizedBaseIntegrationEngine):
111110
termination_reason = None
112111
last_recorded_range = 0.0
113112

114-
while (range_vector.x <= range_limit_ft + min_step) or (
113+
while (range_vector.x <= range_limit_ft) or (
115114
last_recorded_range <= range_limit_ft - 1e-6):
116115
self.integration_step_count += 1
117116

@@ -241,9 +240,9 @@ cdef class CythonizedRK4IntegrationEngine(CythonizedBaseIntegrationEngine):
241240
data.time, &data.position, &data.velocity, data.mach,
242241
&self._shot_s, density_ratio, drag, data_filter.current_flag
243242
))
244-
# Ensure that we have at least two data points in trajectory, or 1 if filter_flags==NONE
243+
# Ensure that we have at least two data points in trajectory,
245244
# ... as well as last point if we had an incomplete trajectory
246-
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 0:
245+
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 1:
247246
if len(ranges) > 0 and ranges[-1].time == time: # But don't duplicate the last point.
248247
pass
249248
else:

py_ballisticcalc/engines/base_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def should_record(self, position: Vector, velocity: Vector, mach: float,
147147
self.check_mach_crossing(velocity.magnitude(), mach)
148148
if self.filter & TrajFlag.APEX:
149149
self.check_apex(velocity)
150-
if bool(self.current_flag & self.filter) and data is None:
150+
if (bool(self.current_flag & self.filter) and data is None) or time==0.0:
151151
data = BaseTrajData(time=time, position=position,
152152
velocity=velocity, mach=mach)
153153
self.previous_time = time

py_ballisticcalc/engines/euler.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,6 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
8585
).mul_by_const(velocity) # type: ignore
8686
# endregion
8787

88-
# Ensure one iteration when record step is smaller than calc_step
89-
min_step = min(props.calc_step, range_step_ft)
90-
9188
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags, range_step=range_step_ft,
9289
initial_position=range_vector, initial_velocity=velocity_vector,
9390
barrel_angle_rad=props.barrel_elevation_rad,
@@ -99,7 +96,7 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
9996
termination_reason = None
10097
last_recorded_range = 0.0
10198
start_integration_step_count = self.integration_step_count
102-
while (range_vector.x <= range_limit_ft + min_step) or (
99+
while (range_vector.x <= range_limit_ft) or (
103100
last_recorded_range <= range_limit_ft - 1e-6):
104101
self.integration_step_count += 1
105102

@@ -152,9 +149,9 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
152149
ranges.append(make_trajectory_row(
153150
props, data.time, data.position, data.velocity, data.mach, data_filter.current_flag)
154151
)
155-
# Ensure that we have at least two data points in trajectory, or 1 if filter_flags==NONE
152+
# Ensure that we have at least two data points in trajectory,
156153
# ... as well as last point if we had an incomplete trajectory
157-
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 0:
154+
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 1:
158155
if len(ranges) > 0 and ranges[-1].time == time: # But don't duplicate the last point.
159156
pass
160157
else:

py_ballisticcalc/engines/rk4.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
7878
).mul_by_const(velocity) # type: ignore
7979
# endregion
8080

81-
min_step = min(props.calc_step, range_step_ft)
82-
8381
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags, range_step=range_step_ft,
8482
initial_position=range_vector, initial_velocity=velocity_vector,
8583
barrel_angle_rad=props.barrel_elevation_rad,
@@ -92,7 +90,7 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
9290
termination_reason = None
9391
last_recorded_range = 0.0
9492
start_integration_step_count = self.integration_step_count
95-
while (range_vector.x <= range_limit_ft + min_step) or (
93+
while (range_vector.x <= range_limit_ft) or (
9694
last_recorded_range <= range_limit_ft - 1e-6):
9795
self.integration_step_count += 1
9896

@@ -160,9 +158,9 @@ def f(v: Vector) -> Vector: # dv/dt (acceleration)
160158
ranges.append(make_trajectory_row(
161159
props, data.time, data.position, data.velocity, data.mach, data_filter.current_flag)
162160
)
163-
# Ensure that we have at least two data points in trajectory, or 1 if filter_flags==NONE
161+
# Ensure that we have at least two data points in trajectory,
164162
# ... as well as last point if we had an incomplete trajectory
165-
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 0:
163+
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 1:
166164
if len(ranges) > 0 and ranges[-1].time == time: # But don't duplicate the last point.
167165
pass
168166
else:

py_ballisticcalc/engines/velocity_verlet.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,6 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
8787
acceleration_vector = self.gravity_vector - drag * relative_velocity # type: ignore[operator]
8888
# endregion
8989

90-
# Ensure one iteration when record step is smaller than calc_step
91-
min_step = min(props.calc_step, range_step_ft)
92-
9390
data_filter = _TrajectoryDataFilter(filter_flags=filter_flags, range_step=range_step_ft,
9491
initial_position=range_vector, initial_velocity=velocity_vector,
9592
barrel_angle_rad=props.barrel_elevation_rad,
@@ -102,7 +99,7 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
10299
termination_reason = None
103100
last_recorded_range = 0.0
104101
start_integration_step_count = self.integration_step_count
105-
while (range_vector.x <= range_limit_ft + min_step) or (
102+
while (range_vector.x <= range_limit_ft) or (
106103
last_recorded_range <= range_limit_ft - 1e-6):
107104
self.integration_step_count += 1
108105

@@ -160,9 +157,9 @@ def _integrate(self, props: ShotProps, range_limit_ft: float, range_step_ft: flo
160157
ranges.append(make_trajectory_row(
161158
props, data.time, data.position, data.velocity, data.mach, data_filter.current_flag)
162159
)
163-
# Ensure that we have at least two data points in trajectory, or 1 if filter_flags==NONE
160+
# Ensure that we have at least two data points in trajectory,
164161
# ... as well as last point if we had an incomplete trajectory
165-
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 0:
162+
if (filter_flags and ((len(ranges) < 2) or termination_reason)) or len(ranges) == 1:
166163
if len(ranges) > 0 and ranges[-1].time == time: # But don't duplicate the last point.
167164
pass
168165
else:

py_ballisticcalc/generics/engine.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
zeroing angles for firearms.
99
"""
1010

11+
from abc import abstractmethod
1112
from typing import TypeVar, Optional, Union
1213

1314
from typing_extensions import Protocol, runtime_checkable
@@ -37,6 +38,7 @@ def __init__(self, _config: ConfigT):
3738
_config (Config): The configuration object.
3839
"""
3940

41+
@abstractmethod
4042
def integrate(self, shot_info: Shot,
4143
max_range: Distance,
4244
dist_step: Optional[Distance] = None,
@@ -66,6 +68,7 @@ def integrate(self, shot_info: Shot,
6668
HitResult: Object for describing the trajectory.
6769
"""
6870

71+
@abstractmethod
6972
def zero_angle(self, shot_info: Shot, distance: Distance) -> Angular:
7073
"""
7174
Iterative algorithm to find barrel elevation needed for a particular zero

py_ballisticcalc/interface.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from dataclasses import dataclass, field
33
from importlib.metadata import entry_points, EntryPoint
44
from typing import Generic, Any
5+
import warnings
56

67
from deprecated import deprecated
78
from typing_extensions import Union, List, Optional, TypeVar, Type
@@ -170,39 +171,49 @@ def set_weapon_zero(self, shot: Shot, zero_distance: Union[float, Distance]) ->
170171

171172
def fire(self, shot: Shot,
172173
trajectory_range: Union[float, Distance],
173-
trajectory_step: Optional[Union[float, Distance]] = None,
174+
trajectory_step: Optional[Union[float, Distance]] = None, *,
174175
extra_data: bool = False,
176+
dense_output: bool = False,
175177
time_step: float = 0.0,
178+
flags: Union[TrajFlag, int] = TrajFlag.NONE,
176179
raise_range_error: bool = True) -> HitResult:
177180
"""Calculates the trajectory for the given shot parameters.
178181
179182
Args:
180-
shot (Shot): Initial shot parameters, including position and barrel angle.
183+
shot (Shot): Shot parameters, including position and barrel angle.
181184
trajectory_range (float | Distance): Distance at which to stop computing the trajectory.
182185
trajectory_step (float | Distance | None, optional): Distance between recorded trajectory points.
183-
If 0 or None, defaults to `trajectory_range`.
184-
extra_data (bool, optional): If True, requests all TrajFlags. Otherwise only requests TrajFlag.RANGE.
185-
Defaults to False.
186+
If 0 or None, defaults to `trajectory_range`.
187+
extra_data (bool, optional): [DEPRECATED] Requests flags=TrajFlags.ALL
188+
and trajectory_step=PreferredUnits.distance(1).
189+
dense_output (bool, optional): HitResult stores all calculation steps so it can interpolate any point.
186190
time_step (float, optional): Minimum time sampling interval in seconds. If > 0, data is
187-
recorded at least this frequently. Defaults to 0.0.
191+
recorded at least this frequently. Defaults to 0.0.
192+
flags (TrajFlag, optional): Flags for specific points of interest. Defaults to TrajFlag.NONE.
188193
raise_range_error (bool, optional): If True, raises RangeError if returned by integration.
189194
190195
Returns:
191196
HitResult: Object containing computed trajectory.
192197
"""
193198
trajectory_range = PreferredUnits.distance(trajectory_range)
194199
dist_step = trajectory_range
195-
filter_flags = TrajFlag.RANGE
200+
filter_flags = flags
196201
if trajectory_step:
197202
dist_step = PreferredUnits.distance(trajectory_step)
198-
filter_flags = TrajFlag.RANGE
203+
filter_flags |= TrajFlag.RANGE
199204
if dist_step.raw_value > trajectory_range.raw_value:
200205
dist_step = trajectory_range
201206

202207
if extra_data:
208+
warnings.warn("extra_data is deprecated and will be removed in future versions. "
209+
"Explicitly specify desired TrajectoryData frequency and flags.",
210+
DeprecationWarning
211+
)
212+
#dist_step = PreferredUnits.distance(1.0) # << For compatibility with v2.1
203213
filter_flags = TrajFlag.ALL
204214

205-
result = self._engine_instance.integrate(shot, trajectory_range, dist_step, time_step, filter_flags)
215+
result = self._engine_instance.integrate(shot, trajectory_range, dist_step, time_step,
216+
filter_flags, dense_output=dense_output)
206217
if result.error and raise_range_error:
207218
raise result.error
208219
return result

tests/test_incomplete_shots.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Distance,
88
TrajFlag
99
)
10+
from py_ballisticcalc.unit import PreferredUnits
1011
from tests.fixtures_and_helpers import print_out_trajectory_compact, zero_height_calc, \
1112
shot_with_relative_angle_in_degrees
1213

@@ -51,7 +52,7 @@ def test_vertical_shot(zero_height_calc, loaded_engine_instance):
5152
hit_result = zero_height_calc.fire(shot, range, extra_data=extra_data, raise_range_error=False)
5253
print_out_trajectory_compact(hit_result)
5354
# In this case all we know is we should have two points, and the last point should be below zero.
54-
assert len(hit_result) == 2
55+
assert len(hit_result) == 2, "With extra_data=False, calculator should return exactly 2 points"
5556
assert hit_result[-1].height.raw_value < 1e-9, "Last point's height should be at or below zero"
5657

5758
extra_data = True
@@ -167,4 +168,4 @@ def test_end_points_are_included(distance, height, angle_in_degrees, zero_height
167168
print(f'Difference in results Distance: {distance_difference :.02f} '
168169
f'Height {height_difference :.02f}')
169170

170-
assert distance_difference <= Distance.Foot(0.2) >> Distance.Meter
171+
assert distance_difference <= (PreferredUnits.distance(1.0) >> Distance.Meter)

0 commit comments

Comments
 (0)