Skip to content

Vector

Vector

Bases: NamedTuple

Immutable 3D vector for ballistic trajectory calculations.

Attributes:

Name Type Description
x float

Distance/horizontal component (positive = downrange direction).

y float

Vertical component (positive = upward direction).

z float

Horizontal component (positive = right lateral direction).

Examples:

Basic vector creation and operations:

# Create position vector (100m downrange, 10m high)
position = Vector(100.0, 10.0, 0.0)

# Create velocity vector (800 m/s muzzle velocity)
velocity = Vector(800.0, 0.0, 0.0)

# Vector arithmetic
new_pos = position + velocity * 0.1  # Position after 0.1 seconds

# Calculate magnitude
speed = velocity.magnitude()  # 800.0 m/s

# Create unit vector for direction
direction = velocity.normalize()  # Vector(1.0, 0.0, 0.0)

# Wind vector (5 m/s crosswind from left)
wind = Vector(0.0, 0.0, 5.0)

# Calculate wind effect angle
cos_angle = velocity.mul_by_vector(wind) / (velocity.magnitude() * wind.magnitude())

# Gravity vector
gravity = Vector(0.0, -9.81, 0.0)  # m/s²

Methods:

Name Description
magnitude

Calculate the Euclidean norm (length) of the vector.

mul_by_const

Multiply vector by a scalar constant.

mul_by_vector

Calculate the dot product (scalar product) of two vectors.

add

Add two vectors component-wise.

subtract

Subtract one vector from another component-wise.

negate

Create a vector with opposite direction (negative vector).

normalize

Create a unit vector pointing in the same direction.

Functions

magnitude
magnitude() -> float

Calculate the Euclidean norm (length) of the vector.

Returns:

Type Description
float

The magnitude (length) of the vector as a non-negative float.

Examples:

# Unit vector magnitude
unit = Vector(1.0, 0.0, 0.0)
assert unit.magnitude() == 1.0

# Velocity magnitude (speed)
velocity = Vector(800.0, 100.0, 50.0)
speed = velocity.magnitude()  # ~806.5

# Distance calculation
position = Vector(100.0, 50.0, 25.0)
distance = position.magnitude()  # Distance from origin
Note

Uses math.hypot() for numerical stability with extreme values. Equivalent to sqrt(x² + y² + z²) but more robust.

Source code in py_ballisticcalc/vector.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def magnitude(self) -> float:
    """Calculate the Euclidean norm (length) of the vector.

    Returns:
        The magnitude (length) of the vector as a non-negative float.

    Examples:
        ```python
        # Unit vector magnitude
        unit = Vector(1.0, 0.0, 0.0)
        assert unit.magnitude() == 1.0

        # Velocity magnitude (speed)
        velocity = Vector(800.0, 100.0, 50.0)
        speed = velocity.magnitude()  # ~806.5

        # Distance calculation
        position = Vector(100.0, 50.0, 25.0)
        distance = position.magnitude()  # Distance from origin
        ```

    Note:
        Uses math.hypot() for numerical stability with extreme values.
        Equivalent to sqrt(x² + y² + z²) but more robust.
    """
    # return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
    return math.hypot(self.x, self.y, self.z)
mul_by_const
mul_by_const(a: float) -> Vector

Multiply vector by a scalar constant.

Parameters:

Name Type Description Default
a float

Scalar multiplier. Can be positive (same direction), negative (opposite direction), or zero (zero vector).

required

Returns:

Type Description
Vector

New Vector instance with each component multiplied by the scalar.

Examples:

# Scale velocity vector
velocity = Vector(800.0, 0.0, 0.0)
half_velocity = velocity.mul_by_const(0.5)  # Vector(400.0, 0.0, 0.0)

# Reverse direction
reversed_vel = velocity.mul_by_const(-1.0)  # Vector(-800.0, 0.0, 0.0)

# Time-based scaling for position updates
delta_pos = velocity.mul_by_const(0.001)  # Position change in 1ms
Note

This operation preserves vector direction for non-zero scalars. Multiplying by zero produces a zero vector.

