From b911c57c30942be4f341d8614e4d5e485b57e831 Mon Sep 17 00:00:00 2001 From: Dmytro Yaroshenko <73843436+o-murphy@users.noreply.github.com> Date: Sat, 6 Apr 2024 21:16:05 +0300 Subject: [PATCH] Refactored settings (#61) * removed settings.py * calculator specific global constants moved to trajectory_calc.py/.pyx * config loader moved to __init__.py * added pybc.calculator section to config loader * added functions to reset defaults * py_ballisticcalc_exts/setup.py fix --- .github/workflows/python-publish.yml | 54 +++++-- .pybc.toml | 11 +- Example.ipynb | 13 +- assets/.pybc-imperial.toml | 6 +- assets/.pybc-metrics.toml | 6 +- assets/.pybc-mixed.toml | 9 +- examples/canik_50bmg.py | 2 +- examples/ukrop_338lm_300gr_smk.py | 4 +- py_ballisticcalc/__init__.py | 134 +++++++++++++++++- py_ballisticcalc/backend.py | 23 ++- py_ballisticcalc/drag_tables.py | 3 + py_ballisticcalc/example.py | 4 +- py_ballisticcalc/settings.py | 31 ---- py_ballisticcalc/trajectory_calc.py | 50 ++++++- py_ballisticcalc/unit.py | 105 +++++--------- .../py_ballisticcalc_exts/trajectory_calc.pyx | 51 ++++++- py_ballisticcalc_exts/pyproject.toml | 2 +- py_ballisticcalc_exts/setup.py | 4 +- pyproject.toml | 4 +- tests/test_computer.py | 15 +- tests/test_config_loader.py | 44 +++--- tests/test_units.py | 6 +- 22 files changed, 392 insertions(+), 189 deletions(-) delete mode 100644 py_ballisticcalc/settings.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index e72f20e..bebdbfc 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -70,35 +70,61 @@ jobs: deploy: needs: build - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] - + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v3 with: - python-version: ${{ matrix.python-version }} + python-version: "3.9" + - name: Install dependencies run: | python -m pip install --upgrade pip pip install build twine + - name: Build package run: python -m build - - name: Build extensions package - run: | - cd py_ballisticcalc_exts - python -m build --outdir ../dist - cd .. - - name: Publish package to PyPI run: | python -m twine upload dist/* --skip-existing --verbose --non-interactive env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + + deploy-exts: + needs: [build, deploy] + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + python-version: [ "3.9", "3.10", "3.11", "3.12" ] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build extensions package + run: | + cd py_ballisticcalc_exts + python -m build --outdir ../dist + cd .. + + - name: Publish package to PyPI + run: | + python -m twine upload dist/* --skip-existing --verbose --non-interactive + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.pybc.toml b/.pybc.toml index 55599a7..f2a1624 100644 --- a/.pybc.toml +++ b/.pybc.toml @@ -1,7 +1,7 @@ # Config template for py_ballisticcalc title = "standard py_ballisticcalc config template" -version = "2.0.0b3" +version = "2.0.0b4" [pybc.preferred_units] angular = 'Degree' @@ -21,5 +21,10 @@ target_height = 'Inch' twist = 'Inch' [pybc.calculator] -max_calc_step_size = [0.5, "Foot"] -use_powder_sensitivity = true \ No newline at end of file +max_calc_step_size = { value = 0.5, units = "Foot" } +use_powder_sensitivity = false + +# # or use: +# [pybc.calculator.max_calc_step_size] +# value = 0.5 +# units = "Foot" diff --git a/Example.ipynb b/Example.ipynb index eeb6690..c73d715 100644 --- a/Example.ipynb +++ b/Example.ipynb @@ -44,7 +44,7 @@ "from py_ballisticcalc import TableG7, TableG1\n", "from py_ballisticcalc import Ammo, Atmo, Wind\n", "from py_ballisticcalc import Weapon, Shot, Calculator\n", - "from py_ballisticcalc import Settings, PreferredUnits\n", + "from py_ballisticcalc import PreferredUnits, set_global_use_powder_sensitivity\n", "from py_ballisticcalc.drag_model import *\n", "from py_ballisticcalc.unit import *\n", "\n", @@ -268,7 +268,8 @@ "PreferredUnits.velocity = Unit.MPS\n", "PreferredUnits.drop = Unit.Meter\n", "PreferredUnits.sight_height = Unit.Centimeter\n", - "Settings.USE_POWDER_SENSITIVITY = True\n", + "\n", + "set_global_use_powder_sensitivity(True)\n", "\n", "# Standard .50BMG\n", "dm = DragModel(0.62, TableG1, 661, 0.51, 2.3)\n", @@ -394,14 +395,14 @@ "outputs": [], "source": [ "# Single G1 BC\n", - "dm1 = DragModelMultiBC([BCpoint(.462, Mach=1)], TableG1, weight=168, diameter=.308)\n", + "dm1 = DragModelMultiBC([BCPoint(.462, Mach=1)], TableG1, weight=168, diameter=.308)\n", "# Sierra's G1 BC: 0.462 above 2600 fps, 0.447 above 2100 fps, 0.424 above 1600 fps, and .405 below that.\n", - "dm2 = DragModelMultiBC([BCpoint(.462, V=Velocity.FPS(2600)), BCpoint(.462-(.462-.447)/2, V=Velocity.FPS(2350)), BCpoint(.424-(.424-.405)/2, V=Velocity.FPS(1850)), BCpoint(.405, V=Velocity.FPS(1600))],\n", + "dm2 = DragModelMultiBC([BCPoint(.462, V=Velocity.FPS(2600)), BCPoint(.462-(.462-.447)/2, V=Velocity.FPS(2350)), BCPoint(.424-(.424-.405)/2, V=Velocity.FPS(1850)), BCPoint(.405, V=Velocity.FPS(1600))],\n", " TableG1, weight=168, diameter=.308)\n", "# Single G7 BC\n", - "dm3 = DragModelMultiBC([BCpoint(.224, Mach=1)], TableG7, weight=168, diameter=.308)\n", + "dm3 = DragModelMultiBC([BCPoint(.224, Mach=1)], TableG7, weight=168, diameter=.308)\n", "# Litz's G7 multi-BC:\n", - "dm4 = DragModelMultiBC([BCpoint(.211, V=Velocity.FPS(1500)), BCpoint(.214, V=Velocity.FPS(2000)), BCpoint(.222, V=Velocity.FPS(2500)), BCpoint(.226, V=Velocity.FPS(3000))],\n", + "dm4 = DragModelMultiBC([BCPoint(.211, V=Velocity.FPS(1500)), BCPoint(.214, V=Velocity.FPS(2000)), BCPoint(.222, V=Velocity.FPS(2500)), BCPoint(.226, V=Velocity.FPS(3000))],\n", " TableG7, weight=168, diameter=.308)\n", "ax = pandas.DataFrame(dm1.drag_table).plot(x='Mach', y='CD', ylabel='Drag Coefficient', label='G1 Single BC (Baseline)')\n", "pandas.DataFrame(dm2.drag_table).plot(x='Mach', y='CD', label='G1 Multiple BC (Sierra)', linestyle='dashed', linewidth=2, ax=ax)\n", diff --git a/assets/.pybc-imperial.toml b/assets/.pybc-imperial.toml index 4f789fe..43c473f 100644 --- a/assets/.pybc-imperial.toml +++ b/assets/.pybc-imperial.toml @@ -1,7 +1,7 @@ # Config template for py_ballisticcalc title = "py_ballisticcalc config" -version = "2.0.0b3" +version = "2.0.0b4" [pybc.preferred_units] angular = 'Degree' @@ -21,5 +21,5 @@ target_height = 'Inch' twist = 'Inch' [pybc.calculator] -max_calc_step_size = [0.5, "Foot"] -use_powder_sensitivity = true \ No newline at end of file +max_calc_step_size = { value = 0.5, units = "Foot" } +use_powder_sensitivity = false \ No newline at end of file diff --git a/assets/.pybc-metrics.toml b/assets/.pybc-metrics.toml index 5657cc9..34786d3 100644 --- a/assets/.pybc-metrics.toml +++ b/assets/.pybc-metrics.toml @@ -1,7 +1,7 @@ # Config template for py_ballisticcalc title = "py_ballisticcalc config" -version = "2.0.0b3" +version = "2.0.0b4" [pybc.preferred_units] angular = 'Degree' @@ -21,5 +21,5 @@ target_height = 'Meter' twist = 'Centimeter' [pybc.calculator] -max_calc_step_size = [0.5, "Foot"] -use_powder_sensitivity = true \ No newline at end of file +max_calc_step_size = { value = 0.5, units = "Foot" } +use_powder_sensitivity = false \ No newline at end of file diff --git a/assets/.pybc-mixed.toml b/assets/.pybc-mixed.toml index 52fc208..782d9d7 100644 --- a/assets/.pybc-mixed.toml +++ b/assets/.pybc-mixed.toml @@ -1,7 +1,7 @@ # Config template for py_ballisticcalc title = "py_ballisticcalc config" -version = "2.0.0b3" +version = "2.0.0b4" [pybc.preferred_units] angular = 'Degree' @@ -21,5 +21,8 @@ target_height = 'Meter' twist = 'Inch' [pybc.calculator] -max_calc_step_size = [0.5, "Foot"] -use_powder_sensitivity = true \ No newline at end of file +use_powder_sensitivity = false + +[pybc.calculator.max_calc_step_size] +value = 0.5 +units = "Foot" \ No newline at end of file diff --git a/examples/canik_50bmg.py b/examples/canik_50bmg.py index 3126400..ef9f3fe 100644 --- a/examples/canik_50bmg.py +++ b/examples/canik_50bmg.py @@ -2,7 +2,7 @@ from py_ballisticcalc import DragModel, TableG1 from py_ballisticcalc import Ammo from py_ballisticcalc import Weapon, Shot, Calculator -from py_ballisticcalc import Settings as Set + PreferredUnits.distance = Unit.METER PreferredUnits.velocity = Unit.MPS diff --git a/examples/ukrop_338lm_300gr_smk.py b/examples/ukrop_338lm_300gr_smk.py index 57deed0..ac91db5 100644 --- a/examples/ukrop_338lm_300gr_smk.py +++ b/examples/ukrop_338lm_300gr_smk.py @@ -1,7 +1,7 @@ """Example of library usage""" from py_ballisticcalc import * -from py_ballisticcalc import Settings as Set + # set global library settings PreferredUnits.velocity = Velocity.MPS @@ -11,7 +11,7 @@ PreferredUnits.sight_height = Distance.Centimeter PreferredUnits.drop = Distance.Centimeter -Set.USE_POWDER_SENSITIVITY = True # enable muzzle velocity correction my powder temperature +set_global_use_powder_sensitivity(True) # enable muzzle velocity correction my powder temperature # define params with default prefer_units weight, diameter = 300, 0.338 diff --git a/py_ballisticcalc/__init__.py b/py_ballisticcalc/__init__.py index 15cce09..0efeeb3 100644 --- a/py_ballisticcalc/__init__.py +++ b/py_ballisticcalc/__init__.py @@ -8,12 +8,144 @@ __credits__ = ["o-murphy", "dbookstaber"] +import os + from .backend import * from .drag_tables import * from .drag_model import * -from .settings import * from .interface import * +from .logger import logger from .trajectory_data import * from .conditions import * from .munition import * from .unit import * + +try: + import tomllib +except ImportError: + import tomli as tomllib + + +def _load_config(filepath=None): + + def find_pybc_toml(start_dir=os.getcwd()): + """ + Search for the pyproject.toml file starting from the specified directory. + :param start_dir: (str) The directory to start searching from. Default is the current working directory. + :return: str: The absolute path to the pyproject.toml file if found, otherwise None. + """ + current_dir = os.path.abspath(start_dir) + while True: + # Check if pybc.toml or .pybc.toml exists in the current directory + pybc_paths = [ + os.path.join(current_dir, '.pybc.toml'), + os.path.join(current_dir, 'pybc.toml'), + ] + for pypc_path in pybc_paths: + if os.path.exists(pypc_path): + return os.path.abspath(pypc_path) + + # Move to the parent directory + parent_dir = os.path.dirname(current_dir) + + # If we have reached the root directory, stop searching + if parent_dir == current_dir: + return None + + current_dir = parent_dir + + if filepath is None: + if (filepath := find_pybc_toml()) is None: + find_pybc_toml(os.path.dirname(__file__)) + + logger.info(f"Found {os.path.basename(filepath)} at {os.path.dirname(filepath)}") + + with open(filepath, "rb") as fp: + _config = tomllib.load(fp) + + if _pybc := _config.get('pybc'): + if preferred_units := _pybc.get('preferred_units'): + PreferredUnits.set(**preferred_units) + else: + logger.warning("Config has not `pybc.preferred_units` section") + + if calculator := _pybc.get('calculator'): + if max_calc_step_size := calculator.get('max_calc_step_size'): + try: + _val = max_calc_step_size.get("value") + _units = Unit[max_calc_step_size.get("units")] + set_global_max_calc_step_size(_units(_val)) + except (KeyError, TypeError, ValueError): + logger.warning("Wrong max_calc_step_size units or value") + + if use_powder_sensitivity := calculator.get('use_powder_sensitivity'): + set_global_use_powder_sensitivity(use_powder_sensitivity) + else: + logger.warning("Config has not `pybc.calculator` section") + else: + logger.warning("Config has not `pybc` section") + + +def _basic_config(filename=None, + max_calc_step_size: [float, Distance] = None, + use_powder_sensitivity: bool = False, + preferred_units: dict[str, Unit] = None): + + """ + Method to load preferred units from file or Mapping + """ + if filename and (preferred_units or max_calc_step_size or use_powder_sensitivity): + raise ValueError("Can't use preferred_units and config file at same time") + if not filename and (preferred_units or max_calc_step_size or use_powder_sensitivity): + if preferred_units: + PreferredUnits.set(**preferred_units) + if max_calc_step_size: + set_global_max_calc_step_size(max_calc_step_size) + if use_powder_sensitivity: + set_global_use_powder_sensitivity(use_powder_sensitivity) + else: + # trying to load definitions from pybc.toml + _load_config(filename) + + +basicConfig = _basic_config + +__all__ = [ + 'Calculator', + 'basicConfig', + 'logger', + 'TrajectoryCalc', + 'get_global_max_calc_step_size', + 'get_global_use_powder_sensitivity', + 'set_global_max_calc_step_size', + 'set_global_use_powder_sensitivity', + 'reset_globals', + 'DragModel', + 'DragDataPoint', + 'BCPoint', + 'DragModelMultiBC', + 'TrajectoryData', + 'HitResult', + 'TrajFlag', + 'Atmo', + 'Wind', + 'Shot', + 'Weapon', + 'Ammo', + 'Unit', + 'AbstractUnit', + 'AbstractUnitType', + 'UnitProps', + 'UnitPropsDict', + 'Distance', + 'Velocity', + 'Angular', + 'Temperature', + 'Pressure', + 'Energy', + 'Weight', + 'Dimension', + 'PreferredUnits' +] + +__all__ += ["TableG%s" % n for n in (1, 7, 2, 5, 6, 8, 'I', 'S')] diff --git a/py_ballisticcalc/backend.py b/py_ballisticcalc/backend.py index 40b7523..f52d195 100644 --- a/py_ballisticcalc/backend.py +++ b/py_ballisticcalc/backend.py @@ -5,13 +5,30 @@ # try to use cython based backend try: - from py_ballisticcalc_exts import TrajectoryCalc + from py_ballisticcalc_exts import (TrajectoryCalc, + get_global_max_calc_step_size, + get_global_use_powder_sensitivity, + set_global_max_calc_step_size, + set_global_use_powder_sensitivity, + reset_globals) logger.info("Binary modules found, running in binary mode") except ImportError as error: - from .trajectory_calc import TrajectoryCalc + from .trajectory_calc import (TrajectoryCalc, + get_global_max_calc_step_size, + get_global_use_powder_sensitivity, + set_global_max_calc_step_size, + set_global_use_powder_sensitivity, + reset_globals) logger.warning("Library running in pure python mode. " "For better performance install 'py_ballisticcalc.exts' package") -__all__ = ('TrajectoryCalc', ) +__all__ = ( + 'TrajectoryCalc', + 'get_global_max_calc_step_size', + 'get_global_use_powder_sensitivity', + 'set_global_max_calc_step_size', + 'set_global_use_powder_sensitivity', + 'reset_globals', +) diff --git a/py_ballisticcalc/drag_tables.py b/py_ballisticcalc/drag_tables.py index fb6115c..f1fa5d3 100644 --- a/py_ballisticcalc/drag_tables.py +++ b/py_ballisticcalc/drag_tables.py @@ -667,3 +667,6 @@ {'Mach': 3.95, 'CD': 0.9295}, {'Mach': 4.00, 'CD': 0.9280}, ] + + +__all__ = ["TableG%s" % n for n in (1, 7, 2, 5, 6, 8, 'I', 'S')] diff --git a/py_ballisticcalc/example.py b/py_ballisticcalc/example.py index f0536e1..a331d41 100644 --- a/py_ballisticcalc/example.py +++ b/py_ballisticcalc/example.py @@ -2,7 +2,7 @@ # pylint: disable=wildcard-import,unused-wildcard-import from py_ballisticcalc import * -from py_ballisticcalc import Settings as Set + # Modify default prefer_units PreferredUnits.velocity = Velocity.FPS @@ -10,7 +10,7 @@ PreferredUnits.distance = Distance.Meter PreferredUnits.sight_height = Distance.Centimeter -Set.USE_POWDER_SENSITIVITY = True # Correct muzzle velocity for powder temperature +set_global_use_powder_sensitivity(True) # Correct muzzle velocity for powder temperature # Define ammunition parameters weight, diameter = 168, 0.308 # Numbers will be assumed to use default Settings.Units diff --git a/py_ballisticcalc/settings.py b/py_ballisticcalc/settings.py deleted file mode 100644 index 7e05ceb..0000000 --- a/py_ballisticcalc/settings.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Global settings of the py_ballisticcalc library""" -import logging -from .unit import Distance, PreferredUnits - -__all__ = ('Settings',) - - -class Settings: # pylint: disable=too-few-public-methods - """Global settings class of the py_ballisticcalc library""" - - _MAX_CALC_STEP_SIZE: float = 0.5 - USE_POWDER_SENSITIVITY: bool = False - - @classmethod - def set_max_calc_step_size(cls, value: [float, Distance]): - """ - _MAX_CALC_STEP_SIZE setter - :param value: [float, Distance] maximum calculation step (used internally) - """ - - logging.warning("Settings._MAX_CALC_STEP_SIZE: change this property " - "only if you know what you are doing; " - "too big step can corrupt calculation accuracy") - - if not isinstance(value, (Distance, float, int)): - raise ValueError("MAX_CALC_STEP_SIZE has to be a type of 'Distance'") - cls._MAX_CALC_STEP_SIZE = PreferredUnits.distance(value) >> Distance.Foot - - @classmethod - def get_max_calc_step_size(cls) -> [float, Distance]: - return cls._MAX_CALC_STEP_SIZE diff --git a/py_ballisticcalc/trajectory_calc.py b/py_ballisticcalc/trajectory_calc.py index a66923d..cc55359 100644 --- a/py_ballisticcalc/trajectory_calc.py +++ b/py_ballisticcalc/trajectory_calc.py @@ -9,11 +9,17 @@ from .drag_model import DragDataPoint from .conditions import Atmo, Shot, Wind from .munition import Ammo -from .settings import Settings from .trajectory_data import TrajectoryData, TrajFlag -from .unit import Distance, Angular, Velocity, Weight, Energy, Pressure, Temperature +from .unit import Distance, Angular, Velocity, Weight, Energy, Pressure, Temperature, PreferredUnits -__all__ = ('TrajectoryCalc',) +__all__ = ( + 'TrajectoryCalc', + 'get_global_max_calc_step_size', + 'get_global_use_powder_sensitivity', + 'set_global_max_calc_step_size', + 'set_global_use_powder_sensitivity', + 'reset_globals' +) cZeroFindingAccuracy = 0.000005 cMinimumVelocity = 50.0 @@ -21,6 +27,37 @@ cMaxIterations = 20 cGravityConstant = -32.17405 +_globalUsePowderSensitivity = False +_globalMaxCalcStepSize = Distance.Foot(0.5) + + +def get_global_max_calc_step_size() -> Distance: + return _globalMaxCalcStepSize + + +def get_global_use_powder_sensitivity() -> bool: + return _globalUsePowderSensitivity + + +def reset_globals() -> None: + global _globalUsePowderSensitivity, _globalMaxCalcStepSize + _globalUsePowderSensitivity = False + _globalMaxCalcStepSize = Distance.Foot(0.5) + + +def set_global_max_calc_step_size(value: [float, Distance]) -> None: + global _globalMaxCalcStepSize + if (_value := PreferredUnits.distance(value)).raw_value <= 0: + raise ValueError("_globalMaxCalcStepSize have to be > 0") + _globalMaxCalcStepSize = PreferredUnits.distance(value) + + +def set_global_use_powder_sensitivity(value: bool) -> None: + global _globalUsePowderSensitivity + if not isinstance(value, bool): + raise TypeError(f"set_global_use_powder_sensitivity {value=} is not a boolean") + _globalUsePowderSensitivity = value + class CurvePoint(NamedTuple): """Coefficients for quadratic interpolation""" @@ -110,9 +147,10 @@ def get_calc_step(step: float = 0): :param step: proposed step size :return: step size for calculations (in feet) """ + preferred_step = _globalMaxCalcStepSize >> Distance.Foot if step == 0: - return Settings.get_max_calc_step_size() / 2.0 - return min(step, Settings.get_max_calc_step_size()) / 2.0 + return preferred_step / 2.0 + return min(step, preferred_step) / 2.0 def trajectory(self, shot_info: Shot, max_range: Distance, dist_step: Distance, extra_data: bool = False): @@ -138,7 +176,7 @@ def _init_trajectory(self, shot_info: Shot): self.cant_sine = math.sin(shot_info.cant_angle >> Angular.Radian) self.alt0 = shot_info.atmo.altitude >> Distance.Foot self.calc_step = self.get_calc_step() - if Settings.USE_POWDER_SENSITIVITY: + if _globalUsePowderSensitivity: self.muzzle_velocity = shot_info.ammo.get_velocity_for_temp(shot_info.atmo.temperature) >> Velocity.FPS else: self.muzzle_velocity = shot_info.ammo.mv >> Velocity.FPS diff --git a/py_ballisticcalc/unit.py b/py_ballisticcalc/unit.py index 5d468aa..9672aca 100644 --- a/py_ballisticcalc/unit.py +++ b/py_ballisticcalc/unit.py @@ -1,7 +1,7 @@ """ Useful types for prefer_units of measurement conversion for ballistics calculations """ -import os + import sys from abc import ABC, abstractmethod from dataclasses import dataclass, MISSING, Field @@ -11,14 +11,11 @@ from py_ballisticcalc.logger import logger -try: - import tomllib -except ImportError: - import tomli as tomllib -__all__ = ('Unit', 'AbstractUnit', 'UnitProps', 'UnitPropsDict', 'Distance', +__all__ = ('Unit', 'AbstractUnit', 'AbstractUnitType', 'UnitProps', + 'UnitPropsDict', 'Distance', 'Velocity', 'Angular', 'Temperature', 'Pressure', - 'Energy', 'Weight', 'Dimension', 'PreferredUnits', 'basicConfig') + 'Energy', 'Weight', 'Dimension', 'PreferredUnits') AbstractUnitType = TypeVar('AbstractUnitType', bound='AbstractUnit') @@ -671,73 +668,43 @@ def __setattr__(self, key, value): super().__setattr__(key, value) + @classmethod + def defaults(self): + """resets preferred units to defaults""" + self.angular = Unit.Degree + self.distance = Unit.Yard + self.velocity = Unit.FPS + self.pressure = Unit.InHg + self.temperature = Unit.Fahrenheit + self.diameter = Unit.Inch + self.length = Unit.Inch + self.weight = Unit.Grain + self.adjustment = Unit.Mil + self.drop = Unit.Inch + self.energy = Unit.FootPound + self.ogw = Unit.Pound + self.sight_height = Unit.Inch + self.target_height = Unit.Inch + self.twist = Unit.Inch + @classmethod def set(cls, **kwargs): """set preferred units from Mapping""" for attribute, value in kwargs.items(): - try: - if hasattr(PreferredUnits, attribute): - setattr(PreferredUnits, attribute, Unit[value]) - else: - logger.warning(f"{attribute=} not found in preferred_units") - except KeyError: - logger.warning(f"{value=} not found in preferred_units") - @classmethod - def _load_config(cls, filepath=None): - - def find_pybc_toml(start_dir=os.path.dirname(__file__)): - """ - Search for the pyproject.toml file starting from the specified directory. - :param start_dir: (str) The directory to start searching from. Default is the current working directory. - :return: str: The absolute path to the pyproject.toml file if found, otherwise None. - """ - current_dir = os.path.abspath(start_dir) - while True: - # Check if pybc.toml or .pybc.toml exists in the current directory - pybc_paths = [ - os.path.join(current_dir, '.pybc.toml'), - os.path.join(current_dir, 'pybc.toml'), - ] - for pypc_path in pybc_paths: - if os.path.exists(pypc_path): - return os.path.abspath(pypc_path) - - # Move to the parent directory - parent_dir = os.path.dirname(current_dir) - - # If we have reached the root directory, stop searching - if parent_dir == current_dir: - return None - - current_dir = parent_dir - - if filepath is None: - filepath = find_pybc_toml() - - with open(filepath, "rb") as fp: - _config = tomllib.load(fp) - - if _pybc := _config.get('pybc'): - if preferred_units := _pybc.get('preferred_units'): - cls.set(**preferred_units) + if hasattr(PreferredUnits, attribute): + if isinstance(value, Unit): + setattr(PreferredUnits, attribute, value) + elif isinstance(value, str): + try: + setattr(PreferredUnits, attribute, Unit[value]) + except KeyError: + logger.warning(f"{value=} not a member of Unit") else: - logger.warning("Config has not `pybc.preferred_units` section") + logger.warning(f"type of {value=} have not been converted to a member of Unit") else: - logger.warning("Config has not `pybc` section") + logger.warning(f"{attribute=} not found in preferred_units") - @classmethod - def basic_config(cls, filename=None, **preferred_units): - """ - Method to load preferred units from file or Mapping - """ - if filename and preferred_units: - raise ValueError("Can't use preferred_units and config file at same time") - if preferred_units: - cls.set(**preferred_units) - else: - # trying to load definitions from pybc.toml - cls._load_config(filename) # pylint: disable=redefined-builtin,too-few-public-methods,too-many-arguments @@ -774,9 +741,3 @@ def __rshift__(self, other): @abstractmethod def __lshift__(self, other): ... - - -PreferredUnits.basic_config() # init PreferredUnits - -# export method globally -basicConfig = PreferredUnits.basic_config diff --git a/py_ballisticcalc_exts/py_ballisticcalc_exts/trajectory_calc.pyx b/py_ballisticcalc_exts/py_ballisticcalc_exts/trajectory_calc.pyx index 61f1870..f81ae87 100644 --- a/py_ballisticcalc_exts/py_ballisticcalc_exts/trajectory_calc.pyx +++ b/py_ballisticcalc_exts/py_ballisticcalc_exts/trajectory_calc.pyx @@ -3,11 +3,17 @@ cimport cython from py_ballisticcalc.conditions import Shot, Wind from py_ballisticcalc.munition import Ammo -from py_ballisticcalc.settings import Settings from py_ballisticcalc.trajectory_data import TrajectoryData from py_ballisticcalc.unit import * -__all__ = ('TrajectoryCalc',) +__all__ = ( + 'TrajectoryCalc', + 'get_global_max_calc_step_size', + 'get_global_use_powder_sensitivity', + 'set_global_max_calc_step_size', + 'set_global_use_powder_sensitivity', + 'reset_globals' +) cdef double cZeroFindingAccuracy = 0.000005 cdef double cMinimumVelocity = 50.0 @@ -15,6 +21,37 @@ cdef double cMaximumDrop = -15000 cdef int cMaxIterations = 20 cdef double cGravityConstant = -32.17405 +cdef int _globalUsePowderSensitivity = False +cdef object _globalMaxCalcStepSize = Distance.Foot(0.5) + +def get_global_max_calc_step_size() -> Distance: + return _globalMaxCalcStepSize + + +def get_global_use_powder_sensitivity() -> bool: + return bool(_globalUsePowderSensitivity) + + +def set_global_max_calc_step_size(value: [object, float]) -> None: + global _globalMaxCalcStepSize + if (_value := PreferredUnits.distance(value)).raw_value <= 0: + raise ValueError("_globalMaxCalcStepSize have to be > 0") + _globalMaxCalcStepSize = PreferredUnits.distance(value) + + +def set_global_use_powder_sensitivity(value: bool) -> None: + global _globalUsePowderSensitivity + if not isinstance(value, bool): + raise TypeError(f"set_global_use_powder_sensitivity {value=} is not a boolean") + _globalUsePowderSensitivity = int(value) + + +def reset_globals() -> None: + global _globalUsePowderSensitivity, _globalMaxCalcStepSize + _globalUsePowderSensitivity = False + _globalMaxCalcStepSize = Distance.Foot(0.5) + + cdef struct CurvePoint: double a, b, c @@ -158,7 +195,7 @@ cdef class TrajectoryCalc: self.cant_sine = sin(shot_info.cant_angle >> Angular.Radian) self.alt0 = shot_info.atmo.altitude >> Distance.Foot self.calc_step = get_calc_step() - if Settings.USE_POWDER_SENSITIVITY: + if _globalUsePowderSensitivity: self.muzzle_velocity = shot_info.ammo.get_velocity_for_temp(shot_info.atmo.temperature) >> Velocity.FPS else: self.muzzle_velocity = shot_info.ammo.mv >> Velocity.FPS @@ -398,13 +435,13 @@ cdef double get_correction(double distance, double offset): return atan(offset / distance) return 0 # better None - +@cython.cdivision(True) cdef double get_calc_step(double step = 0): - cdef double defined_max = Settings.get_max_calc_step_size() + cdef double preferred_step = _globalMaxCalcStepSize >> Distance.Foot # cdef double defined_max = 0.5 # const will be better optimized with cython if step == 0: - return defined_max / 2.0 - return min(step, defined_max) / 2.0 + return preferred_step / 2.0 + return min(step, preferred_step) / 2.0 cdef double calculate_energy(double bullet_weight, double velocity): return bullet_weight * pow(velocity, 2) / 450400 diff --git a/py_ballisticcalc_exts/pyproject.toml b/py_ballisticcalc_exts/pyproject.toml index a3a3f75..70dcfee 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.0.0b3" +version = "2.0.0b4" authors = [ { name="o-murphy", email="thehelixpg@gmail.com" }, diff --git a/py_ballisticcalc_exts/setup.py b/py_ballisticcalc_exts/setup.py index 02123a9..45b40af 100644 --- a/py_ballisticcalc_exts/setup.py +++ b/py_ballisticcalc_exts/setup.py @@ -6,7 +6,7 @@ from pathlib import Path from setuptools import setup, Extension -import numpy +# import numpy try: from Cython.Build import cythonize @@ -66,7 +66,7 @@ def iter_extensions(path) -> list: ext = Extension(ext_name, [ext_path.as_posix()], extra_compile_args=extra_compile_args, - include_dirs=[numpy.get_include()] + # include_dirs=[numpy.get_include()] ) founded_extensions.append(ext) return founded_extensions diff --git a/pyproject.toml b/pyproject.toml index 3fe96b5..40abce3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] name = "py_ballisticcalc" -version = "2.0.0b3" +version = "2.0.0b4" authors = [ { name="o-murphy", email="thehelixpg@gmail.com" }, @@ -54,6 +54,6 @@ exclude = ["py_ballisticcalc_exts*"] [project.optional-dependencies] -exts = ['py_ballisticcalc.exts==2.0.0b3'] +exts = ['py_ballisticcalc.exts==2.0.0b4'] lint = ['pylint', 'flake8'] charts = ['matplotlib', 'pandas'] diff --git a/tests/test_computer.py b/tests/test_computer.py index d3d9a7c..3e4b8cf 100644 --- a/tests/test_computer.py +++ b/tests/test_computer.py @@ -2,10 +2,13 @@ import unittest import copy -from py_ballisticcalc import DragModel, Ammo, Weapon, Calculator, Shot, Wind, Atmo, TableG7 -from py_ballisticcalc import Settings as Set +from py_ballisticcalc import ( + DragModel, Ammo, Weapon, Calculator, Shot, Wind, Atmo, TableG7, + get_global_use_powder_sensitivity, set_global_use_powder_sensitivity +) from py_ballisticcalc.unit import * + class TestComputer(unittest.TestCase): """Basic verifications that wind, spin, and cant values produce effects of correct sign and magnitude""" @@ -160,15 +163,15 @@ def test_ammo_optional(self): self.assertEqual(t.trajectory[5].height, self.baseline_trajectory[5].height) def test_powder_sensitivity(self): - """With USE_POWDER_SENSITIVITY: Reducing temperature should reduce muzzle velocity""" - previous = Set.USE_POWDER_SENSITIVITY - Set.USE_POWDER_SENSITIVITY = True + """With _globalUsePowderSensitivity: Reducing temperature should reduce muzzle velocity""" + previous = get_global_use_powder_sensitivity() + set_global_use_powder_sensitivity(True) self.ammo.calc_powder_sens(Velocity.FPS(2550), Temperature.Celsius(0)) cold = Atmo(temperature=Temperature.Celsius(-5)) shot = Shot(weapon=self.weapon, ammo=self.ammo, atmo=cold) t = self.calc.fire(shot=shot, trajectory_range=self.range, trajectory_step=self.step) self.assertLess(t.trajectory[0].velocity, self.baseline_trajectory[0].velocity) - Set.USE_POWDER_SENSITIVITY = previous + set_global_use_powder_sensitivity(previous) #endregion Ammo diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py index 1514017..8a0e8c2 100644 --- a/tests/test_config_loader.py +++ b/tests/test_config_loader.py @@ -1,28 +1,40 @@ import os from unittest import TestCase -from py_ballisticcalc import basicConfig, PreferredUnits, Unit +from py_ballisticcalc import (basicConfig, PreferredUnits, Unit, + get_global_max_calc_step_size, reset_globals) + +ASSETS_DIR = os.path.join( + os.path.dirname( + os.path.dirname(__file__) + ), 'assets') class TestConfigLoader(TestCase): def test_preferred_units_load(self): - basicConfig() - self.assertEqual(PreferredUnits.distance, Unit.Yard) - - def test_custom_config_path(self): + with self.subTest("env"): + basicConfig() + self.assertEqual(PreferredUnits.distance, Unit.Yard) - assets_dir = os.path.join( - os.path.dirname( - os.path.dirname(__file__) - ), 'assets') + with self.subTest("manual"): + basicConfig(max_calc_step_size=Unit.Meter(0.3), preferred_units={ + 'distance': Unit.Meter + }) + self.assertEqual(get_global_max_calc_step_size().units, Unit.Meter) + self.assertEqual(PreferredUnits.distance, Unit.Meter) - basicConfig(os.path.join(assets_dir, ".pybc-imperial.toml")) - self.assertEqual(PreferredUnits.distance, Unit.Foot) + with self.subTest("imperial"): + basicConfig(os.path.join(ASSETS_DIR, ".pybc-imperial.toml")) + self.assertEqual(PreferredUnits.distance, Unit.Foot) - basicConfig(os.path.join(assets_dir, ".pybc-metrics.toml")) - self.assertEqual(PreferredUnits.distance, Unit.Meter) - - basicConfig(os.path.join(assets_dir, ".pybc-mixed.toml")) - self.assertEqual(PreferredUnits.velocity, Unit.MPS) + with self.subTest("mixed"): + basicConfig(os.path.join(ASSETS_DIR, ".pybc-metrics.toml")) + self.assertEqual(PreferredUnits.distance, Unit.Meter) + with self.subTest("mixed"): + basicConfig(os.path.join(ASSETS_DIR, ".pybc-mixed.toml")) + self.assertEqual(PreferredUnits.velocity, Unit.MPS) + basicConfig() + reset_globals() + PreferredUnits.defaults() diff --git a/tests/test_units.py b/tests/test_units.py index a0ab613..482dc08 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -1,9 +1,6 @@ -import typing import unittest -import warnings -from dataclasses import field, dataclass +from dataclasses import dataclass -from py_ballisticcalc import Settings from py_ballisticcalc.unit import * @@ -16,7 +13,6 @@ def back_n_forth(test, value, units): class TestPrefUnits(unittest.TestCase): def test_pref(self): - @dataclass class TestClass(PreferredUnits.Mixin): as_metadata_str: [float, Distance] = Dimension(prefer_units='sight_height')