diff --git a/py_ballisticcalc.exts/py_ballisticcalc_exts/trajectory_calc.pyx b/py_ballisticcalc.exts/py_ballisticcalc_exts/trajectory_calc.pyx index 9ab2420..4602881 100644 --- a/py_ballisticcalc.exts/py_ballisticcalc_exts/trajectory_calc.pyx +++ b/py_ballisticcalc.exts/py_ballisticcalc_exts/trajectory_calc.pyx @@ -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 @@ -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 @@ -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 @@ -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= ((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 @@ -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 @@ -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: @@ -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, diff --git a/py_ballisticcalc.exts/pyproject.toml b/py_ballisticcalc.exts/pyproject.toml index a2a9cfd..24621e5 100644 --- a/py_ballisticcalc.exts/pyproject.toml +++ b/py_ballisticcalc.exts/pyproject.toml @@ -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="thehelixpg@gmail.com" }, diff --git a/py_ballisticcalc/trajectory_calc/_trajectory_calc.py b/py_ballisticcalc/trajectory_calc/_trajectory_calc.py index dd072b5..b2d9775 100644 --- a/py_ballisticcalc/trajectory_calc/_trajectory_calc.py +++ b/py_ballisticcalc/trajectory_calc/_trajectory_calc.py @@ -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 @@ -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 @@ -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 @@ -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) @@ -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. @@ -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() @@ -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) @@ -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: @@ -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, diff --git a/py_ballisticcalc/unit.py b/py_ballisticcalc/unit.py index c9c7074..994a043 100644 --- a/py_ballisticcalc/unit.py +++ b/py_ballisticcalc/unit.py @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 85ac4ec..43ec12d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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="thehelixpg@gmail.com" }, @@ -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 = [ diff --git a/tests/fixtures_and_helpers.py b/tests/fixtures_and_helpers.py new file mode 100644 index 0000000..e581b84 --- /dev/null +++ b/tests/fixtures_and_helpers.py @@ -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>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) diff --git a/tests/test_danger_space.py b/tests/test_danger_space.py index 98881fd..42a8e9f 100644 --- a/tests/test_danger_space.py +++ b/tests/test_danger_space.py @@ -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( diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 5689c4a..33b6268 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -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: diff --git a/tests/test_incomplete_shots.py b/tests/test_incomplete_shots.py new file mode 100644 index 0000000..d85b0ba --- /dev/null +++ b/tests/test_incomplete_shots.py @@ -0,0 +1,207 @@ +import time + +import pytest +from py_ballisticcalc import ( + Distance, + RangeError, + HitResult, +) +from tests.fixtures_and_helpers import print_out_trajectory_compact, zero_height_calc, \ + shot_with_relative_angle_in_degrees + + +def test_shot_incomplete(zero_height_calc): + + angle_in_degrees = 5.219710693607955 + distance = 6937.3716148080375 + + shot = shot_with_relative_angle_in_degrees(angle_in_degrees) + range = Distance.Meter(distance) + + try: + extra_data = False + hit_result= zero_height_calc.fire(shot, range, extra_data=extra_data) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + + last_point_distance = hit_result[-1].distance >> Distance.Meter + last_point_height = hit_result[-1].height >> Distance.Meter + print(f"{ last_point_distance=} { last_point_height=}") + assert last_point_distance == pytest.approx(3525.0, abs=0.2) + assert last_point_height == pytest.approx(0.0, abs=0.1) + + try: + extra_data = False + hit_result= zero_height_calc.fire(shot, range, extra_data=extra_data, trajectory_step=range) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + + last_point_distance = hit_result[-1].distance >> Distance.Meter + last_point_height = hit_result[-1].height >> Distance.Meter + assert last_point_distance == pytest.approx(3525.0, abs=0.2) + assert last_point_height == pytest.approx(0.0, abs=0.1) + + try: + extra_data = True + hit_result= zero_height_calc.fire(shot, range, extra_data=extra_data) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + + last_point_distance = hit_result[-1].distance >> Distance.Meter + last_point_height = hit_result[-1].height >> Distance.Meter + assert last_point_distance == pytest.approx(3525.0, abs=0.2) + assert last_point_height == pytest.approx(0.0, abs=0.1) + + try: + extra_data = True + hit_result= zero_height_calc.fire(shot, range, extra_data=extra_data, trajectory_step=range) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + + last_point_distance = hit_result[-1].distance >> Distance.Meter + last_point_height = hit_result[-1].height >> Distance.Meter + assert last_point_distance == pytest.approx(3525.0, abs=0.2) + assert last_point_height == pytest.approx(0.0, abs=0.1) + +def test_vertical_shot(zero_height_calc): + shot = shot_with_relative_angle_in_degrees(90) + range=Distance.Meter(10) + try: + extra_data=False + hit_result = zero_height_calc.fire(shot, range, extra_data=extra_data) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + assert hit_result[-1].distance>>Distance.Meter == pytest.approx(0, abs=1e-10) + assert hit_result[-1].height>>Distance.Meter == pytest.approx(0, abs=0.1) + + try: + extra_data=True + hit_result = zero_height_calc.fire(shot, range, extra_data=extra_data) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + assert hit_result[-1].distance>>Distance.Meter == pytest.approx(0, abs=1e-10) + assert hit_result[-1].height>>Distance.Meter == pytest.approx(0, abs=0.1) + +def test_no_duplicate_points(zero_height_calc): + # this is a shot for point (1000, 0) + shot = shot_with_relative_angle_in_degrees(0.46571949074059704) + # setting up bigger distance than required by shot + range=Distance.Meter(1100) + try: + extra_data=False + hit_result = zero_height_calc.fire(shot, range, extra_data=extra_data, trajectory_step=Distance.Meter(100)) + except RangeError as e: + print(f'{e.reason} {len(e.incomplete_trajectory)=}') + if e.reason in[ RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + hit_result = HitResult(shot, e.incomplete_trajectory, extra=extra_data) + print_out_trajectory_compact(hit_result) + assert len(hit_result.trajectory)>=2 + assert hit_result[-2]!=hit_result[-1] + assert hit_result[-2].distance>>Distance.Meter == pytest.approx(1000, abs=0.2) + assert hit_result[-2].height>>Distance.Meter == pytest.approx(0, abs=0.01) + assert hit_result[-1].distance>>Distance.Meter == pytest.approx(1000, abs=0.2) + assert hit_result[-1].height>>Distance.Meter == pytest.approx(0, abs=0.01) + +def test_no_duplicated_point_many_trajectories(zero_height_calc): + # bigger than max range of weapon + range = Distance.Meter(8000) + for extra_data in [False, True]: + angle = 0 + while angle<=90: + shot = shot_with_relative_angle_in_degrees(angle) + try: + hit_result = zero_height_calc.fire(shot, range, extra_data=extra_data) + except RangeError as e: + if e.reason in [RangeError.MaximumDropReached, RangeError.MinimumAltitudeReached]: + print(f'Got range error {e=}') + hit_result = HitResult(shot, e.incomplete_trajectory, extra_data) + + else: + raise e + print(f'{len(hit_result.trajectory)=}') + assert len(hit_result.trajectory)==len(set(hit_result.trajectory)) + angle += 10 + + +test_points = [ + (400, 300, 37.018814944137404), + (1200, 900, 37.5653274152026), + (1200, 1500, 52.1940023594277), + (1682.0020070293451, 3979.589760371905, 70.6834782844347), + (4422.057278753554, 1975.0518929482573, 34.6455781039671), + (5865.263344484814, 1097.7312160636257, 30.1865144767384), + (564.766336537204, 1962.27673604624, 74.371041637992), + (5281.061059442218, 2529.348893994985, 46.2771485569329), + (2756.3221111683733, 4256.441991651934,65.7650037845664), + (63.11845014860512, 4215.811071201791, 89.2734502050901), + (3304.002996878733, 4187.8846508525485, 65.48673417912764), + (6937.3716148080375, 358.5414845184736, 38.98449130666212), + (7126.0478000569165, 0.001, 38.58299087491584), + ] + +@pytest.mark.parametrize("distance, height, angle_in_degrees", test_points) +def test_end_points_are_included(distance, height, angle_in_degrees, zero_height_calc): + shot = shot_with_relative_angle_in_degrees(angle_in_degrees) + calc = zero_height_calc + range = Distance.Meter(distance) + print(f'\nDistance: {distance:.2f} Height: {height:.2f}') + + extra_data_flag = True + + start_time_extra_data = time.time() + try: + hit_result_extra_data = calc.fire(shot, range, extra_data=extra_data_flag) + except RangeError as e: + print(f'Got range error {e=}') + hit_result_extra_data = HitResult(shot, e.incomplete_trajectory, extra=extra_data_flag) + end_time_extra_data = time.time() + print(f'{extra_data_flag=} {len(hit_result_extra_data.trajectory)=} {(end_time_extra_data-start_time_extra_data)=:.3f}s') + print_out_trajectory_compact(hit_result_extra_data, f"extra_data={extra_data_flag}") + last_point_extra_data = hit_result_extra_data[-1] + distance_extra_data = last_point_extra_data.distance >> Distance.Meter + height_extra_data = last_point_extra_data.height >> Distance.Meter + print(f"Extra data={extra_data_flag} Distance {distance_extra_data:.02f} Height {height_extra_data:.02f}") + no_extra_data_flag = False + start_time_no_extra_data = time.time() + try: + hit_result_no_extra_data = calc.fire(shot, range, extra_data=no_extra_data_flag, + # trajectory_step=range) + trajectory_step=Distance.Foot(0.2)) + # ) + except RangeError as e: + print(f'Got range error {e=}') + hit_result_no_extra_data = HitResult(shot, e.incomplete_trajectory, extra=no_extra_data_flag) + end_time_no_extra_data = time.time() + print(f'{no_extra_data_flag=} {len(hit_result_no_extra_data.trajectory)=} {(end_time_no_extra_data-start_time_no_extra_data)=:.3f}s') + print_out_trajectory_compact(hit_result_no_extra_data, f"extra_data={no_extra_data_flag}") + + last_point_no_extra_data = hit_result_no_extra_data[-1] + distance_no_extra_data = last_point_no_extra_data.distance >> Distance.Meter + height_no_extra_data = last_point_no_extra_data.height >> Distance.Meter + + print(f"Extra data={no_extra_data_flag} Distance {distance_no_extra_data} Height {height_no_extra_data}") + print(f"Extra data={no_extra_data_flag} Distance {distance_no_extra_data:.02f} Height {height_no_extra_data:.02f}") + distance_difference = abs(distance_extra_data - distance_no_extra_data) + height_difference = abs(height_extra_data - height_no_extra_data) + print(f'Difference in results Distance: {distance_difference :.02f} ' + f'Height {height_difference :.02f}') + + assert distance_difference<=Distance.Foot(0.2)>>Distance.Meter diff --git a/tests/test_issues.py b/tests/test_issues.py index 2150c5e..db402f2 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -78,80 +78,55 @@ def tearDown(self): def testResultsWithImperialUnits(self): loadImperialUnits() hit_result = self.calc.fire(self.shot, self.range, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithImperialUnits_FloatInput(self): loadImperialUnits() hit_result = self.calc.fire(self.shot, self.range>>PreferredUnits.distance, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithMetricUnits(self): loadMetricUnits() hit_result = self.calc.fire(self.shot, self.range, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithMetricUnits_FloatInput(self): loadMetricUnits() hit_result = self.calc.fire(self.shot, self.range>>PreferredUnits.distance, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithMetricUnits_FloatTrajectoryStep(self): loadMetricUnits() hit_result = self.calc.fire(self.shot, self.range, trajectory_step=Distance.Inch(2916.5623262316285)>>PreferredUnits.distance, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithImperialUnitsAndYards(self): loadImperialUnits() PreferredUnits.distance = Distance.Yard hit_result = self.calc.fire(self.shot, self.range, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultsWithImperialUnitAndYards_UnitTrajectoryStep(self): loadImperialUnits() PreferredUnits.distance = Distance.Yard hit_result = self.calc.fire(self.shot, self.range, trajectory_step=Distance.Inch(2916.5623262316285), extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) - last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.check_expected_last_point(hit_result) def testResultWithImperialUnits_FloatRange(self): loadImperialUnits() self.assertEqual(PreferredUnits.distance, Distance.Foot) hit_result = self.calc.fire(self.shot, self.range>>Distance.Foot, extra_data=False) - self.assertEqual(len(hit_result.trajectory), 11) + self.check_expected_last_point(hit_result) + + def check_expected_last_point(self, hit_result): + self.assertEqual(11, len(hit_result.trajectory)) last_hit_point = hit_result[-1] - self.assertEqual(last_hit_point.time, 0.9920863205706615) - self.assertEqual((last_hit_point.distance>>Distance.Meter), 740.8498567639497) - self.assertEqual((last_hit_point.height >> Distance.Meter), 168.4355597904274) + self.assertEqual(0.9920863205706615, last_hit_point.time) + self.assertEqual(740.8498567639497, (last_hit_point.distance >> Distance.Meter)) + self.assertEqual(168.4355597904274, (last_hit_point.height >> Distance.Meter))