Source code in py_ballisticcalc/vector.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def mul_by_const(self, a: float) -> Vector:
    """Multiply vector by a scalar constant.

    Args:
        a: Scalar multiplier. Can be positive (same direction), negative
            (opposite direction), or zero (zero vector).

    Returns:
        New Vector instance with each component multiplied by the scalar.

    Examples:
        ```python
        # Scale velocity vector
        velocity = Vector(800.0, 0.0, 0.0)
        half_velocity = velocity.mul_by_const(0.5)  # Vector(400.0, 0.0, 0.0)

        # Reverse direction
        reversed_vel = velocity.mul_by_const(-1.0)  # Vector(-800.0, 0.0, 0.0)

        # Time-based scaling for position updates
        delta_pos = velocity.mul_by_const(0.001)  # Position change in 1ms
        ```

    Note:
        This operation preserves vector direction for non-zero scalars.
        Multiplying by zero produces a zero vector.
    """
    return Vector(self.x * a, self.y * a, self.z * a)
mul_by_vector
mul_by_vector(b: Vector) -> float

Calculate the dot product (scalar product) of two vectors.

Computes the dot product, which represents the projection of one vector onto another. The result is a scalar value used in angle calculations, projections, and determining vector relationships.

Parameters:

Name Type Description Default
b Vector

The other Vector instance to compute dot product with.

required

Returns:

Type Description
float

Scalar result of the dot product (x₁·x₂ + y₁·y₂ + z₁·z₂). Positive values indicate vectors pointing in similar directions, negative values indicate opposite directions, zero indicates perpendicular vectors.

Examples:

# Parallel vectors (same direction)
v1 = Vector(1.0, 0.0, 0.0)
v2 = Vector(2.0, 0.0, 0.0)
dot = v1.mul_by_vector(v2)  # 2.0 (positive)

# Perpendicular vectors
v1 = Vector(1.0, 0.0, 0.0)
v2 = Vector(0.0, 1.0, 0.0)
dot = v1.mul_by_vector(v2)  # 0.0 (perpendicular)

# Angle calculation
velocity = Vector(800.0, 100.0, 0.0)
wind = Vector(0.0, 0.0, 10.0)
cos_angle = velocity.mul_by_vector(wind) / (velocity.magnitude() * wind.magnitude())

# Work calculation (force · displacement)
force = Vector(100.0, 0.0, 0.0)  # Newtons
displacement = Vector(10.0, 5.0, 0.0)  # meters
work = force.mul_by_vector(displacement)  # 1000.0 Joules
Note
  • The dot product is commutative: a·b = b·a
  • For unit vectors, the dot product equals the cosine of the angle between them.
Source code in py_ballisticcalc/vector.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def mul_by_vector(self, b: Vector) -> float:
    """Calculate the dot product (scalar product) of two vectors.

    Computes the dot product, which represents the projection of one vector
    onto another. The result is a scalar value used in angle calculations,
    projections, and determining vector relationships.

    Args:
        b: The other Vector instance to compute dot product with.

    Returns:
        Scalar result of the dot product (x₁·x₂ + y₁·y₂ + z₁·z₂).
            Positive values indicate vectors pointing in similar directions,
            negative values indicate opposite directions, zero indicates perpendicular vectors.

    Examples:
        ```python
        # Parallel vectors (same direction)
        v1 = Vector(1.0, 0.0, 0.0)
        v2 = Vector(2.0, 0.0, 0.0)
        dot = v1.mul_by_vector(v2)  # 2.0 (positive)

        # Perpendicular vectors
        v1 = Vector(1.0, 0.0, 0.0)
        v2 = Vector(0.0, 1.0, 0.0)
        dot = v1.mul_by_vector(v2)  # 0.0 (perpendicular)

        # Angle calculation
        velocity = Vector(800.0, 100.0, 0.0)
        wind = Vector(0.0, 0.0, 10.0)
        cos_angle = velocity.mul_by_vector(wind) / (velocity.magnitude() * wind.magnitude())

        # Work calculation (force · displacement)
        force = Vector(100.0, 0.0, 0.0)  # Newtons
        displacement = Vector(10.0, 5.0, 0.0)  # meters
        work = force.mul_by_vector(displacement)  # 1000.0 Joules
        ```

    Note:
        - The dot product is commutative: a·b = b·a
        - For unit vectors, the dot product equals the cosine of the angle between them.
    """
    return self.x * b.x + self.y * b.y + self.z * b.z
add
add(b: Vector) -> Vector

Add two vectors component-wise.

Parameters:

Name Type Description Default
b Vector

The other Vector instance to add to this vector.

required

Returns:

Type Description
Vector

New Vector instance representing the sum of both vectors.

Examples:

