physpyx 3.0.0__tar.gz → 3.2.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-3.0.0 → physpyx-3.2.0}/PKG-INFO +1 -1
- {physpyx-3.0.0 → physpyx-3.2.0}/pyproject.toml +1 -1
- {physpyx-3.0.0 → physpyx-3.2.0}/pyproject.toml.orig +1 -1
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/astronomy.py +6 -5
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/qty.py +20 -1
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/quantity.py +33 -18
- physpyx-3.2.0/src/physpyx/tikz.py +196 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/README.md +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/__init__.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/constants.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/context.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/data/all-nuclides.csv +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/helper.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/manager.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/nuclide.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/physpyx.py +0 -0
- {physpyx-3.0.0 → physpyx-3.2.0}/src/physpyx/registry.py +0 -0
|
@@ -40,6 +40,11 @@ class Orbit:
|
|
|
40
40
|
else:
|
|
41
41
|
raise ValueError
|
|
42
42
|
|
|
43
|
+
if self.a < self.b:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"The semi-major axis should be bigger or equal to the semi-minor axis (a>=b)"
|
|
46
|
+
)
|
|
47
|
+
|
|
43
48
|
self.c = self.a * self.e # linear eccentricity
|
|
44
49
|
self.p = self.a * (1.0 - self.e * self.e) # semi-latus
|
|
45
50
|
self.T = 2 * np.pi * self.a * np.sqrt(self.a / self.mu) # period
|
|
@@ -69,11 +74,7 @@ class Orbit:
|
|
|
69
74
|
r = self.p / (1 + self.e * np.cos(self.theta))
|
|
70
75
|
self.x = r * np.array([np.cos(self.theta), np.sin(self.theta)]) + self.v * dt
|
|
71
76
|
|
|
72
|
-
def run(
|
|
73
|
-
self,
|
|
74
|
-
steps: int,
|
|
75
|
-
every: int = 1,
|
|
76
|
-
) -> Iterator[NDArray]:
|
|
77
|
+
def run(self, steps: int, every: int = 1) -> Iterator[NDArray]:
|
|
77
78
|
if steps % every:
|
|
78
79
|
raise ValueError(f"{every} is not a divisor {steps} steps")
|
|
79
80
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import sys
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from logging import getLogger
|
|
5
|
+
from typing import cast
|
|
4
6
|
|
|
7
|
+
from numpy import array
|
|
5
8
|
from pint._typing import Scalar
|
|
6
9
|
from pint.facets.context import Context
|
|
7
10
|
|
|
@@ -56,7 +59,7 @@ class Qty(PTManager):
|
|
|
56
59
|
|
|
57
60
|
def __call__(
|
|
58
61
|
self,
|
|
59
|
-
value: Scalar |
|
|
62
|
+
value: Scalar | Iterable[Scalar | SiunitxQuantity] | str,
|
|
60
63
|
unit: str | None = None,
|
|
61
64
|
to: str | None = None,
|
|
62
65
|
fmt: str = "g",
|
|
@@ -90,6 +93,22 @@ class Qty(PTManager):
|
|
|
90
93
|
if unit is not None:
|
|
91
94
|
logger.warning("Units are ignored when instanciating a constant")
|
|
92
95
|
value, unit, fmt = self._constants(value, fmt)
|
|
96
|
+
elif isinstance(value, Iterable):
|
|
97
|
+
if all(isinstance(item, SiunitxQuantity) for item in value):
|
|
98
|
+
value = cast(Iterable[SiunitxQuantity], value)
|
|
99
|
+
dim = {v.dimensionality for v in value}
|
|
100
|
+
if len(dim) != 1:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Can't create a vector with different dimensionality ({dim})"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if unit == None:
|
|
106
|
+
unit = ""
|
|
107
|
+
if self._ureg.Unit(unit).dimensionality != dim.pop():
|
|
108
|
+
raise ValueError("Need to set unit with correct dimensionality")
|
|
109
|
+
value = array([v.fmtex(to=unit).m for v in value])
|
|
110
|
+
else:
|
|
111
|
+
value = array(value)
|
|
93
112
|
|
|
94
113
|
q = self._ureg.Quantity(
|
|
95
114
|
value,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from collections.abc import Generator, Iterator
|
|
1
|
+
from collections.abc import Generator, Iterable, Iterator
|
|
2
2
|
from logging import getLogger
|
|
3
3
|
from typing import ClassVar, Self, TypedDict, cast
|
|
4
4
|
|
|
5
|
-
from numpy import cross, linalg
|
|
5
|
+
from numpy import cross, linalg
|
|
6
6
|
from pint._typing import Magnitude, UnitLike
|
|
7
7
|
from pint.registry import Quantity
|
|
8
8
|
|
|
@@ -82,8 +82,8 @@ class SiunitxQuantity(Quantity):
|
|
|
82
82
|
return alt
|
|
83
83
|
|
|
84
84
|
def __iter__(self) -> Iterator["SiunitxQuantity"]:
|
|
85
|
-
# NOTE: this is used to set formatting options in tuple expansions
|
|
86
|
-
#
|
|
85
|
+
# NOTE: this is used to set formatting options in tuple expansions rx,
|
|
86
|
+
# ry = qty([3, 2], "kilometer", fmt=".1e")
|
|
87
87
|
for val in super().__iter__():
|
|
88
88
|
val._fmt = self._fmt
|
|
89
89
|
val._base = self._base
|
|
@@ -99,33 +99,35 @@ class SiunitxQuantity(Quantity):
|
|
|
99
99
|
elif self._preferred:
|
|
100
100
|
quantity = self._REGISTRY.to_preferred_units(self)
|
|
101
101
|
if self._compact:
|
|
102
|
+
if isinstance(self.m, Iterable):
|
|
103
|
+
raise ValueError("'compact=True' for vectors has an undefined behavior")
|
|
102
104
|
quantity = cast(SiunitxQuantity, self.to_compact())
|
|
103
105
|
|
|
104
106
|
quantity._fmt = self._fmt
|
|
105
107
|
return (
|
|
106
108
|
quantity._format_array()
|
|
107
|
-
if isinstance(quantity.m,
|
|
109
|
+
if isinstance(quantity.m, Iterable)
|
|
108
110
|
else quantity._format_scalar()
|
|
109
111
|
)
|
|
110
112
|
|
|
113
|
+
def _format_magnitude(self, m: float) -> str:
|
|
114
|
+
out = f"{m:{self._fmt}}"
|
|
115
|
+
if out.startswith("1e"):
|
|
116
|
+
return out[1:]
|
|
117
|
+
elif out.startswith("-1e"):
|
|
118
|
+
return "-" + out[2:]
|
|
119
|
+
return out
|
|
120
|
+
|
|
111
121
|
def _format_array(self) -> str:
|
|
112
122
|
if self.m.ndim != 1:
|
|
113
123
|
raise NotImplementedError
|
|
114
124
|
out = r"\begin{pmatrix}"
|
|
115
125
|
if isinstance(self.m[0], complex):
|
|
116
126
|
out += "\\\\".join(
|
|
117
|
-
|
|
118
|
-
rf"\complexnum{{{v:{self._fmt}}}}" if abs(v) > 1e-16 else r"\num{0}"
|
|
119
|
-
for v in self.m
|
|
120
|
-
]
|
|
127
|
+
rf"\complexnum{{{self._format_magnitude(m)}}}" for m in self.m
|
|
121
128
|
)
|
|
122
129
|
else:
|
|
123
|
-
out += "\\\\".join(
|
|
124
|
-
[
|
|
125
|
-
rf"\num{{{v:{self._fmt}}}}" if abs(v) > 1e-16 else r"\num{0}"
|
|
126
|
-
for v in self.m
|
|
127
|
-
]
|
|
128
|
-
)
|
|
130
|
+
out += "\\\\".join(rf"\num{{{self._format_magnitude(m)}}}" for m in self.m)
|
|
129
131
|
|
|
130
132
|
out += r"\end{pmatrix}"
|
|
131
133
|
if ustr := self._format_unit():
|
|
@@ -133,9 +135,7 @@ class SiunitxQuantity(Quantity):
|
|
|
133
135
|
return out
|
|
134
136
|
|
|
135
137
|
def _format_scalar(self) -> str:
|
|
136
|
-
out =
|
|
137
|
-
if out.startswith("1e"):
|
|
138
|
-
out = out[1:]
|
|
138
|
+
out = self._format_magnitude(self.m)
|
|
139
139
|
if ustr := self._format_unit():
|
|
140
140
|
if isinstance(self.m, complex):
|
|
141
141
|
return rf"\complexqty{{{out}}}{{{ustr}}}"
|
|
@@ -157,6 +157,21 @@ class SiunitxQuantity(Quantity):
|
|
|
157
157
|
# "PlainQuantity[Unknown]"'
|
|
158
158
|
return cast(SiunitxQuantity, super().__add__(other))
|
|
159
159
|
|
|
160
|
+
def __truediv__(self, other: object) -> "SiunitxQuantity":
|
|
161
|
+
# NOTE: to fix typing warning 'Cannot access attribute "???" for class
|
|
162
|
+
# "PlainQuantity[Unknown]"'
|
|
163
|
+
return cast(SiunitxQuantity, super().__truediv__(other))
|
|
164
|
+
|
|
165
|
+
def __mul__(self, other: object) -> "SiunitxQuantity":
|
|
166
|
+
# NOTE: to fix typing warning 'Cannot access attribute "???" for class
|
|
167
|
+
# "PlainQuantity[Unknown]"'
|
|
168
|
+
return cast(SiunitxQuantity, super().__mul__(other))
|
|
169
|
+
|
|
170
|
+
def __rmul__(self, other: object) -> "SiunitxQuantity":
|
|
171
|
+
# NOTE: to fix typing warning 'Cannot access attribute "???" for class
|
|
172
|
+
# "PlainQuantity[Unknown]"'
|
|
173
|
+
return cast(SiunitxQuantity, super().__rmul__(other))
|
|
174
|
+
|
|
160
175
|
def _format_unit(self) -> str:
|
|
161
176
|
num = ""
|
|
162
177
|
den = ""
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from typing import Self
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Coordinate:
|
|
9
|
+
def __init__(self, x: float, y: float):
|
|
10
|
+
self.x = x
|
|
11
|
+
self.y = y
|
|
12
|
+
|
|
13
|
+
def norm(self) -> float:
|
|
14
|
+
return np.sqrt(self.x * self.x + self.y * self.y)
|
|
15
|
+
|
|
16
|
+
def orientation(self) -> float:
|
|
17
|
+
return np.atan2(self.y, self.x) * 180 / np.pi
|
|
18
|
+
|
|
19
|
+
def __isub__(self, other: "Coordinate") -> Self:
|
|
20
|
+
self.x -= other.x
|
|
21
|
+
self.y -= other.y
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def __sub__(self, other: "Coordinate") -> "Coordinate":
|
|
25
|
+
return Coordinate(self.x - other.x, self.y - other.y)
|
|
26
|
+
|
|
27
|
+
def __iadd__(self, other: "Coordinate") -> Self:
|
|
28
|
+
self.x += other.x
|
|
29
|
+
self.y += other.y
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def __mul__(self, number: float) -> "Coordinate":
|
|
33
|
+
return Coordinate(self.x * number, self.y * number)
|
|
34
|
+
|
|
35
|
+
__rmul__ = __mul__
|
|
36
|
+
|
|
37
|
+
def __truediv__(self, number: float) -> "Coordinate":
|
|
38
|
+
return Coordinate(self.x / number, self.y / number)
|
|
39
|
+
|
|
40
|
+
def __add__(self, other: "Coordinate") -> "Coordinate":
|
|
41
|
+
return Coordinate(self.x + other.x, self.y + other.y)
|
|
42
|
+
|
|
43
|
+
def dot(self, other: "Coordinate") -> float:
|
|
44
|
+
return self.x * other.x + self.y * other.y
|
|
45
|
+
|
|
46
|
+
def __str__(self) -> str:
|
|
47
|
+
return f"({self.x}, {self.y})"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Rectangle:
|
|
51
|
+
def __init__(self, c1: Coordinate, c2: Coordinate):
|
|
52
|
+
self.c1 = c1
|
|
53
|
+
self.c2 = c2
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return f"{self.c1} rectangle {self.c2}"
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def center(self) -> Coordinate:
|
|
60
|
+
return 0.5 * self.c1 + 0.5 * self.c2
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def north(self) -> Coordinate:
|
|
64
|
+
return Coordinate(0.5 * self.c1.x + 0.5 * self.c2.x, max(self.c1.y, self.c2.y))
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def south(self) -> Coordinate:
|
|
68
|
+
return Coordinate(0.5 * self.c1.x + 0.5 * self.c2.x, min(self.c1.y, self.c2.y))
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def east(self) -> Coordinate:
|
|
72
|
+
return Coordinate(max(self.c1.x, self.c2.x), 0.5 * self.c1.y + 0.5 * self.c2.y)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def west(self) -> Coordinate:
|
|
76
|
+
return Coordinate(min(self.c1.x, self.c2.x), 0.5 * self.c1.y + 0.5 * self.c2.y)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def north_east(self) -> Coordinate:
|
|
80
|
+
return Coordinate(max(self.c1.x, self.c2.x), max(self.c1.y, self.c2.y))
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def north_west(self) -> Coordinate:
|
|
84
|
+
return Coordinate(min(self.c1.x, self.c2.x), max(self.c1.y, self.c2.y))
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def south_west(self) -> Coordinate:
|
|
88
|
+
return Coordinate(min(self.c1.x, self.c2.x), min(self.c1.y, self.c2.y))
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def south_east(self) -> Coordinate:
|
|
92
|
+
return Coordinate(max(self.c1.x, self.c2.x), min(self.c1.y, self.c2.y))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Circle:
|
|
96
|
+
class LR(IntEnum):
|
|
97
|
+
LEFT = -1
|
|
98
|
+
RIGHT = 1
|
|
99
|
+
|
|
100
|
+
class IO(IntEnum):
|
|
101
|
+
OUTER = -1
|
|
102
|
+
INNER = 1
|
|
103
|
+
|
|
104
|
+
class ORIENT(IntEnum):
|
|
105
|
+
DEFAULT = 0
|
|
106
|
+
CLOCKWISE = -1
|
|
107
|
+
COUNTER_CLOCKWISE = 1
|
|
108
|
+
|
|
109
|
+
def __init__(self, center: Coordinate, radius: float):
|
|
110
|
+
self.center = center
|
|
111
|
+
self.radius = radius
|
|
112
|
+
|
|
113
|
+
def __str__(self) -> str:
|
|
114
|
+
return f"{self.center} circle ({self.radius})"
|
|
115
|
+
|
|
116
|
+
def tangent_points_to_circle(
|
|
117
|
+
self, circ: "Circle", lr: LR, io: IO
|
|
118
|
+
) -> tuple[Coordinate, Coordinate]:
|
|
119
|
+
D = circ.center - self.center
|
|
120
|
+
d = D.norm()
|
|
121
|
+
R = (circ.radius + io * self.radius) / d
|
|
122
|
+
e1 = D / d
|
|
123
|
+
e2 = Coordinate(-e1.y, e1.x)
|
|
124
|
+
|
|
125
|
+
ab = R * e1 + lr * np.sqrt(1 - R * R) * e2
|
|
126
|
+
c = -io * self.radius - ab.dot(self.center)
|
|
127
|
+
return (
|
|
128
|
+
self.center - ab * (ab.dot(self.center) + c),
|
|
129
|
+
circ.center - ab * (ab.dot(circ.center) + c),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def tangent_points_passing_by(self, P: Coordinate, lr: LR) -> Coordinate:
|
|
133
|
+
D = P - self.center
|
|
134
|
+
d = D.norm()
|
|
135
|
+
e1 = D / d
|
|
136
|
+
e2 = Coordinate(-e1.y, e1.x)
|
|
137
|
+
a = self.radius * self.radius / d * e1
|
|
138
|
+
b = self.radius / d * np.sqrt(d * d - self.radius * self.radius) * e2
|
|
139
|
+
return self.center + a + lr * b
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Pulley(Circle):
|
|
143
|
+
def to_tex(self) -> Generator[str]:
|
|
144
|
+
yield rf"\fill[black!30] {self};"
|
|
145
|
+
yield rf"\draw[black!50, line width={2 * self.radius}] {Circle(self.center, 0.85 * self.radius)};"
|
|
146
|
+
yield rf"\fill[black!70] {Circle(self.center, 0.25 * self.radius)};"
|
|
147
|
+
shift = np.random.random() * np.pi / 3
|
|
148
|
+
for i in range(6):
|
|
149
|
+
c = self.center + Coordinate(
|
|
150
|
+
0.6 * self.radius * np.sin(i * np.pi / 3 + shift),
|
|
151
|
+
0.6 * self.radius * np.cos(i * np.pi / 3 + shift),
|
|
152
|
+
)
|
|
153
|
+
yield rf"\fill[black!60]{Circle(c, 0.1 * self.radius)};"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class Rope:
|
|
157
|
+
def __init__(self, start: Coordinate):
|
|
158
|
+
self.current = start
|
|
159
|
+
self._path = f"{start}"
|
|
160
|
+
|
|
161
|
+
def __str__(self) -> str:
|
|
162
|
+
return self._path
|
|
163
|
+
|
|
164
|
+
def to_pulley(self, pulley: Pulley, lr: Circle.LR) -> None:
|
|
165
|
+
self.current = pulley.tangent_points_passing_by(self.current, lr)
|
|
166
|
+
self._path += f" -- {self.current}"
|
|
167
|
+
|
|
168
|
+
def _compute_angles(
|
|
169
|
+
self, a: Coordinate, b: Coordinate, o: Circle.ORIENT
|
|
170
|
+
) -> tuple[float, float]:
|
|
171
|
+
return (self.current - a).orientation(), (b - a).orientation() + o * 360
|
|
172
|
+
|
|
173
|
+
def from_pulley(
|
|
174
|
+
self,
|
|
175
|
+
pulley: Pulley,
|
|
176
|
+
to: Coordinate,
|
|
177
|
+
lr: Circle.LR,
|
|
178
|
+
orientation: Circle.ORIENT = Circle.ORIENT.DEFAULT,
|
|
179
|
+
) -> None:
|
|
180
|
+
tmp = pulley.tangent_points_passing_by(to, lr)
|
|
181
|
+
angle_i, angle_o = self._compute_angles(pulley.center, tmp, orientation)
|
|
182
|
+
self._path += f" arc ({angle_i}:{angle_o}:{pulley.radius}) -- {to}"
|
|
183
|
+
self.current = to
|
|
184
|
+
|
|
185
|
+
def from_pulley_to_pulley(
|
|
186
|
+
self,
|
|
187
|
+
P1: Pulley,
|
|
188
|
+
P2: Pulley,
|
|
189
|
+
lr: Circle.LR,
|
|
190
|
+
io: Circle.IO,
|
|
191
|
+
orientation: Circle.ORIENT = Circle.ORIENT.DEFAULT,
|
|
192
|
+
) -> None:
|
|
193
|
+
t1, t2 = P1.tangent_points_to_circle(P2, lr, io)
|
|
194
|
+
angle_i, angle_o = self._compute_angles(P1.center, t1, orientation)
|
|
195
|
+
self._path += f" arc ({angle_i}:{angle_o}:{P1.radius}) -- {t2}"
|
|
196
|
+
self.current = t2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|