physpyx 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {physpyx-2.2.0 → physpyx-2.3.0}/PKG-INFO +1 -1
- physpyx-2.3.0/pyproject.toml +48 -0
- physpyx-2.2.0/pyproject.toml → physpyx-2.3.0/pyproject.toml.orig +2 -1
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/__init__.py +2 -1
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/astronomy.py +4 -3
- physpyx-2.3.0/src/physpyx/constants.py +75 -0
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/context.py +13 -2
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/manager.py +1 -1
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/nuclide.py +20 -26
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/physpyx.py +2 -2
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/qty.py +6 -8
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/quantity.py +26 -143
- physpyx-2.3.0/src/physpyx/registry.py +64 -0
- {physpyx-2.2.0 → physpyx-2.3.0}/README.md +0 -0
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/data/all-nuclides.csv +0 -0
- {physpyx-2.2.0 → physpyx-2.3.0}/src/physpyx/helper.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "physpyx"
|
|
3
|
+
version = "2.3.0"
|
|
4
|
+
description = "Provides a way to use python code in LaTeX"
|
|
5
|
+
classifiers = [
|
|
6
|
+
"Programming Language :: Python :: 3",
|
|
7
|
+
"Operating System :: Unix",
|
|
8
|
+
"Topic :: Education",
|
|
9
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
10
|
+
]
|
|
11
|
+
license = "GPL-3.0-or-later"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.13"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy>=2.3.4",
|
|
16
|
+
"pint>=0.25.1",
|
|
17
|
+
"scipy>=1.17.0",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[[project.authors]]
|
|
21
|
+
name = "Jérôme Dufour"
|
|
22
|
+
email = "jerome.dufour@eduvaud.ch"
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://bitbucket.org/jdufour/physpyx/src/master/"
|
|
26
|
+
Changelog = "https://bitbucket.org/jdufour/physpyx/src/master/CHANGELOG.md"
|
|
27
|
+
Documentation = "https://physpyx.readthedocs.io/fr/latest/"
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = [
|
|
31
|
+
"mypy>=1.18.1",
|
|
32
|
+
"ipdb>=0.13.13",
|
|
33
|
+
"scipy-stubs>=1.17.1.5",
|
|
34
|
+
"ruff>=0.16.0",
|
|
35
|
+
]
|
|
36
|
+
docs = [
|
|
37
|
+
"sphinx>=7.1.2",
|
|
38
|
+
"sphinx-rtd-theme>=3.0.2",
|
|
39
|
+
"sphinx-argparse>=0.5.2",
|
|
40
|
+
"myst-parser>=4.0.1",
|
|
41
|
+
"sphinx-autodoc2>=0.5.0",
|
|
42
|
+
"sphinx-autobuild>=2025.8.25",
|
|
43
|
+
"sphinx-design>=0.7.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["uv_build>=0.11.0,<0.12.0"]
|
|
48
|
+
build-backend = "uv_build"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "physpyx"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Jérôme Dufour", email="jerome.dufour@eduvaud.ch" },
|
|
6
6
|
]
|
|
@@ -25,6 +25,7 @@ dev = [
|
|
|
25
25
|
"mypy>=1.18.1",
|
|
26
26
|
"ipdb>=0.13.13",
|
|
27
27
|
"scipy-stubs>=1.17.1.5",
|
|
28
|
+
"ruff>=0.16.0",
|
|
28
29
|
]
|
|
29
30
|
docs = [
|
|
30
31
|
"sphinx>=7.1.2",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .astronomy import Orbit
|
|
2
|
+
from .context import NuclearContext
|
|
2
3
|
from .nuclide import NuclideTable
|
|
3
4
|
from .physpyx import PhysPyX
|
|
4
5
|
from .qty import Qty
|
|
5
6
|
|
|
6
|
-
__all__ = ["NuclideTable", "
|
|
7
|
+
__all__ = ["NuclearContext", "NuclideTable", "Orbit", "PhysPyX", "Qty"]
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
from numpy.typing import NDArray
|
|
3
|
-
from scipy.special import ellipk, ellipkinc
|
|
4
5
|
from scipy.optimize import newton
|
|
5
|
-
from
|
|
6
|
+
from scipy.special import ellipk, ellipkinc
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class Orbit:
|
|
@@ -77,7 +78,7 @@ class Orbit:
|
|
|
77
78
|
raise ValueError(f"{every} is not a divisor {steps} steps")
|
|
78
79
|
|
|
79
80
|
dt = self.T / steps
|
|
80
|
-
for i in range(
|
|
81
|
+
for i in range(steps + 1):
|
|
81
82
|
if i % every == 0:
|
|
82
83
|
yield self.x
|
|
83
84
|
self.step(dt)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from json import loads
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from physpyx.nuclide import NuclideTable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Constants:
|
|
9
|
+
_CONSTANTS: ClassVar[dict[str, tuple[float | int, str, str]]] = {
|
|
10
|
+
"cesium_standard": (9192631770, "hertz", "d"), # SI defining constant
|
|
11
|
+
"c": (299792458, "meter per second", "d"), # SI defining constant
|
|
12
|
+
"h": (6.62607015e-34, "joule second", ".8e"), # SI defining constant
|
|
13
|
+
"qe": (1.602176634e-19, "coulomb", ".3e"), # SI defining constant
|
|
14
|
+
"kB": (1.380649e-23, "joule per kelvin", "e"), # SI defining constant
|
|
15
|
+
"Na": (6.02214076e23, "", ".8e"), # SI defining constant
|
|
16
|
+
"R": (8.31446261815324, "joule per kelvin per mole", "f"),
|
|
17
|
+
"k": (8.987551e9, "newton meter squared per coulomb squared", ".0e"),
|
|
18
|
+
"epsilon0": (
|
|
19
|
+
8.8541878188e-12,
|
|
20
|
+
"coulomb squared per newton meter squared",
|
|
21
|
+
".3e",
|
|
22
|
+
),
|
|
23
|
+
"mu0": (1.2566370612696005e-06, "tesla meter per ampere", ".3e"),
|
|
24
|
+
"mn": (1.67492750056e-27, "kilogram", ".3e"),
|
|
25
|
+
"mp": (1.67262192595e-27, "kilogram", ".3e"),
|
|
26
|
+
"me": (9.1093837139e-31, "kilogram", ".3e"),
|
|
27
|
+
"a0": (5.29177210903e-11, "meter", "e"),
|
|
28
|
+
"G": (6.6743e-11, "newton meter squared per kilogram squared", ".2e"),
|
|
29
|
+
"g": (9.81, "meter per second squared", ".2f"),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def __init__(self, fname: Path | None):
|
|
33
|
+
self._fname = fname
|
|
34
|
+
self._nuclide_table = NuclideTable()
|
|
35
|
+
self._constants: dict[str, tuple[float, str, str]] = {}
|
|
36
|
+
|
|
37
|
+
def _get_default_constant(
|
|
38
|
+
self, name: str, fmt: str
|
|
39
|
+
) -> tuple[float, str, str] | None:
|
|
40
|
+
if data := self._CONSTANTS.get(name, None):
|
|
41
|
+
value, unit, default_fmt = data
|
|
42
|
+
return value, unit, default_fmt if fmt == "g" else fmt
|
|
43
|
+
|
|
44
|
+
def _get_custom_constant(
|
|
45
|
+
self, name: str, fmt: str
|
|
46
|
+
) -> tuple[float, str, str] | None:
|
|
47
|
+
if not self._constants and self._fname and self._fname.exists():
|
|
48
|
+
self._constants = loads(self._fname.read_bytes())
|
|
49
|
+
|
|
50
|
+
if data := self._constants.get(name, None):
|
|
51
|
+
value, unit, default_fmt = data
|
|
52
|
+
return value, unit, default_fmt if fmt == "g" else fmt
|
|
53
|
+
|
|
54
|
+
def _get_nuclide_data(self, name: str, fmt: str) -> tuple[float, str, str] | None:
|
|
55
|
+
try:
|
|
56
|
+
match name[0]:
|
|
57
|
+
case "m":
|
|
58
|
+
n = self._nuclide_table.get(symbol=name[1:])
|
|
59
|
+
return n.atomic_mass, "amu", "f" if fmt == "g" else fmt
|
|
60
|
+
case "t":
|
|
61
|
+
n = self._nuclide_table.get(symbol=name[1:])
|
|
62
|
+
if n.radioactivity.stable:
|
|
63
|
+
raise ValueError(f"No half-life for {n.symbol}")
|
|
64
|
+
return n.radioactivity.half_life, "second", "g"
|
|
65
|
+
except KeyError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def __call__(self, name: str, fmt: str) -> tuple[float, str, str]:
|
|
69
|
+
if data := (
|
|
70
|
+
self._get_default_constant(name, fmt)
|
|
71
|
+
or self._get_custom_constant(name, fmt)
|
|
72
|
+
or self._get_nuclide_data(name, fmt)
|
|
73
|
+
):
|
|
74
|
+
return data
|
|
75
|
+
raise ValueError(f"Constant '{name}' not found")
|
|
@@ -7,7 +7,7 @@ class SiunitxContext(Context):
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class NuclearContext(SiunitxContext):
|
|
10
|
-
def __init__(self,
|
|
10
|
+
def __init__(self, preferred_units: bool = True):
|
|
11
11
|
super().__init__("nuclear")
|
|
12
12
|
self.add_transformation(
|
|
13
13
|
"[mass]",
|
|
@@ -20,5 +20,16 @@ class NuclearContext(SiunitxContext):
|
|
|
20
20
|
lambda ureg, x: x / ureg.speed_of_light / ureg.speed_of_light,
|
|
21
21
|
)
|
|
22
22
|
self.preferred_units = (
|
|
23
|
-
{
|
|
23
|
+
{
|
|
24
|
+
UnitsContainer({"[mass]": 1}): "amu",
|
|
25
|
+
UnitsContainer(
|
|
26
|
+
{
|
|
27
|
+
"[mass]": 1,
|
|
28
|
+
"[length]": 2,
|
|
29
|
+
"[time]": -2,
|
|
30
|
+
}
|
|
31
|
+
): "megaelectronvolt",
|
|
32
|
+
}
|
|
33
|
+
if preferred_units
|
|
34
|
+
else {}
|
|
24
35
|
)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
from collections.abc import Generator, Iterable, Iterator
|
|
1
2
|
from enum import Enum
|
|
2
3
|
from logging import getLogger
|
|
3
4
|
from math import sqrt
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from pickle import dump, loads
|
|
6
7
|
from tempfile import gettempdir
|
|
7
|
-
from typing import
|
|
8
|
+
from typing import ClassVar, TypedDict
|
|
8
9
|
|
|
9
10
|
logger = getLogger(__name__)
|
|
10
11
|
|
|
@@ -59,7 +60,7 @@ class PossibleDecays(Enum):
|
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
class Channel:
|
|
62
|
-
_DECAY_MAP = {d: md for md in PossibleDecays for d in md.channels
|
|
63
|
+
_DECAY_MAP: ClassVar = {d: md for md in PossibleDecays for d in md.channels}
|
|
63
64
|
|
|
64
65
|
def __init__(self, channel: str, prob: float):
|
|
65
66
|
self.decay = self._DECAY_MAP[channel]
|
|
@@ -105,16 +106,15 @@ class DecayChannels:
|
|
|
105
106
|
self.channels.append(B)
|
|
106
107
|
look_for_C = True
|
|
107
108
|
|
|
108
|
-
if look_for_C:
|
|
109
|
-
if C
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
self.channels.append(C)
|
|
109
|
+
if look_for_C and (C := self._parse_decays(data[24], data[25])):
|
|
110
|
+
if C.prob < 1:
|
|
111
|
+
logger.warning(f"{symbol} skip 3rd (<1% | none)")
|
|
112
|
+
elif A.prob + B.prob + C.prob > 100:
|
|
113
|
+
logger.warning(
|
|
114
|
+
f"{symbol} skip 3rd ({A.prob + B.prob + C.prob:.0f}%>100%)"
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
self.channels.append(C)
|
|
118
118
|
|
|
119
119
|
if len(self.channels) == 1 and self.channels[0].prob == 0:
|
|
120
120
|
self.channels[0].prob = 100
|
|
@@ -214,8 +214,7 @@ class Nuclide:
|
|
|
214
214
|
with_mass: bool = False,
|
|
215
215
|
with_decay: bool = False,
|
|
216
216
|
) -> Generator[str]:
|
|
217
|
-
|
|
218
|
-
yield tex
|
|
217
|
+
yield from self.radioactivity.to_tex(self.N, self.Z)
|
|
219
218
|
|
|
220
219
|
if with_symbol:
|
|
221
220
|
yield rf"\node[anchor=north] at ({self.N + 0.5}, {self.Z + 1}) {{\ce{{^{self.A}{self.symbol}}}}};"
|
|
@@ -231,14 +230,13 @@ class Nuclide:
|
|
|
231
230
|
|
|
232
231
|
class NuclideTable:
|
|
233
232
|
def __init__(self):
|
|
234
|
-
self._by_numbers =
|
|
235
|
-
self._by_symbols =
|
|
233
|
+
self._by_numbers = {}
|
|
234
|
+
self._by_symbols = {}
|
|
236
235
|
|
|
237
236
|
def __iter__(self) -> Iterator[Nuclide]:
|
|
238
237
|
if not self._by_numbers:
|
|
239
238
|
self._load()
|
|
240
|
-
|
|
241
|
-
yield n
|
|
239
|
+
yield from self._by_numbers.values()
|
|
242
240
|
|
|
243
241
|
def _load(self) -> None:
|
|
244
242
|
pk_file = Path(gettempdir()) / "physpyx-nuclides.pkl"
|
|
@@ -354,14 +352,10 @@ class NuclideTable:
|
|
|
354
352
|
Nmin, Nmax = 1000, 0
|
|
355
353
|
Zmin, Zmax = 1000, 0
|
|
356
354
|
for n in nuclides:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if n.N > Nmax:
|
|
362
|
-
Nmax = n.N
|
|
363
|
-
if n.Z > Zmax:
|
|
364
|
-
Zmax = n.Z
|
|
355
|
+
Nmin = min(Nmin, n.N)
|
|
356
|
+
Zmin = min(Zmin, n.Z)
|
|
357
|
+
Nmax = max(Nmax, n.N)
|
|
358
|
+
Zmax = max(Zmax, n.Z)
|
|
365
359
|
|
|
366
360
|
yield rf"\draw[<->] ({Nmin - 0.1}, {Zmax + 1}) node[left]{{$Z$}} |- ({Nmax + 1}, {Zmin - 0.1}) node[below]{{$N$}};"
|
|
367
361
|
for i in range(Nmin, Nmax + 1):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Generator
|
|
2
2
|
from contextlib import contextmanager
|
|
3
3
|
|
|
4
4
|
from physpyx.manager import PTManager
|
|
@@ -20,7 +20,7 @@ class PhysPyX(PTManager):
|
|
|
20
20
|
indent = "\t" * self._stack
|
|
21
21
|
self._out += indent + f"\n{indent}".join(data) + "\n"
|
|
22
22
|
else:
|
|
23
|
-
raise
|
|
23
|
+
raise TypeError
|
|
24
24
|
|
|
25
25
|
@contextmanager
|
|
26
26
|
def begin(
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import sys
|
|
3
|
-
|
|
4
3
|
from logging import getLogger
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
4
|
|
|
5
|
+
from pint._typing import Scalar
|
|
7
6
|
from pint.facets.context import Context
|
|
8
7
|
|
|
8
|
+
from physpyx.constants import Constants
|
|
9
9
|
from physpyx.manager import PTManager
|
|
10
|
-
from physpyx.quantity import AltDict,
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from pint._typing import Scalar
|
|
10
|
+
from physpyx.quantity import AltDict, SiunitxQuantity
|
|
11
|
+
from physpyx.registry import SiunitxUnitRegistry
|
|
14
12
|
|
|
15
13
|
logger = getLogger(__name__)
|
|
16
14
|
|
|
@@ -42,9 +40,9 @@ class Qty(PTManager):
|
|
|
42
40
|
outfile.write(
|
|
43
41
|
"\\ExplSyntaxOn\n\\prop_gset_from_keyval:Nn \\g_physpyx_pty_prop {\n"
|
|
44
42
|
)
|
|
45
|
-
for
|
|
43
|
+
for variable, qty in sys._getframe(2).f_locals.items():
|
|
46
44
|
if isinstance(qty, SiunitxQuantity):
|
|
47
|
-
for name, new in qty.export(
|
|
45
|
+
for name, new in qty.export(variable):
|
|
48
46
|
outfile.write(f"\t{{{name}}} = {{{new}}},\n")
|
|
49
47
|
if old := current.pop(name, None):
|
|
50
48
|
if old != new:
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Generator, Iterator
|
|
2
2
|
from logging import getLogger
|
|
3
|
-
from
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from pint.registry import GenericUnitRegistry, Quantity, Unit
|
|
6
|
-
from pint.facets.context import Context
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Generator, TypedDict, cast
|
|
3
|
+
from typing import ClassVar, Self, TypedDict, cast
|
|
8
4
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from pint._typing import Magnitude, UnitLike
|
|
5
|
+
from numpy import cross, linalg, ndarray
|
|
6
|
+
from pint._typing import Magnitude, UnitLike
|
|
7
|
+
from pint.registry import Quantity
|
|
14
8
|
|
|
15
9
|
logger = getLogger(__name__)
|
|
16
10
|
|
|
@@ -31,7 +25,7 @@ class SiunitxQuantity(Quantity):
|
|
|
31
25
|
_preferred: bool
|
|
32
26
|
_alt: AltDict
|
|
33
27
|
|
|
34
|
-
_sinunitx_map = {
|
|
28
|
+
_sinunitx_map: ClassVar[dict[str, str]] = {
|
|
35
29
|
"astronomical_unit": "astronomicalunit",
|
|
36
30
|
"degree_Celsius": "celsius",
|
|
37
31
|
"delta_degree_Celsius": "celsius",
|
|
@@ -54,7 +48,7 @@ class SiunitxQuantity(Quantity):
|
|
|
54
48
|
compact: bool = False,
|
|
55
49
|
preferred: bool = True,
|
|
56
50
|
alt: AltDict | bool = False,
|
|
57
|
-
) ->
|
|
51
|
+
) -> Self:
|
|
58
52
|
"""
|
|
59
53
|
Return an instance of SiunitxQuantity
|
|
60
54
|
|
|
@@ -71,7 +65,7 @@ class SiunitxQuantity(Quantity):
|
|
|
71
65
|
:param alt: If provided, writes an additional alternative output with
|
|
72
66
|
different formatting. The string '_alt' is added to the base name.
|
|
73
67
|
"""
|
|
74
|
-
quantity = cast(
|
|
68
|
+
quantity = cast(Self, super().__new__(cls, value, units))
|
|
75
69
|
quantity._fmt = fmt
|
|
76
70
|
quantity._base = base
|
|
77
71
|
quantity._compact = compact
|
|
@@ -86,6 +80,17 @@ class SiunitxQuantity(Quantity):
|
|
|
86
80
|
else:
|
|
87
81
|
return alt
|
|
88
82
|
|
|
83
|
+
def __iter__(self) -> Iterator["SiunitxQuantity"]:
|
|
84
|
+
# NOTE: this is used to set formatting options in tuple expansions:
|
|
85
|
+
# rx, ry = qty([3, 2], "kilometer", fmt=".1e")
|
|
86
|
+
for val in super().__iter__():
|
|
87
|
+
val._fmt = self._fmt
|
|
88
|
+
val._base = self._base
|
|
89
|
+
val._compact = self._compact
|
|
90
|
+
val._preferred = self._preferred
|
|
91
|
+
val._alt = self._alt
|
|
92
|
+
yield val
|
|
93
|
+
|
|
89
94
|
def __str__(self) -> str:
|
|
90
95
|
quantity = self
|
|
91
96
|
if self._base:
|
|
@@ -146,6 +151,11 @@ class SiunitxQuantity(Quantity):
|
|
|
146
151
|
# "PlainQuantity[Unknown]"'
|
|
147
152
|
return cast(SiunitxQuantity, super().__neg__())
|
|
148
153
|
|
|
154
|
+
def __add__(self, other: object) -> "SiunitxQuantity":
|
|
155
|
+
# NOTE: to fix typing warning 'Cannot access attribute "???" for class
|
|
156
|
+
# "PlainQuantity[Unknown]"'
|
|
157
|
+
return cast(SiunitxQuantity, super().__add__(other))
|
|
158
|
+
|
|
149
159
|
def _format_unit(self) -> str:
|
|
150
160
|
num = ""
|
|
151
161
|
den = ""
|
|
@@ -176,7 +186,7 @@ class SiunitxQuantity(Quantity):
|
|
|
176
186
|
den += r"\per" + tmp
|
|
177
187
|
return num + den
|
|
178
188
|
|
|
179
|
-
def export(self, name: str) -> Generator[tuple[str, str]
|
|
189
|
+
def export(self, name: str) -> Generator[tuple[str, str]]:
|
|
180
190
|
try:
|
|
181
191
|
yield (
|
|
182
192
|
name,
|
|
@@ -189,7 +199,7 @@ class SiunitxQuantity(Quantity):
|
|
|
189
199
|
# the one raised in PTManager.__exit__
|
|
190
200
|
logger.error(f"exporting {name} failed, {self._fmt}")
|
|
191
201
|
e.add_note(f"exporting {name} failed")
|
|
192
|
-
raise
|
|
202
|
+
raise
|
|
193
203
|
|
|
194
204
|
if self._alt:
|
|
195
205
|
alt = self.fmtex(**self._alt)
|
|
@@ -257,130 +267,3 @@ class SiunitxQuantity(Quantity):
|
|
|
257
267
|
if not isinstance(other, SiunitxQuantity):
|
|
258
268
|
raise TypeError(f"Can't perform a cross product with a {type(other)}")
|
|
259
269
|
return cast(SiunitxQuantity, cross(self, other))
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
class SiunitxUnitRegistry(GenericUnitRegistry):
|
|
263
|
-
Quantity: type = SiunitxQuantity
|
|
264
|
-
Unit: type = Unit
|
|
265
|
-
|
|
266
|
-
def __init__(self):
|
|
267
|
-
super().__init__()
|
|
268
|
-
self.define("heure = hour")
|
|
269
|
-
self.define("heures = hours")
|
|
270
|
-
self.define("jour = day")
|
|
271
|
-
self.define("jours = days")
|
|
272
|
-
self.define("semaine = week")
|
|
273
|
-
self.define("semaines = weeks")
|
|
274
|
-
self.define("mois = month")
|
|
275
|
-
self.define("an = year")
|
|
276
|
-
self.define("ans = years")
|
|
277
|
-
self.define("celerity = speed_of_light")
|
|
278
|
-
self.define("chf = [currency] = franc")
|
|
279
|
-
self.define("ct = 0.01 * chf = centime")
|
|
280
|
-
self._preferred_units = {
|
|
281
|
-
Unit(name).dimensionality: name
|
|
282
|
-
for name in [
|
|
283
|
-
"meter",
|
|
284
|
-
"second",
|
|
285
|
-
"kilogram",
|
|
286
|
-
"ampere",
|
|
287
|
-
"kelvin",
|
|
288
|
-
"mole",
|
|
289
|
-
"meter per second",
|
|
290
|
-
"meter per second squared",
|
|
291
|
-
"newton",
|
|
292
|
-
"joule",
|
|
293
|
-
"watt",
|
|
294
|
-
"pascal",
|
|
295
|
-
"volt",
|
|
296
|
-
"ohm",
|
|
297
|
-
"tesla",
|
|
298
|
-
"henry",
|
|
299
|
-
"farad",
|
|
300
|
-
"coulomb",
|
|
301
|
-
]
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
def enable_contexts(self, *names_or_contexts: str | Context, **kwargs: Any) -> None:
|
|
305
|
-
for context in names_or_contexts:
|
|
306
|
-
if isinstance(context, SiunitxContext):
|
|
307
|
-
self._preferred_units |= context.preferred_units
|
|
308
|
-
return super().enable_contexts(*names_or_contexts, **kwargs)
|
|
309
|
-
|
|
310
|
-
def to_preferred_units(self, qty: SiunitxQuantity) -> SiunitxQuantity:
|
|
311
|
-
return cast(
|
|
312
|
-
SiunitxQuantity,
|
|
313
|
-
qty.to(to)
|
|
314
|
-
if (to := self._preferred_units.get(qty.dimensionality, None))
|
|
315
|
-
else qty.to_reduced_units(),
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
class Constants:
|
|
320
|
-
_CONSTANTS = {
|
|
321
|
-
"cesium_standard": (9192631770, "hertz", "d"), # SI defining constant
|
|
322
|
-
"c": (299792458, "meter per second", "d"), # SI defining constant
|
|
323
|
-
"h": (6.62607015e-34, "joule second", ".8e"), # SI defining constant
|
|
324
|
-
"qe": (1.602176634e-19, "coulomb", ".3e"), # SI defining constant
|
|
325
|
-
"kB": (1.380649e-23, "joule per kelvin", "e"), # SI defining constant
|
|
326
|
-
"Na": (6.02214076e23, "", ".8e"), # SI defining constant
|
|
327
|
-
"R": (8.31446261815324, "joule per kelvin per mole", "f"),
|
|
328
|
-
"k": (8.987551e9, "newton meter squared per coulomb squared", ".0e"),
|
|
329
|
-
"epsilon0": (
|
|
330
|
-
8.8541878188e-12,
|
|
331
|
-
"coulomb squared per newton meter squared",
|
|
332
|
-
".3e",
|
|
333
|
-
),
|
|
334
|
-
"mu0": (1.2566370612696005e-06, "tesla meter per ampere", ".3e"),
|
|
335
|
-
"mn": (1.67492750056e-27, "kilogram", ".3e"),
|
|
336
|
-
"mp": (1.67262192595e-27, "kilogram", ".3e"),
|
|
337
|
-
"me": (9.1093837139e-31, "kilogram", ".3e"),
|
|
338
|
-
"a0": (5.29177210903e-11, "meter", "e"),
|
|
339
|
-
"G": (6.6743e-11, "newton meter squared per kilogram squared", ".2e"),
|
|
340
|
-
"g": (9.81, "meter per second squared", ".2f"),
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
def __init__(self, fname: Path | None):
|
|
344
|
-
self._fname = fname
|
|
345
|
-
self._nuclide_table = NuclideTable()
|
|
346
|
-
self._constants: dict[str, tuple[float, str, str]] = {}
|
|
347
|
-
|
|
348
|
-
def _get_default_constant(
|
|
349
|
-
self, name: str, fmt: str
|
|
350
|
-
) -> tuple[float, str, str] | None:
|
|
351
|
-
if data := self._CONSTANTS.get(name, None):
|
|
352
|
-
value, unit, default_fmt = data
|
|
353
|
-
return value, unit, default_fmt if fmt == "g" else fmt
|
|
354
|
-
|
|
355
|
-
def _get_custom_constant(
|
|
356
|
-
self, name: str, fmt: str
|
|
357
|
-
) -> tuple[float, str, str] | None:
|
|
358
|
-
if not self._constants and self._fname and self._fname.exists():
|
|
359
|
-
self._constants = loads(self._fname.read_bytes())
|
|
360
|
-
|
|
361
|
-
if data := self._constants.get(name, None):
|
|
362
|
-
value, unit, default_fmt = data
|
|
363
|
-
return value, unit, default_fmt if fmt == "g" else fmt
|
|
364
|
-
|
|
365
|
-
def _get_nuclide_data(self, name: str, fmt: str) -> tuple[float, str, str] | None:
|
|
366
|
-
try:
|
|
367
|
-
match name[0]:
|
|
368
|
-
case "m":
|
|
369
|
-
n = self._nuclide_table.get(symbol=name[1:])
|
|
370
|
-
return n.atomic_mass, "amu", "f" if fmt == "g" else fmt
|
|
371
|
-
case "t":
|
|
372
|
-
n = self._nuclide_table.get(symbol=name[1:])
|
|
373
|
-
if n.radioactivity.stable:
|
|
374
|
-
raise ValueError(f"No half-life for {n.symbol}")
|
|
375
|
-
return n.radioactivity.half_life, "second", "g"
|
|
376
|
-
except KeyError:
|
|
377
|
-
pass
|
|
378
|
-
|
|
379
|
-
def __call__(self, name: str, fmt: str) -> tuple[float, str, str]:
|
|
380
|
-
if data := (
|
|
381
|
-
self._get_default_constant(name, fmt)
|
|
382
|
-
or self._get_custom_constant(name, fmt)
|
|
383
|
-
or self._get_nuclide_data(name, fmt)
|
|
384
|
-
):
|
|
385
|
-
return data
|
|
386
|
-
raise ValueError(f"Constant '{name}' not found")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
from pint.facets.context import Context
|
|
4
|
+
from pint.registry import GenericUnitRegistry, Unit
|
|
5
|
+
|
|
6
|
+
from physpyx.context import SiunitxContext
|
|
7
|
+
from physpyx.quantity import SiunitxQuantity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SiunitxUnitRegistry(GenericUnitRegistry):
|
|
11
|
+
Quantity: type = SiunitxQuantity
|
|
12
|
+
Unit: type = Unit
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.define("heure = hour")
|
|
17
|
+
self.define("heures = hours")
|
|
18
|
+
self.define("jour = day")
|
|
19
|
+
self.define("jours = days")
|
|
20
|
+
self.define("semaine = week")
|
|
21
|
+
self.define("semaines = weeks")
|
|
22
|
+
self.define("mois = month")
|
|
23
|
+
self.define("an = year")
|
|
24
|
+
self.define("ans = years")
|
|
25
|
+
self.define("celerity = speed_of_light")
|
|
26
|
+
self.define("chf = [currency] = franc")
|
|
27
|
+
self.define("ct = 0.01 * chf = centime")
|
|
28
|
+
self._preferred_units = {
|
|
29
|
+
Unit(name).dimensionality: name
|
|
30
|
+
for name in [
|
|
31
|
+
"meter",
|
|
32
|
+
"second",
|
|
33
|
+
"kilogram",
|
|
34
|
+
"ampere",
|
|
35
|
+
"kelvin",
|
|
36
|
+
"mole",
|
|
37
|
+
"meter per second",
|
|
38
|
+
"meter per second squared",
|
|
39
|
+
"newton",
|
|
40
|
+
"joule",
|
|
41
|
+
"watt",
|
|
42
|
+
"pascal",
|
|
43
|
+
"volt",
|
|
44
|
+
"ohm",
|
|
45
|
+
"tesla",
|
|
46
|
+
"henry",
|
|
47
|
+
"farad",
|
|
48
|
+
"coulomb",
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def enable_contexts(self, *names_or_contexts: str | Context, **kwargs: Any) -> None:
|
|
53
|
+
for context in names_or_contexts:
|
|
54
|
+
if isinstance(context, SiunitxContext):
|
|
55
|
+
self._preferred_units |= context.preferred_units
|
|
56
|
+
return super().enable_contexts(*names_or_contexts, **kwargs)
|
|
57
|
+
|
|
58
|
+
def to_preferred_units(self, qty: SiunitxQuantity) -> SiunitxQuantity:
|
|
59
|
+
return cast(
|
|
60
|
+
SiunitxQuantity,
|
|
61
|
+
qty.to(to)
|
|
62
|
+
if (to := self._preferred_units.get(qty.dimensionality, None))
|
|
63
|
+
else qty.to_reduced_units(),
|
|
64
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|