# Position update
position = Vector(100.0, 10.0, 0.0)
displacement = Vector(5.0, 1.0, 0.5)
new_position = position.add(displacement)  # Vector(105.0, 11.0, 0.5)

# Velocity combination
muzzle_velocity = Vector(800.0, 0.0, 0.0)
wind_velocity = Vector(0.0, 0.0, 5.0)
total_velocity = muzzle_velocity.add(wind_velocity)  # Vector(800.0, 0.0, 5.0)

# Trajectory step integration
old_pos = Vector(50.0, 20.0, 0.0)
velocity_delta = Vector(8.0, 0.1, 0.0)  # velocity * time_step
new_pos = old_pos.add(velocity_delta)
Note
  • Vector addition is commutative: a + b = b + a
  • Vector addition is associative: (a + b) + c = a + (b + c)
Source code in py_ballisticcalc/vector.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def add(self, b: Vector) -> Vector:
    """Add two vectors component-wise.

    Args:
        b: The other Vector instance to add to this vector.

    Returns:
        New Vector instance representing the sum of both vectors.

    Examples:
        ```python
        # Position update
        position = Vector(100.0, 10.0, 0.0)
        displacement = Vector(5.0, 1.0, 0.5)
        new_position = position.add(displacement)  # Vector(105.0, 11.0, 0.5)

        # Velocity combination
        muzzle_velocity = Vector(800.0, 0.0, 0.0)
        wind_velocity = Vector(0.0, 0.0, 5.0)
        total_velocity = muzzle_velocity.add(wind_velocity)  # Vector(800.0, 0.0, 5.0)

        # Trajectory step integration
        old_pos = Vector(50.0, 20.0, 0.0)
        velocity_delta = Vector(8.0, 0.1, 0.0)  # velocity * time_step
        new_pos = old_pos.add(velocity_delta)
        ```

    Note:
        - Vector addition is commutative: a + b = b + a
        - Vector addition is associative: (a + b) + c = a + (b + c)
    """
    return Vector(self.x + b.x, self.y + b.y, self.z + b.z)
subtract
subtract(b: Vector) -> Vector

Subtract one vector from another component-wise.

Parameters:

Name Type Description Default
b Vector

The Vector instance to subtract from this vector.

required

Returns:

Type Description
Vector

New Vector instance representing the difference (self - b).

Examples:

# Relative position calculation
target_pos = Vector(1000.0, 0.0, 50.0)
bullet_pos = Vector(500.0, 10.0, 45.0)
relative_pos = target_pos.subtract(bullet_pos)  # Vector(500.0, -10.0, 5.0)

# Velocity change calculation
initial_velocity = Vector(800.0, 0.0, 0.0)
final_velocity = Vector(750.0, -5.0, 2.0)
velocity_change = final_velocity.subtract(initial_velocity)  # Vector(-50.0, -5.0, 2.0)

# Range vector calculation
muzzle_pos = Vector(0.0, 1.5, 0.0)  # Scope height
impact_pos = Vector(1000.0, -2.0, 10.0)
range_vector = impact_pos.subtract(muzzle_pos)  # Vector(1000.0, -3.5, 10.0)
Note
  • Vector subtraction is NOT commutative: a - b ≠ b - a
  • The result represents the vector from b to self.
Source code in py_ballisticcalc/vector.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def subtract(self, b: Vector) -> Vector:
    """Subtract one vector from another component-wise.

    Args:
        b: The Vector instance to subtract from this vector.

    Returns:
        New Vector instance representing the difference (self - b).

    Examples:
        ```python
        # Relative position calculation
        target_pos = Vector(1000.0, 0.0, 50.0)
        bullet_pos = Vector(500.0, 10.0, 45.0)
        relative_pos = target_pos.subtract(bullet_pos)  # Vector(500.0, -10.0, 5.0)

        # Velocity change calculation
        initial_velocity = Vector(800.0, 0.0, 0.0)
        final_velocity = Vector(750.0, -5.0, 2.0)
        velocity_change = final_velocity.subtract(initial_velocity)  # Vector(-50.0, -5.0, 2.0)

        # Range vector calculation
        muzzle_pos = Vector(0.0, 1.5, 0.0)  # Scope height
        impact_pos = Vector(1000.0, -2.0, 10.0)
        range_vector = impact_pos.subtract(muzzle_pos)  # Vector(1000.0, -3.5, 10.0)
        ```

    Note:
        - Vector subtraction is NOT commutative: a - b ≠ b - a
        - The result represents the vector from b to self.
    """
    return Vector(self.x - b.x, self.y - b.y, self.z - b.z)
