physpyx 3.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: physpyx
3
- Version: 3.1.0
3
+ Version: 3.2.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.2.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.2.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,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
 
@@ -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