physpyx 2.0.0__tar.gz → 2.1.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.
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: physpyx
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Provides a way to use python code in LaTeX
5
5
  Author: Jérôme Dufour
6
6
  Author-email: Jérôme Dufour <jerome.dufour@eduvaud.ch>
7
+ License-Expression: GPL-3.0-or-later
7
8
  Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
9
9
  Classifier: Operating System :: Unix
10
10
  Classifier: Topic :: Education
11
11
  Classifier: Topic :: Scientific/Engineering :: Physics
@@ -1,17 +1,17 @@
1
1
  [project]
2
2
  name = "physpyx"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  authors = [
5
5
  { name="Jérôme Dufour", email="jerome.dufour@eduvaud.ch" },
6
6
  ]
7
7
  description = "Provides a way to use python code in LaTeX"
8
8
  classifiers = [
9
9
  "Programming Language :: Python :: 3",
10
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
11
10
  "Operating System :: Unix",
12
11
  "Topic :: Education",
13
12
  "Topic :: Scientific/Engineering :: Physics"
14
13
  ]
14
+ license = "GPL-3.0-or-later"
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.13"
17
17
  dependencies = [
@@ -24,6 +24,7 @@ dependencies = [
24
24
  dev = [
25
25
  "mypy>=1.18.1",
26
26
  "ipdb>=0.13.13",
27
+ "scipy-stubs>=1.17.1.5",
27
28
  ]
28
29
  docs = [
29
30
  "sphinx>=7.1.2",
@@ -36,7 +37,7 @@ docs = [
36
37
  ]
37
38
 
38
39
  [build-system]
39
- requires = ["uv_build>=0.10.9,<0.11.0"]
40
+ requires = ["uv_build>=0.11.0,<0.12.0"]
40
41
  build-backend = "uv_build"
41
42
 
42
43
  [project.urls]
@@ -90,7 +90,7 @@ class Orbit:
90
90
  return self.a * ellipkinc(phi, m) - arc_length
91
91
 
92
92
  for i in range(n + 1):
93
- phi = np.pi / 2 - newton(func, x0=np.pi / 2 * i / n, args=[P * i / n])
93
+ phi = np.pi / 2 - newton(func, x0=np.pi / 2 * i / n, args=(P * i / n))
94
94
  r = self.b / np.sqrt(1 - (self.e * np.cos(phi)) ** 2)
95
95
  yield np.array([-self.F + r * np.cos(phi), r * np.sin(phi)])
96
96
 
@@ -224,7 +224,7 @@ class Nuclide:
224
224
  if with_mass:
225
225
  label += rf"\num{{{self.atomic_mass:.6f}}}"
226
226
  if with_decay:
227
- label += "/".join(channel.label for channel in self.radioactivity)
227
+ label += str(self.radioactivity.decays.channels)
228
228
  if label:
229
229
  yield rf"\node[anchor=south] at ({self.N + 0.5}, {self.Z}) {{\tiny{{{label}}}}};"
230
230
 
@@ -337,7 +337,7 @@ class NuclideTable:
337
337
  :param y: y-position of the label (top of table if x=Zmax+1, bottom of table if x=Zmin-1)
338
338
  :param vertical: orientation of the label
339
339
  """
340
- decays = {c.decay for n in nuclides for c in n.radioactivity.decays}
340
+ decays = {c.decay for n in nuclides for c in n.radioactivity.decays.channels}
341
341
  for i, decay in enumerate(sorted(decays, key=lambda x: x.name)):
342
342
  pos = f"({x}, {y - i})" if vertical else f"({x + i}, {y})"
343
343
  yield rf"\fill[color={decay.color}!60] {pos} rectangle ++(1, 1) node[midway, black]{{{decay.label}}};"
@@ -4,7 +4,7 @@ import sys
4
4
  from logging import getLogger
5
5
  from pint._typing import Scalar
6
6
  from physpyx.manager import PTManager
7
- from physpyx.quantity import Constants, SiunitxQuantity, SiunitxUnitRegistry
7
+ from physpyx.quantity import AltDict, Constants, SiunitxQuantity, SiunitxUnitRegistry
8
8
 
9
9
  logger = getLogger(__name__)
10
10
 
@@ -41,34 +41,25 @@ class Qty(PTManager):
41
41
 
42
42
  def _write_successfile(self) -> None:
43
43
  if self._fname.exists():
44
- active = dict(re.findall(r"{(.*)} = {(.*)}", self._fname.read_text()))
44
+ current = dict(re.findall(r"{(.*)} = {(.*)}", self._fname.read_text()))
45
45
  else:
46
- active = {}
46
+ current = {}
47
47
  with self._fname.open("w") as outfile:
48
48
  outfile.write(
49
49
  "\\ExplSyntaxOn\n\\prop_gset_from_keyval:Nn \\g_physpyx_pty_prop {\n"
50
50
  )
51
51
  for name, qty in sys._getframe(2).f_locals.items():
52
52
  if isinstance(qty, SiunitxQuantity):
53
- if name == "mu0":
54
- qty = r"\qty[input-digits = 0123456789\pi]{4\pi e-7}{\tesla\meter\per\ampere}"
55
- try:
56
- outfile.write(f"\t{{{name}}} = {{{str(qty)}}},\n")
57
- if old := active.pop(name, None):
58
- if old != str(qty):
59
- logger.info(f"update {name}: {old} -> {qty}")
53
+ for name, new in qty.export(name):
54
+ outfile.write(f"\t{{{name}}} = {{{new}}},\n")
55
+ if old := current.pop(name, None):
56
+ if old != new:
57
+ logger.info(f"update {name}: {old} -> {new}")
60
58
  else:
61
- logger.info(f"add {name}: {qty}")
62
- except ValueError as e:
63
- # TODO: report the correct error in
64
- # PTManager.write_errorfile, not the one raised in
65
- # PTManager.__exit__
66
- logger.error(f"exporting {name} failed, {qty._fmt}")
67
- e.add_note(f"exporting {name} failed")
68
- raise e
59
+ logger.info(f"add {name}={new}")
69
60
 
70
- for name, qty in active.items():
71
- logger.info(f"remove {name}: {qty}")
61
+ for name, old in current.items():
62
+ logger.info(f"remove {name}: {old}")
72
63
  outfile.write("}\n\\ExplSyntaxOff\n")
73
64
 
74
65
  def __call__(
@@ -80,6 +71,7 @@ class Qty(PTManager):
80
71
  base: bool = False,
81
72
  compact: bool = False,
82
73
  preferred: bool = False,
74
+ alt: AltDict | bool = False,
83
75
  ) -> SiunitxQuantity:
84
76
  """
85
77
  Return an instance of SiunitxQuantity
@@ -94,11 +86,13 @@ class Qty(PTManager):
94
86
  :param to: Immediately converts unit.
95
87
  :param fmt: Sets the "f-string" format of the magnitude.
96
88
  :param base: If true, converts the unit into SI units.
97
- :param compact: As described in the "Pint" documentation "returns a
89
+ :param compact: As described in the `Pint` documentation "returns a
98
90
  rescaled, human-readable unit".
99
91
  :param preferred: If true, attempts to convert unit into simpler one.
100
92
  For instance (kg.m/s2 will be converted to N). Note that, this defaults
101
93
  to "False" only here.
94
+ :param alt: If provided, writes an additional alternative output with
95
+ different formatting. The string '_alt' is added to the base name.
102
96
  """
103
97
  if isinstance(value, str):
104
98
  if unit is not None:
@@ -106,6 +100,12 @@ class Qty(PTManager):
106
100
  value, unit, fmt = self._constants(value, fmt)
107
101
 
108
102
  q = self._ureg.Quantity(
109
- value, unit, fmt=fmt, base=base, compact=compact, preferred=preferred
103
+ value,
104
+ unit,
105
+ fmt=fmt,
106
+ base=base,
107
+ compact=compact,
108
+ preferred=preferred,
109
+ alt=alt,
110
110
  )
111
111
  return q if to is None else q.fmtex(to=to)
@@ -4,18 +4,28 @@ from numpy import cross, ndarray, linalg
4
4
  from pathlib import Path
5
5
  from pint._typing import Magnitude, UnitLike
6
6
  from pint.registry import GenericUnitRegistry, Quantity, Unit
7
- from typing import cast
7
+ from typing import Generator, TypedDict, cast
8
8
 
9
9
  from physpyx.nuclide import NuclideTable
10
10
 
11
11
  logger = getLogger(__name__)
12
12
 
13
13
 
14
+ class AltDict(TypedDict, total=False):
15
+ fmt: str
16
+ base: bool
17
+ preferred: bool
18
+ compact: bool
19
+ to: str
20
+ alt: "AltDict"
21
+
22
+
14
23
  class SiunitxQuantity(Quantity):
15
24
  _fmt: str
16
25
  _base: bool
17
26
  _compact: bool
18
27
  _preferred: bool
28
+ _alt: AltDict
19
29
 
20
30
  _sinunitx_map = {
21
31
  "astronomical_unit": "astronomicalunit",
@@ -50,6 +60,7 @@ class SiunitxQuantity(Quantity):
50
60
  "tesla",
51
61
  "henry",
52
62
  "farad",
63
+ "coulomb",
53
64
  ]
54
65
  }
55
66
 
@@ -61,6 +72,7 @@ class SiunitxQuantity(Quantity):
61
72
  base: bool = False,
62
73
  compact: bool = False,
63
74
  preferred: bool = True,
75
+ alt: AltDict | bool = False,
64
76
  ) -> "SiunitxQuantity":
65
77
  """
66
78
  Return an instance of SiunitxQuantity
@@ -69,30 +81,41 @@ class SiunitxQuantity(Quantity):
69
81
  :param unit: Assigns the unit if any.
70
82
  :param fmt: Sets the amplitude "f-string" format.
71
83
  :param base: If true, converts the unit into SI units.
72
- :param compact: As described in the "Pint" documentation "returns a
84
+ :param compact: As described in the `Pint` documentation "returns a
73
85
  rescaled to compact, human-readable units".
74
- :param preferred: If true, attempts to convert unit into simpler one.
75
- For instance (kg.m/s2 will be converted to N). Note that, this defaults
76
- to "True" which means that all SiunitxQuantity resulting from
86
+ :param preferred: If true, attempts to convert units into preferred
87
+ ones. For instance (kg.m/s2 will be converted to N). Note that, this
88
+ defaults to "True" which means that all SiunitxQuantity resulting from
77
89
  mathematical operations, will be converted.
90
+ :param alt: If provided, writes an additional alternative output with
91
+ different formatting. The string '_alt' is added to the base name.
78
92
  """
79
93
  quantity = cast(SiunitxQuantity, super().__new__(cls, value, units))
80
94
  quantity._fmt = fmt
81
95
  quantity._base = base
82
96
  quantity._compact = compact
83
97
  quantity._preferred = preferred
98
+ quantity._alt = cls._cast_alt_dict(alt)
84
99
  return quantity
85
100
 
101
+ @classmethod
102
+ def _cast_alt_dict(cls, alt: AltDict | bool) -> AltDict:
103
+ if isinstance(alt, bool):
104
+ return {"preferred": True} if alt else AltDict()
105
+ else:
106
+ return alt
107
+
86
108
  def __str__(self) -> str:
87
109
  quantity = self
88
110
  if self._base:
89
111
  quantity = cast(SiunitxQuantity, self.to_base_units())
90
112
  elif self._preferred:
91
- if sum(1 for _ in self.unit_items()) != 1:
92
- if to := self._preferred_units.get(self.dimensionality, None):
93
- quantity = cast(SiunitxQuantity, self.to(to))
94
- else:
95
- quantity = cast(SiunitxQuantity, self.to_reduced_units())
113
+ quantity = cast(
114
+ SiunitxQuantity,
115
+ self.to(to)
116
+ if (to := self._preferred_units.get(self.dimensionality, None))
117
+ else self.to_reduced_units(),
118
+ )
96
119
  if self._compact:
97
120
  quantity = cast(SiunitxQuantity, self.to_compact())
98
121
 
@@ -143,8 +166,8 @@ class SiunitxQuantity(Quantity):
143
166
  return rf"\num{{{out}}}"
144
167
 
145
168
  def __neg__(self) -> "SiunitxQuantity":
146
- # NOTE: only useful to fix typing warning Cannot access attribute "???"
147
- # for class "PlainQuantity[Unknown]"
169
+ # NOTE: to fix typing warning 'Cannot access attribute "???" for class
170
+ # "PlainQuantity[Unknown]"'
148
171
  return cast(SiunitxQuantity, super().__neg__())
149
172
 
150
173
  def _format_unit(self) -> str:
@@ -171,12 +194,33 @@ class SiunitxQuantity(Quantity):
171
194
  tmp += r"\cubed"
172
195
  else:
173
196
  tmp += rf"\tothe{{{p:f}}}"
174
- if power >= 0:
197
+ if power >= 0.0:
175
198
  num += tmp
176
199
  else:
177
200
  den += r"\per" + tmp
178
201
  return num + den
179
202
 
203
+ def export(self, name: str) -> Generator[tuple[str, str], None, None]:
204
+ try:
205
+ yield (
206
+ name,
207
+ r"\qty[input-digits = 0123456789\pi]{4\pi e-7}{\tesla\meter\per\ampere}"
208
+ if name == "mu0"
209
+ else str(self),
210
+ )
211
+ except ValueError as e:
212
+ # TODO: report the correct error in
213
+ # PTManager.write_errorfile, not the one raised in
214
+ # PTManager.__exit__
215
+ logger.error(f"exporting {name} failed, {self._fmt}")
216
+ e.add_note(f"exporting {name} failed")
217
+ raise e
218
+
219
+ if self._alt:
220
+ alt = self.fmtex(**self._alt)
221
+ alt._alt = AltDict()
222
+ yield from alt.export(name + "_alt")
223
+
180
224
  def fmtex(
181
225
  self,
182
226
  *,
@@ -185,15 +229,24 @@ class SiunitxQuantity(Quantity):
185
229
  base: bool | None = None,
186
230
  compact: bool | None = None,
187
231
  preferred: bool | None = None,
232
+ alt: AltDict | bool = False,
188
233
  ) -> "SiunitxQuantity":
189
234
  fmt = self._fmt if fmt is None else fmt
190
235
  compact = self._compact if compact is None else compact
191
236
  base = self._base if base is None else base
192
237
  preferred = self._preferred if preferred is None else preferred
238
+ alt = self._cast_alt_dict(alt)
239
+
193
240
  if to == "":
194
241
  m = self.m if self.units != "percent" else self.m / 100
195
242
  return self.__class__(
196
- m, "", fmt=fmt, base=False, compact=compact, preferred=preferred
243
+ m,
244
+ "",
245
+ fmt=fmt,
246
+ base=False,
247
+ compact=compact,
248
+ preferred=preferred,
249
+ alt=alt,
197
250
  )
198
251
 
199
252
  if to:
@@ -202,10 +255,17 @@ class SiunitxQuantity(Quantity):
202
255
  q._base = False
203
256
  q._compact = compact
204
257
  q._preferred = False
258
+ q._alt = alt
205
259
  return q
206
260
 
207
261
  return self.__class__(
208
- self.m, self.units, fmt=fmt, base=base, compact=compact, preferred=preferred
262
+ self.m,
263
+ self.units,
264
+ fmt=fmt,
265
+ base=base,
266
+ compact=compact,
267
+ preferred=preferred,
268
+ alt=alt,
209
269
  )
210
270
 
211
271
  def norm(self) -> "SiunitxQuantity":
@@ -241,7 +301,7 @@ class Constants:
241
301
  "k": (8.987551e9, "newton meter squared per coulomb squared", ".0e"),
242
302
  "epsilon0": (
243
303
  8.8541878188e-12,
244
- "coulomb squared per newton meter sauqred",
304
+ "coulomb squared per newton meter squared",
245
305
  ".3e",
246
306
  ),
247
307
  "mu0": (1.2566370612696005e-06, "tesla meter per ampere", ".3e"),
File without changes
File without changes
File without changes
File without changes
File without changes