negate
negate() -> Vector

Create a vector with opposite direction (negative vector).

Returns a new vector with all components negated, effectively creating a vector pointing in the opposite direction with the same magnitude.

Returns:

Type Description
Vector

New Vector instance with all components negated (-x, -y, -z).

Examples:

# Reverse velocity direction
forward_velocity = Vector(800.0, 0.0, 0.0)
backward_velocity = forward_velocity.negate()  # Vector(-800.0, 0.0, 0.0)

# Opposite force direction
drag_force = Vector(-25.0, -2.0, 0.0)
thrust_force = drag_force.negate()  # Vector(25.0, 2.0, 0.0)

# Reflection calculation
incident_vector = Vector(100.0, -50.0, 25.0)
reflected_vector = incident_vector.negate()  # Vector(-100.0, 50.0, -25.0)
Note
  • The magnitude remains unchanged: |v| = |-v|
  • Negating twice returns the original vector: -(-v) = v
Source code in py_ballisticcalc/vector.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def negate(self) -> Vector:
    """Create a vector with opposite direction (negative vector).

    Returns a new vector with all components negated, effectively creating
    a vector pointing in the opposite direction with the same magnitude.

    Returns:
        New Vector instance with all components negated (-x, -y, -z).

    Examples:
        ```python
        # Reverse velocity direction
        forward_velocity = Vector(800.0, 0.0, 0.0)
        backward_velocity = forward_velocity.negate()  # Vector(-800.0, 0.0, 0.0)

        # Opposite force direction
        drag_force = Vector(-25.0, -2.0, 0.0)
        thrust_force = drag_force.negate()  # Vector(25.0, 2.0, 0.0)

        # Reflection calculation
        incident_vector = Vector(100.0, -50.0, 25.0)
        reflected_vector = incident_vector.negate()  # Vector(-100.0, 50.0, -25.0)
        ```

    Note:
        - The magnitude remains unchanged: |v| = |-v|
        - Negating twice returns the original vector: -(-v) = v
    """
    return Vector(-self.x, -self.y, -self.z)
normalize
normalize() -> Vector

Create a unit vector pointing in the same direction.

Returns:

Type Description
Vector

New Vector instance with magnitude 1.0 and same direction. For near-zero vectors (magnitude < 1e-10), returns a copy of the original vector to avoid division by zero.

Examples:

# Create direction vector
velocity = Vector(800.0, 100.0, 50.0)
direction = velocity.normalize()  # Unit vector in velocity direction

# Wind direction calculation
wind_vector = Vector(5.0, 0.0, 3.0)
wind_direction = wind_vector.normalize()  # Unit vector for wind direction

# Line of sight vector
los_vector = Vector(1000.0, -10.0, 25.0)
los_unit = los_vector.normalize()  # Unit vector toward target
Note

For numerical stability, vectors with magnitude < 1e-10 are considered zero vectors and returned unchanged rather than normalized. The normalized vector preserves direction but has magnitude = 1.0.

Source code in py_ballisticcalc/vector.py
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def normalize(self) -> Vector:
    """Create a unit vector pointing in the same direction.

    Returns:
        New Vector instance with magnitude 1.0 and same direction.
            For near-zero vectors (magnitude < 1e-10), returns a copy of
            the original vector to avoid division by zero.

    Examples:
        ```python
        # Create direction vector
        velocity = Vector(800.0, 100.0, 50.0)
        direction = velocity.normalize()  # Unit vector in velocity direction

        # Wind direction calculation
        wind_vector = Vector(5.0, 0.0, 3.0)
        wind_direction = wind_vector.normalize()  # Unit vector for wind direction

        # Line of sight vector
        los_vector = Vector(1000.0, -10.0, 25.0)
        los_unit = los_vector.normalize()  # Unit vector toward target
        ```

    Note:
        For numerical stability, vectors with magnitude < 1e-10 are considered
        zero vectors and returned unchanged rather than normalized.
        The normalized vector preserves direction but has magnitude = 1.0.
    """
    m = self.magnitude()
    if math.fabs(m) < 1e-10:
        return Vector(self.x, self.y, self.z)
    return self.mul_by_const(1.0 / m)