o-murphy / py-ballisticcalc

LGPL library for small arms ballistic calculations based on point-mass (3 DoF) plus spin drift.
https://pypi.org/project/py-ballisticcalc
GNU Lesser General Public License v3.0
21 stars 8 forks source link

Find the way to provide more efficient methods of calculation #32

Open o-murphy opened 1 year ago

dbookstaber commented 8 months ago

This is implemented in pull request #50

dbookstaber commented 7 months ago

I implemented constant time-step calculation here: https://github.com/dbookstaber/py_ballistics/blob/time-step/py_ballisticcalc/trajectory_calc.py

However it looks like it will always take more iterations than the current method to achieve any particular level of precision, so I do not recommend pursuing this.

o-murphy commented 7 months ago

but in addition to the number of iterations, it is necessary to take into account the time for performing the calculation, it may turn out that due to a decrease in the number of calculations, the number of iterations can be increased without losing performance

we have to make a profiling and performance comparing u can use timeit built-in module to got time some function are executing

dbookstaber commented 7 months ago

By "iterations" I meant the number of passes through the #region Trajectory Loop to calculate any particular trajectory. (This is without regard to the zero-finding logic.)

I think the current method of setting the time step based on the velocity is optimal. Any other approach will create excess precision at some points and lower precision at others.

dbookstaber commented 5 months ago

To improve efficiency, JBM recommends switching from this simple integration approach to something like RK4, which is what he uses.

dbookstaber commented 5 months ago

I implemented RK4 integration in this branch. It is more robust than the Euler implementation we are running, meaning that as step_size increases errors grow more slowly. However on the example I studied it's not like orders of magnitude better.

This provides a foundation for some desirable improvements, including:

  1. Adaptive step_size
  2. I included two different RK4 implementations, one using time and the other using range.x as the independent variable. The latter (CalcMethod.RK_dx) version could particularly benefit TrajectoryCalc.zero_angle since, as long as zero_distance % step_size == 0 then it will end exactly at the distance of interest.
o-murphy commented 2 months ago

Right now I don't have much time to study alternative calculation methods, so I'm completely relying on you. In any case, if we're worried about breaking something, we can always implement it as an experimental feature and enable it via the library's global settings. Like we do with gunpowder sensitivity, we can enable an alternative calculation algorithm. Use inheritance, it allows to implement new feature without massive base code changes and lost of backwards compatibility and also allows extend functionality without changing major release version

Create new module, inherit from existing backend.TrajectoryCalculator

# my_new_solver.py
from py_ballisticcalc.backend import TrajectoryCalculator

class MyNewSolver(TrajectoryCalculator):

    def trajectory(*args, **kwargs):
        # reimplement methods you need
        ...

Add global flags to trajectory_calc.py

# existing flags
_globalUsePowderSensitivity = False
def set_global_use_powder_sensitivity(value: bool) -> None:
    # pylint: disable=global-statement
    global _globalUsePowderSensitivity
    if not isinstance(value, bool):
        raise TypeError(f"set_global_use_powder_sensitivity {value=} is not a boolean")
    _globalUsePowderSensitivity = value

# new flag
_globalUseNewSolver = False
def set_global_use_new_solver(value: bool) -> None:
    global _globalUseNewSolver
    _globalUseNewSolver = value

Add conditional import of your new solver to interface.Calculator

from py_ballisticcalc.backend import TrajectoryCalc, get_global_use_new_solver

# Calculator class
def barrel_elevation_for_target(self, shot: Shot, target_distance: Union[float, Distance]) -> Angular:
    if get_global_use_new_solver():
        from my_new_solver import MyNewSolver
        calc = MyNewSolver()
    else:
       calc = TrajectoryCalc()