physpyx 3.1.0__tar.gz → 3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: physpyx
3
- Version: 3.1.0
3
+ Version: 3.3.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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "physpyx"
3
- version = "3.1.0"
3
+ version = "3.3.0"
4
4
  description = "Provides a way to use python code in LaTeX"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "physpyx"
3
- version = "3.1.0"
3
+ version = "3.3.0"
4
4
  description = "Provides a way to use python code in LaTeX"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -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,16 +74,12 @@ 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
 
80
81
  dt = self.T / steps
81
- for i in range(steps + 1):
82
+ for i in range(steps):
82
83
  if i % every == 0:
83
84
  yield self.x
84
85
  self.step(dt)
@@ -107,7 +108,7 @@ class Orbit:
107
108
  vec_t = self.v / vt
108
109
  vec_p = np.array([-vec_t[1], vec_t[0]])
109
110
 
110
- # compute cetripetal acceleration
111
+ # compute centripetal acceleration
111
112
  a = self.get_acceleration()
112
113
  ac = a.dot(vec_p)
113
114
 
@@ -0,0 +1,243 @@
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 __str__(self) -> str:
17
+ return f"({self.x}, {self.y})"
18
+
19
+ def __eq__(self, other: object) -> bool:
20
+ if not isinstance(other, Coordinate):
21
+ return False
22
+ return bool(np.isclose(self.x, other.x) and np.isclose(self.y, other.y))
23
+
24
+ def __isub__(self, other: "Coordinate") -> Self:
25
+ self.x -= other.x
26
+ self.y -= other.y
27
+ return self
28
+
29
+ def __sub__(self, other: "Coordinate") -> "Coordinate":
30
+ return Coordinate(self.x - other.x, self.y - other.y)
31
+
32
+ def __iadd__(self, other: "Coordinate") -> Self:
33
+ self.x += other.x
34
+ self.y += other.y
35
+ return self
36
+
37
+ def __add__(self, other: "Coordinate") -> "Coordinate":
38
+ return Coordinate(self.x + other.x, self.y + other.y)
39
+
40
+ def __mul__(self, number: float) -> "Coordinate":
41
+ return Coordinate(self.x * number, self.y * number)
42
+
43
+ __rmul__ = __mul__
44
+
45
+ def __truediv__(self, number: float) -> "Coordinate":
46
+ return Coordinate(self.x / number, self.y / number)
47
+
48
+ def dot(self, other: "Coordinate") -> float:
49
+ return self.x * other.x + self.y * other.y
50
+
51
+ def orientation(self) -> float:
52
+ a = np.atan2(self.y, self.x) * 180 / np.pi
53
+ return a + 360 if a < 0 else a
54
+
55
+ def rotate(self, angle: float, deg: bool = True) -> "Coordinate":
56
+ if deg:
57
+ angle *= np.pi / 180
58
+ return Coordinate(
59
+ self.x * np.cos(angle) + self.y * -np.sin(angle),
60
+ self.x * np.sin(angle) + self.y * np.cos(angle),
61
+ )
62
+
63
+
64
+ class Rectangle:
65
+ def __init__(self, c1: Coordinate, c2: Coordinate):
66
+ self.c1 = c1
67
+ self.c2 = c2
68
+
69
+ def __str__(self) -> str:
70
+ return f"{self.c1} rectangle {self.c2}"
71
+
72
+ @property
73
+ def center(self) -> Coordinate:
74
+ return 0.5 * self.c1 + 0.5 * self.c2
75
+
76
+ @property
77
+ def north(self) -> Coordinate:
78
+ return Coordinate(0.5 * self.c1.x + 0.5 * self.c2.x, max(self.c1.y, self.c2.y))
79
+
80
+ @property
81
+ def south(self) -> Coordinate:
82
+ return Coordinate(0.5 * self.c1.x + 0.5 * self.c2.x, min(self.c1.y, self.c2.y))
83
+
84
+ @property
85
+ def east(self) -> Coordinate:
86
+ return Coordinate(max(self.c1.x, self.c2.x), 0.5 * self.c1.y + 0.5 * self.c2.y)
87
+
88
+ @property
89
+ def west(self) -> Coordinate:
90
+ return Coordinate(min(self.c1.x, self.c2.x), 0.5 * self.c1.y + 0.5 * self.c2.y)
91
+
92
+ @property
93
+ def north_east(self) -> Coordinate:
94
+ return Coordinate(max(self.c1.x, self.c2.x), max(self.c1.y, self.c2.y))
95
+
96
+ @property
97
+ def north_west(self) -> Coordinate:
98
+ return Coordinate(min(self.c1.x, self.c2.x), max(self.c1.y, self.c2.y))
99
+
100
+ @property
101
+ def south_west(self) -> Coordinate:
102
+ return Coordinate(min(self.c1.x, self.c2.x), min(self.c1.y, self.c2.y))
103
+
104
+ @property
105
+ def south_east(self) -> Coordinate:
106
+ return Coordinate(max(self.c1.x, self.c2.x), min(self.c1.y, self.c2.y))
107
+
108
+
109
+ class Circle:
110
+ class LR(IntEnum):
111
+ LEFT = -1
112
+ RIGHT = 1
113
+
114
+ class IO(IntEnum):
115
+ OUTER = -1
116
+ INNER = 1
117
+
118
+ class ORIENT(IntEnum):
119
+ DEFAULT = 0
120
+ CLOCKWISE = -1
121
+ COUNTER_CLOCKWISE = 1
122
+
123
+ def __init__(self, center: Coordinate, radius: float):
124
+ self.center = center
125
+ self.radius = radius
126
+
127
+ def __str__(self) -> str:
128
+ return f"{self.center} circle ({self.radius})"
129
+
130
+ def get_point_on_circle(self, angle: float) -> Coordinate:
131
+ angle *= np.pi / 180
132
+ return self.center + Coordinate(
133
+ self.radius * np.cos(angle),
134
+ self.radius * np.sin(angle),
135
+ )
136
+
137
+ def tangent_points_to_circle(
138
+ self, circ: "Circle", lr: LR, io: IO
139
+ ) -> tuple[Coordinate, Coordinate]:
140
+ D = circ.center - self.center
141
+ d = D.norm()
142
+ R = (circ.radius + io * self.radius) / d
143
+ e1 = D / d
144
+ e2 = Coordinate(-e1.y, e1.x)
145
+
146
+ ab = R * e1 + lr * np.sqrt(1 - R * R) * e2
147
+ c = -io * self.radius - ab.dot(self.center)
148
+ return (
149
+ self.center - ab * (ab.dot(self.center) + c),
150
+ circ.center - ab * (ab.dot(circ.center) + c),
151
+ )
152
+
153
+ def tangent_points_passing_by(self, P: Coordinate, lr: LR) -> Coordinate:
154
+ D = P - self.center
155
+ d = D.norm()
156
+ e1 = D / d
157
+ e2 = Coordinate(-e1.y, e1.x)
158
+ a = self.radius * self.radius / d * e1
159
+ b = self.radius / d * np.sqrt(d * d - self.radius * self.radius) * e2
160
+ return self.center + a + lr * b
161
+
162
+
163
+ class Pulley(Circle):
164
+ def to_tex(self) -> Generator[str]:
165
+ yield rf"\fill[black!30] {self};"
166
+ yield rf"\draw[black!50, line width={2 * self.radius}] {Circle(self.center, 0.85 * self.radius)};"
167
+ yield rf"\fill[black!70] {Circle(self.center, 0.25 * self.radius)};"
168
+ shift = np.random.random() * np.pi / 3
169
+ for i in range(6):
170
+ c = self.center + Coordinate(
171
+ 0.6 * self.radius * np.sin(i * np.pi / 3 + shift),
172
+ 0.6 * self.radius * np.cos(i * np.pi / 3 + shift),
173
+ )
174
+ yield rf"\fill[black!60]{Circle(c, 0.1 * self.radius)};"
175
+
176
+
177
+ class Rope:
178
+ def __init__(self, start: Coordinate):
179
+ self.current = start
180
+ self._path = f"{start}"
181
+
182
+ def __str__(self) -> str:
183
+ return self._path
184
+
185
+ def to_pulley(self, pulley: Pulley, lr: Circle.LR) -> None:
186
+ self.current = pulley.tangent_points_passing_by(self.current, lr)
187
+ self._path += f" -- {self.current}"
188
+
189
+ def _compute_angles(
190
+ self, a: Coordinate, b: Coordinate, o: Circle.ORIENT
191
+ ) -> tuple[float, float]:
192
+ return (self.current - a).orientation(), (b - a).orientation() + o * 360
193
+
194
+ def from_pulley(
195
+ self,
196
+ pulley: Pulley,
197
+ to: Coordinate,
198
+ lr: Circle.LR,
199
+ orientation: Circle.ORIENT = Circle.ORIENT.DEFAULT,
200
+ ) -> None:
201
+ tmp = pulley.tangent_points_passing_by(to, lr)
202
+ angle_i, angle_o = self._compute_angles(pulley.center, tmp, orientation)
203
+ self._path += f" arc ({angle_i}:{angle_o}:{pulley.radius}) -- {to}"
204
+ self.current = to
205
+
206
+ def from_pulley_to_pulley(
207
+ self,
208
+ P1: Pulley,
209
+ P2: Pulley,
210
+ lr: Circle.LR,
211
+ io: Circle.IO,
212
+ orientation: Circle.ORIENT = Circle.ORIENT.DEFAULT,
213
+ ) -> None:
214
+ t1, t2 = P1.tangent_points_to_circle(P2, lr, io)
215
+ angle_i, angle_o = self._compute_angles(P1.center, t1, orientation)
216
+ self._path += f" arc ({angle_i}:{angle_o}:{P1.radius}) -- {t2}"
217
+ self.current = t2
218
+
219
+
220
+ class Angle:
221
+ def __init__(
222
+ self,
223
+ origin: Coordinate,
224
+ p1: Coordinate,
225
+ p2: Coordinate,
226
+ orient: Circle.ORIENT = Circle.ORIENT.COUNTER_CLOCKWISE,
227
+ ):
228
+ self.origin = origin
229
+ self.p1 = p1
230
+ self.p2 = p2
231
+ self.alpha1 = (self.p1 - self.origin).orientation()
232
+ self.alpha2 = (self.p2 - self.origin).orientation()
233
+ if orient == Circle.ORIENT.CLOCKWISE and self.alpha2 > self.alpha1:
234
+ self.alpha2 -= 360
235
+ elif orient == Circle.ORIENT.COUNTER_CLOCKWISE and self.alpha1 > self.alpha2:
236
+ self.alpha1 -= 360
237
+ self.alpha = self.alpha2 - self.alpha1
238
+
239
+ def angle(self) -> str:
240
+ return rf"{self.p1} -- {self.origin} -- {self.p2}"
241
+
242
+ def arc(self, radius: float) -> str:
243
+ return rf"([shift={{{self.origin}}}]{self.alpha1}:{radius}) arc ({self.alpha1}:{self.alpha2}:{radius})"
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