Skip to content

Commit ebd396c

Browse files
committed
Fixed Vector bug
1 parent e1cb754 commit ebd396c

4 files changed

Lines changed: 25 additions & 52 deletions

File tree

py_ballisticcalc/drag_model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class DragDataPoint:
4242
Mach: Velocity in Mach units (dimensionless)
4343
CD: Drag coefficient (dimensionless)
4444
"""
45+
4546
Mach: float # Velocity in Mach units
4647
CD: float # Drag coefficient
4748

@@ -321,6 +322,10 @@ def linear_interpolation(x: Union[List[float], Tuple[float]],
321322
- Uses binary search for efficient interval location
322323
"""
323324
assert len(xp) == len(yp), "xp and yp lists must have same length"
325+
# Validate xp strictly increasing to prevent zero-division and undefined intervals
326+
for i in range(1, len(xp)):
327+
if not (xp[i] > xp[i - 1]):
328+
raise ValueError("xp must be strictly increasing with no duplicates")
324329

325330
y = []
326331
for xi in x:

py_ballisticcalc/munition.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class SightReticleStep(NamedTuple):
6565
)
6666
```
6767
"""
68+
6869
vertical: Angular
6970
horizontal: Angular
7071

@@ -84,6 +85,7 @@ class SightClicks(NamedTuple):
8485
clicks = SightClicks(vertical=5.0, horizontal=2.5)
8586
```
8687
"""
88+
8789
vertical: float
8890
horizontal: float
8991

@@ -114,6 +116,7 @@ class Sight:
114116
)
115117
```
116118
"""
119+
117120
focal_plane: SightFocalPlane
118121
scale_factor: Distance
119122
h_click_size: Angular
@@ -154,7 +157,6 @@ def __init__(self,
154157
)
155158
```
156159
"""
157-
158160
if focal_plane not in get_args(SightFocalPlane):
159161
raise ValueError("Wrong focal plane")
160162

@@ -164,14 +166,14 @@ def __init__(self,
164166
if (not isinstance(h_click_size, (Angular, float, int))
165167
or not isinstance(v_click_size, (Angular, float, int))
166168
):
167-
raise TypeError("type Angular expected for 'h_click_size' and 'v_click_size'")
169+
raise TypeError("Angle expected for 'h_click_size' and 'v_click_size'")
168170

169171
self.focal_plane = focal_plane
170172
self.scale_factor = PreferredUnits.distance(scale_factor or 1)
171173
self.h_click_size = PreferredUnits.adjustment(h_click_size)
172174
self.v_click_size = PreferredUnits.adjustment(v_click_size)
173175
if self.h_click_size.raw_value <= 0 or self.v_click_size.raw_value <= 0:
174-
raise TypeError("'h_click_size' and 'v_click_size' have to be positive")
176+
raise TypeError("'h_click_size' and 'v_click_size' must be positive")
175177

176178
def _adjust_sfp_reticle_steps(self, target_distance: Union[float, Distance],
177179
magnification: float) -> SightReticleStep:
@@ -200,16 +202,14 @@ def _adjust_sfp_reticle_steps(self, target_distance: Union[float, Distance],
200202
```
201203
"""
202204
assert self.focal_plane == 'SFP', "SFP focal plane required"
203-
# adjust reticle scale relative to target distance and magnification
205+
_td = PreferredUnits.distance(target_distance)
206+
if _td.raw_value <= 0:
207+
raise ValueError("target_distance must be positive")
204208
def get_sfp_step(click_size: Angular):
209+
"""Calculate SFP reticle step size for a given click size."""
210+
scale_ratio = self.scale_factor.raw_value / _td.raw_value
205211
# Don't need distances conversion because units cancel:
206-
return click_size.units(
207-
click_size.unit_value
208-
* self.scale_factor.raw_value
209-
/ _td.raw_value
210-
* magnification
211-
)
212-
_td = PreferredUnits.distance(target_distance)
212+
return click_size.units(click_size.unit_value * scale_ratio * magnification)
213213
_h_step = get_sfp_step(self.h_click_size)
214214
_v_step = get_sfp_step(self.v_click_size)
215215
return SightReticleStep(_h_step, _v_step)
@@ -251,6 +251,8 @@ def get_adjustment(self, target_distance: Distance,
251251
print(f"Adjust: {clicks.vertical} up, {clicks.horizontal} right")
252252
```
253253
"""
254+
if magnification <= 0:
255+
raise ValueError("magnification must be positive")
254256
if self.focal_plane == 'SFP':
255257
steps = self._adjust_sfp_reticle_steps(target_distance, magnification)
256258
return SightClicks(
@@ -263,8 +265,7 @@ def get_adjustment(self, target_distance: Distance,
263265
windage_adj.raw_value / self.h_click_size.raw_value
264266
)
265267
if self.focal_plane == 'LWIR':
266-
# adjust clicks to magnification
267-
return SightClicks(
268+
return SightClicks( # adjust clicks to magnification
268269
drop_adj.raw_value / (self.v_click_size.raw_value / magnification),
269270
windage_adj.raw_value / (self.h_click_size.raw_value / magnification)
270271
)
@@ -328,6 +329,7 @@ class Weapon:
328329
)
329330
```
330331
"""
332+
331333
sight_height: Distance
332334
twist: Distance
333335
zero_elevation: Angular
@@ -510,6 +512,8 @@ def calc_powder_sens(self, other_velocity: Union[float, Velocity],
510512
v1 = PreferredUnits.velocity(other_velocity) >> Velocity.MPS
511513
t1 = PreferredUnits.temperature(other_temperature) >> Temperature.Celsius
512514

515+
if v0 <= 0 or v1 <= 0:
516+
raise ValueError("calc_powder_sens requires positive muzzle velocities")
513517
v_delta = math.fabs(v0 - v1)
514518
t_delta = math.fabs(t0 - t1)
515519
v_lower = v1 if v1 < v0 else v0

py_ballisticcalc/vector.py

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,10 @@ class Vector(NamedTuple):
8585
z: float
8686

8787
def magnitude(self) -> float:
88-
"""Calculate the Euclidean magnitude (length) of the vector.
89-
90-
Computes the magnitude using the optimized math.hypot() function which
91-
provides better numerical accuracy and avoids overflow/underflow issues
92-
compared to naive sqrt(x²+y²+z²) implementation.
88+
"""Calculate the Euclidean norm (length) of the vector.
9389
9490
Returns:
9591
The magnitude (length) of the vector as a non-negative float.
96-
Returns 0.0 for zero vectors.
9792
9893
Examples:
9994
```python
@@ -103,7 +98,7 @@ def magnitude(self) -> float:
10398
10499
# Velocity magnitude (speed)
105100
velocity = Vector(800.0, 100.0, 50.0)
106-
speed = velocity.magnitude() # ~806.5 m/s
101+
speed = velocity.magnitude() # ~806.5
107102
108103
# Distance calculation
109104
position = Vector(100.0, 50.0, 25.0)
@@ -457,33 +452,6 @@ def __sub__(self, other: Vector) -> Vector: # type: ignore[override]
457452
"""
458453
return self.subtract(other)
459454

460-
def __rsub__(self, other: Vector) -> Vector: # type: ignore[override]
461-
"""Right subtraction operator for vector subtraction.
462-
463-
Enables vector subtraction when this vector is on the right side of
464-
the - operator. Note that vector subtraction is NOT commutative,
465-
so this performs other - self, not self - other.
466-
467-
Args:
468-
other: The Vector instance to subtract this vector from.
469-
470-
Returns:
471-
New Vector instance representing the difference (other - self).
472-
473-
Examples:
474-
```python
475-
v1 = Vector(10.0, 5.0, 0.0)
476-
v2 = Vector(3.0, 2.0, 1.0)
477-
# When using v1 - v2, if v2.__rsub__ is called:
478-
result = v1 - v2 # Actually calls v2.__rsub__(v1) = v1 - v2
479-
```
480-
481-
Note:
482-
The implementation delegates to subtract() which computes self - other.
483-
This may need review for correct right-subtraction semantics.
484-
"""
485-
return self.subtract(other)
486-
487455
def __isub__(self, other: Vector) -> Vector: # type: ignore[override]
488456
"""In-place subtraction operator for vector subtraction.
489457

tests/test_vector.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,7 @@ def test_vector_right_ops_and_inplace_aliases(self):
6969
sum2 = v1.__iadd__(Vector(0.0, -1.0, 1.0))
7070
assert sum2 == Vector(1.0, 1.0, 4.0)
7171

72-
# __rsub__ and __isub__ paths
73-
# Current implementation delegates to subtract(self, other) => self - other
74-
# so __rsub__ behaves like regular __sub__ with swapped args not applied
75-
diff1 = v1.__rsub__(Vector(0.0, 0.0, 0.0))
76-
assert diff1 == Vector(1.0, 2.0, 3.0)
72+
# __isub__ paths
7773
diff2 = v1.__isub__(Vector(1.0, 1.0, 1.0))
7874
assert diff2 == Vector(0.0, 1.0, 2.0)
7975

0 commit comments

Comments
 (0)