Interface
Calculator
¶
Calculator(
*,
config: ConfigT,
engine: EngineFactoryProtocol[ConfigT],
)
Calculator(
*,
config: Any = None,
engine: EngineFactoryProtocolEntry = None,
)
The main interface for the ballistics calculator.
This class provides thread-safe access to the underlying integration engines by creating a new, isolated engine instance for every method call.
Crucially: The engine instance is not created here. To ensure thread safety (especially in free-threaded Python), each method call must operate on a new, isolated engine instance.
Methods:
| Name | Description |
|---|---|
__enter__ |
Enter the runtime context for this Calculator. |
__exit__ |
Exit the runtime context. |
__getattr__ |
Delegate attribute access to the underlying engine instance. |
__getstate__ |
Called by pickle for serialization. |
__setstate__ |
Called by pickle for deserialization. |
barrel_elevation_for_target |
Calculate barrel elevation to hit target at zero_distance. |
aim |
Calculate a target's zero solution and retain its trajectory point. |
aiming_solution_for_target |
Return :meth: |
set_weapon_zero |
Set shot.weapon.zero_elevation so that it hits a target at zero_distance. |
fire |
Calculate the trajectory for the given shot parameters. |
iter_engines |
Iterate all available engines in the entry points. |
Source code in py_ballisticcalc/interface.py
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
Attributes¶
_engine_instance
property
¶
_engine_instance: EngineProtocol
Creates and returns a fresh, isolated engine instance upon every access.
This implementation is the core mechanism for ensuring thread safety in the Calculator class,
particularly essential in free-threaded Python (e.g., CPython with GIL disabled, Python 3.13+).
Thread Safety Rationale¶
Instead of using traditional synchronization primitives (like threading.Lock)
to protect a single, shared engine instance, this method employs isolation.
Since the underlying engine instances are not guaranteed to be thread-safe
internally, generating a new instance for each operation ensures that
no two concurrent threads will ever modify the same engine object. This
approach eliminates race conditions without introducing the overhead or
potential deadlocks associated with locking mechanisms.
Performance Consideration¶
Note: The overall performance of concurrent operations critically depends
on the initialization cost of the underlying engine class (self._engine_class).
If the engine's constructor performs extensive I/O, loads large data tables,
or executes complex setup, repeated instantiation may introduce significant
overhead. For optimal performance, the engine's initialization (__init__)
should be designed to be as lightweight as possible.
Returns:
| Type | Description |
|---|---|
EngineProtocol
|
EngineProtocol[Any]: A new, single-use engine instance configured
with the |
Methods:¶
__enter__
¶
__enter__() -> Self
Enter the runtime context for this Calculator.
Returns:
| Name | Type | Description |
|---|---|---|
Self |
Self
|
The Calculator instance. |
Example
with Calculator(config, RK4IntegrationEngine) as calc: ... result = calc.fire(shot, Distance.Meter(1000))
Source code in py_ballisticcalc/interface.py
234 235 236 237 238 239 240 241 242 243 244 | |
__exit__
¶
__exit__(
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None
Exit the runtime context.
This is a no-op as Calculator is stateless and thread-safe by design — each method call creates an isolated engine instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exc_type
|
type[BaseException] | None
|
Exception type if an exception was raised, None otherwise. |
required |
exc_val
|
BaseException | None
|
Exception instance if an exception was raised, None otherwise. |
required |
exc_tb
|
TracebackType | None
|
Traceback if an exception was raised, None otherwise. |
required |
Source code in py_ballisticcalc/interface.py
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | |
__getattr__
¶
Delegate attribute access to the underlying engine instance.
This method is called when an attribute is requested on the Calculator
instance that is not found through normal attribute lookup (i.e., it's
not a direct attribute of Calculator or its class). It then attempts
to retrieve the attribute from the _engine_instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
str
|
The name of the attribute to retrieve. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The value of the attribute from |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If the attribute is not found on either the
|
Examples:
>>> calc = Calculator(engine=DEFAULT_ENTRY)
>>> calc_step = calc.get_calc_step()
>>> print(calc_step)
0.0025
>>> try:
... calc.unknown_method()
... except AttributeError as e:
... print(e)
'Calculator' object or its underlying engine 'RK4IntegrationEngine' has no attribute 'unknown_method'
Source code in py_ballisticcalc/interface.py
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | |
__getstate__
¶
__getstate__()
Called by pickle for serialization. We only serialize the public fields required for reconstruction. We explicitly exclude the calculated fields like _engine_class to ensure proper re-initialization in the new process.
Source code in py_ballisticcalc/interface.py
333 334 335 336 337 338 339 340 | |
__setstate__
¶
__setstate__(state)
Called by pickle for deserialization. We manually set the fields and call post_init to reload the engine class.
Source code in py_ballisticcalc/interface.py
342 343 344 345 346 347 348 349 350 351 | |
barrel_elevation_for_target
¶
Calculate barrel elevation to hit target at zero_distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shot
|
Shot
|
Shot instance we want to zero. |
required |
target_distance
|
float | Distance
|
Look-distance to "zero," which is point we want to hit. This is the distance that a rangefinder would return with no ballistic adjustment. |
required |
Note
Some rangefinders offer an adjusted distance based on inclinometer measurement. However, without a complete ballistic model these can only approximate the effects on ballistic trajectory of shooting uphill or downhill. Therefore: For maximum accuracy, use the raw sight distance and look_angle as inputs here.
Source code in py_ballisticcalc/interface.py
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
aim
¶
Calculate a target's zero solution and retain its trajectory point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shot
|
Shot
|
Shot instance to solve. |
required |
target_distance
|
float | Distance
|
Look-distance to the target. This is the distance that a rangefinder would return with no ballistic adjustment. |
required |
Returns:
| Type | Description |
|---|---|
Angular
|
The vertical hold relative to the weapon's zero, the windage |
Angular
|
angle, and the trajectory point evaluated by the successful |
TrajectoryData
|
zero-finding iteration. The windage angle uses the sign |
tuple[Angular, Angular, TrajectoryData]
|
convention of :attr: |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the selected engine has no |
Source code in py_ballisticcalc/interface.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | |
aiming_solution_for_target
¶
aiming_solution_for_target(
shot: Shot, target_distance: float | Distance
) -> tuple[Angular, Angular, TrajectoryData]
Return :meth:aim's solution for callers using the descriptive API name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shot
|
Shot
|
Shot instance to solve. |
required |
target_distance
|
float | Distance
|
Look-distance to the target. |
required |
Returns:
| Type | Description |
|---|---|
Angular
|
The vertical hold relative to the weapon's zero, windage angle, |
Angular
|
and terminal trajectory point. |
Source code in py_ballisticcalc/interface.py
398 399 400 401 402 403 404 405 406 407 408 409 410 411 | |
set_weapon_zero
¶
Set shot.weapon.zero_elevation so that it hits a target at zero_distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shot
|
Shot
|
Shot instance to zero. |
required |
zero_distance
|
float | Distance
|
Look-distance to "zero," which is point we want to hit. |
required |
Source code in py_ballisticcalc/interface.py
413 414 415 416 417 418 419 420 421 | |
fire
¶
fire(
shot: Shot,
trajectory_range: float | Distance,
trajectory_step: float | Distance | None = None,
*,
extra_data: bool = False,
dense_output: bool = False,
time_step: float = 0.0,
flags: TrajFlag | int = NONE,
raise_range_error: bool = True,
) -> HitResult
Calculate the trajectory for the given shot parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shot
|
Shot
|
Shot parameters, including position and barrel angle. |
required |
trajectory_range
|
float | Distance
|
Distance at which to stop computing the trajectory. |
required |
trajectory_step
|
float | Distance | None
|
Distance between recorded trajectory points. Defaults to |
None
|
extra_data
|
bool
|
[DEPRECATED] Requests flags=TrajFlags.ALL and trajectory_step=PreferredUnits.distance(1). |
False
|
dense_output
|
bool
|
HitResult stores all calculation steps so it can interpolate any point. |
False
|
time_step
|
float
|
Maximum time between recorded points. If > 0, points are recorded at least this frequently. Defaults to 0.0. |
0.0
|
flags
|
TrajFlag | int
|
Flags for specific points of interest. Defaults to TrajFlag.NONE. |
NONE
|
raise_range_error
|
bool
|
If True, raises RangeError if returned by integration. |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
HitResult |
HitResult
|
Object containing computed trajectory. |
Source code in py_ballisticcalc/interface.py
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 | |
iter_engines
staticmethod
¶
iter_engines() -> Generator[EntryPoint, None, None]
Iterate all available engines in the entry points.
Source code in py_ballisticcalc/interface.py
476 477 478 479 | |
_EngineLoader
dataclass
¶
_EngineLoader()
Discovers and loads engine factories.
Supported entry point layouts
- New: group
py_ballisticcalc.engines.<engine>with name<method>, addressed as"<engine>+<method>"or"<engine>.<method>"(e.g."cython+rk4"). - Legacy (temporary): group
py_ballisticcalcwith names likerk4_engine. Entries duplicating a new-style entry (same target) are skipped. - Direct:
"<module>:<factory>"path.
Methods:
| Name | Description |
|---|---|
engine_id |
Public identifier of an entry point: |
iter_engines |
Iterate over all available engines (new-style first, legacy deduplicated by target). |
engine_id
classmethod
¶
engine_id(ep: EntryPoint) -> str
Public identifier of an entry point: <engine>+<method> for new-style, plain name for legacy.
Source code in py_ballisticcalc/interface.py
79 80 81 82 83 84 | |
iter_engines
classmethod
¶
iter_engines() -> Generator[EntryPoint, None, None]
Iterate over all available engines (new-style first, legacy deduplicated by target).
Source code in py_ballisticcalc/interface.py
86 87 88 89 90 91 92 93 94 95 96 | |