pyetp 0.0.44__py3-none-any.whl → 0.0.46__py3-none-any.whl

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,10 +1,33 @@
1
+ from xsdata.formats.dataclass.models.generics import DerivedElement
1
2
  from xsdata.formats.dataclass.serializers import XmlSerializer
2
3
  from xsdata.formats.dataclass.serializers.config import SerializerConfig
3
4
 
4
5
  import resqml_objects.v201 as ro_201
5
6
 
6
7
 
7
- def serialize_resqml_v201_object(obj: ro_201.AbstractObject) -> bytes:
8
+ def serialize_resqml_v201_object(
9
+ obj: ro_201.AbstractObject | DerivedElement[ro_201.AbstractObject],
10
+ ) -> bytes:
8
11
  serializer = XmlSerializer(config=SerializerConfig())
9
12
 
10
- return str.encode(serializer.render(obj))
13
+ namespace = getattr(obj.Meta, "namespace", None) or obj.Meta.target_namespace
14
+ name = obj.__class__.__name__
15
+
16
+ # This is a solution to enforce the inclusion of the `xsi:type`-attribute
17
+ # on the generated XML-elements.
18
+ if not isinstance(obj, DerivedElement) and name.startswith("obj_"):
19
+ obj = DerivedElement(
20
+ qname=f"{{{namespace}}}{name[4:]}",
21
+ value=obj,
22
+ type=f"{{{namespace}}}{name}",
23
+ )
24
+
25
+ return str.encode(
26
+ serializer.render(
27
+ obj,
28
+ ns_map={
29
+ "eml": "http://www.energistics.org/energyml/data/commonv2",
30
+ "resqml2": "http://www.energistics.org/energyml/data/resqmlv2",
31
+ },
32
+ )
33
+ )
@@ -0,0 +1,295 @@
1
+ import typing
2
+ from dataclasses import dataclass
3
+
4
+ import numpy as np
5
+ import numpy.typing as npt
6
+
7
+ DType = typing.TypeVar("DType", bound=np.float32 | np.float64)
8
+
9
+
10
+ def rotate_2d_vector(r: npt.NDArray[DType], angle: float) -> npt.NDArray[DType]:
11
+ """
12
+ Function used to rotate a set of `N` 2d-vectors stacked in an array of
13
+ shape `(2, N)` by an angle `angle` (in radians) counter-clockwise. This
14
+ function does not remove the origin of the vectors, so that is up to the
15
+ user if this should be done.
16
+
17
+ Returns
18
+ -------
19
+ npt.NDArray[DType]
20
+ The rotated 2d vector with the same shape as the input vector.
21
+ """
22
+ if not np.shape(r)[0] == 2:
23
+ raise TypeError("Array 'r' must have shape '(2,)' or '(2, None)'")
24
+
25
+ if len(np.shape(r)) == 1:
26
+ r = r.reshape(-1, 1)
27
+
28
+ c = np.cos(angle / 2.0)
29
+ s = np.sin(angle / 2.0)
30
+
31
+ return (c**2 - s**2) * r + 2 * c * s * np.vstack([-r[1], r[0]])
32
+
33
+
34
+ def angle_to_unit_vectors(
35
+ angle: float,
36
+ ) -> typing.Annotated[npt.NDArray[np.float64], dict(shape=(2, 2))]:
37
+ """
38
+ Function that constructs a pair of orthonormal unit vectors from an angle
39
+ (in radians), where the first unit vector (the `x`-direction) is rotated
40
+ counter-clockwise to the `[1.0, 0.0]`-axis, and the second unit vector is
41
+ rotated `pi/2.0` compared to the `x`-unit vector.
42
+ """
43
+
44
+ return rotate_2d_vector(np.eye(2).astype(np.float64), angle)
45
+
46
+
47
+ def unit_vectors_to_angle(
48
+ unit_vectors: typing.Annotated[npt.NDArray[DType], dict(shape=(2, 2))],
49
+ ) -> float:
50
+ """
51
+ Function returning the angle (in radians) from the `x`-axis (i.e., the
52
+ `[1.0, 0.0]`-vector) to the `unit_vectors[:, 0]`-vector. This function
53
+ takes in a pair of unit vectors, where the second vector (`unit_vectors[:,
54
+ 1]`) is only used to check that the vectors are orthonormal, and that it is
55
+ rotated `np.pi / 2.0` counter-clockwise relative to the first vector.
56
+
57
+ This function uses `np.atan2` to choose the correct quadrant.
58
+
59
+ Note this entire function can be replaced by `np.atan2` (or `np.angle`
60
+ making the `y`-component of the `x`-vector complex) for the `x`-vector of
61
+ the unit vectors, but is kept to give an interface where you can pass in
62
+ both unit vectors and verify that they are orthonormal and rotated the
63
+ expected way relative to each other.
64
+ """
65
+ x_vec = unit_vectors[:, 0]
66
+ y_vec = unit_vectors[:, 1]
67
+
68
+ tol = np.finfo(x_vec.dtype).eps * 100
69
+ if not np.allclose(x_vec @ y_vec, 0.0, atol=tol):
70
+ raise ValueError("Unit vectors are not orthonormal")
71
+
72
+ angle = np.atan2(x_vec[1], x_vec[0])
73
+
74
+ # Check to ensure that the `y`-vector is rotated `pi / 2.0`
75
+ # counter-clockwise from the `x`-vector.
76
+ y_angle = np.atan2(y_vec[1], y_vec[0])
77
+
78
+ if angle == np.pi / 2.0:
79
+ if y_angle >= 0.0:
80
+ np.testing.assert_allclose(
81
+ angle,
82
+ y_angle - np.pi / 2.0,
83
+ atol=tol,
84
+ )
85
+ else:
86
+ np.testing.assert_allclose(
87
+ angle,
88
+ y_angle + np.pi,
89
+ atol=tol,
90
+ )
91
+ elif np.pi / 2.0 < angle <= np.pi:
92
+ np.testing.assert_allclose(angle, y_angle + 3 * np.pi / 2.0, atol=tol)
93
+ else:
94
+ np.testing.assert_allclose(
95
+ angle,
96
+ y_angle - np.pi / 2.0,
97
+ atol=tol,
98
+ )
99
+
100
+ return angle
101
+
102
+
103
+ @dataclass
104
+ class RegularGridParameters(typing.Generic[DType]):
105
+ """
106
+ Dataclass acting as a set of helper functions and container for regular
107
+ two-dimensional `X` and `Y` grids that follow a surface `Z`. The purpose of
108
+ this dataclass is to either take in uniformly spaced, regular, grids `X`
109
+ and `Y` (can be rotated), or grid vectors `x` and `y`, and store the
110
+ `shape`, `origin`, `spacing` and `unit_vectors` that are needed to
111
+ reconstruct the full grids, and provide methods that returns the full
112
+ grids. Furthermore, the class has optional `crs_angle` (set to `0.0` by
113
+ default, and measured in radians) which makes it convenient to work with
114
+ rotated grids inside rotated local coordinate reference systems.
115
+
116
+ Attributes
117
+ ----------
118
+ shape: tuple[int, int]
119
+ The shape of the full `X` and `Y` grids and the corresponding `Z`-array
120
+ of surface values.
121
+ origin: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))]
122
+ A two-dimensional array of origin values. Corresponds to `X[0, 0]` and
123
+ `Y[0, 0]` in the full grids.
124
+ spacing: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))]
125
+ A two-dimensional array with the grid spacing in each direction.
126
+ unit_vectors: typing.Annotated[npt.NDArray[DType], dict(shape=(2, 2))]
127
+ Two two-dimensional unit vectors of the `X` and `Y` grids. The vectors
128
+ lie in the columns of `unit_vectors`, i.e., `vec_1 = unit_vectors[:,
129
+ 0]` and `vec_2 = unit_vectors[:, 1]`.
130
+ crs_angle: float
131
+ The rotation of the local coordinate reference system, if applicable.
132
+ The default is `0.0`, i.e., an urotated coordinate system is used.
133
+ crs_offset: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))] | None
134
+ The offset of the origin of the local coordinate reference system, if
135
+ applicable. The default is `None` which gets defaulted to `array([0.0,
136
+ 0.0])` of type `DType`.
137
+
138
+ Note that we construct the edge of `X` from `shape[0]`, `origin[0]`,
139
+ `spacing[0]`, and `unit_vectors[:, 0]`, and the edge of `Y` from the second
140
+ element/column.
141
+ """
142
+
143
+ shape: tuple[int, int]
144
+ origin: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))]
145
+ spacing: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))]
146
+ unit_vectors: typing.Annotated[npt.NDArray[DType], dict(shape=(2, 2))]
147
+ crs_angle: float = 0.0
148
+ crs_offset: typing.Annotated[npt.NDArray[DType], dict(shape=(2,))] | None = None
149
+
150
+ # Attaching the helper functions to this class to avoid doing extra
151
+ # imports.
152
+ rotate_2d_vector = staticmethod(rotate_2d_vector)
153
+ angle_to_unit_vectors = staticmethod(angle_to_unit_vectors)
154
+ unit_vectors_to_angle = staticmethod(unit_vectors_to_angle)
155
+
156
+ def __post_init__(self) -> None:
157
+ if self.crs_offset is None:
158
+ self.crs_offset = np.zeros_like(self.origin)
159
+
160
+ def to_xy_grid(
161
+ self, to_global_crs: bool = True
162
+ ) -> tuple[
163
+ npt.NDArray[DType],
164
+ npt.NDArray[DType],
165
+ ]:
166
+ vec_1 = self.unit_vectors[:, 0]
167
+ vec_2 = self.unit_vectors[:, 1]
168
+ origin = self.origin
169
+
170
+ if to_global_crs:
171
+ # Here we construct the unit vectors of the global CRS as seen from
172
+ # the local CRS (global-in-local -> ginl). Hence, the negative sign
173
+ # on the rotation. The global unit vectors are in the columns (axis
174
+ # 1) of the 2 x 2 matrix.
175
+ ginl_unit_vectors = self.angle_to_unit_vectors(-self.crs_angle)
176
+ # Next, we find the elements of the grid unit vectors in the global
177
+ # coordinate system.
178
+ #
179
+ # (new_vec_i)_k = (vec_i)_k @ e_k,
180
+ #
181
+ # where `e_k` is the `k`'th column of `ginl_unit_vectors` and
182
+ # `(vec_i)_k` is the `k`'th element of `vec_i` (with `i` being
183
+ # either `1` or `2`). Below we do the full transformation in a
184
+ # single call.
185
+ new_unit_vectors = np.einsum(
186
+ "ij, kj -> ik", self.unit_vectors, ginl_unit_vectors
187
+ )
188
+
189
+ vec_1 = new_unit_vectors[:, 0]
190
+ vec_2 = new_unit_vectors[:, 1]
191
+
192
+ # Computing the origin of the surface in the global CRS by adding
193
+ # in the offset of the local CRS.
194
+ origin = origin + self.crs_offset
195
+
196
+ edge_1 = vec_1 * self.spacing[0] * np.arange(self.shape[0]).reshape(-1, 1)
197
+ edge_2 = vec_2 * self.spacing[1] * np.arange(self.shape[1]).reshape(-1, 1)
198
+
199
+ X = edge_1[:, 0].reshape(-1, 1) + edge_2[:, 0]
200
+ Y = edge_1[:, 1].reshape(-1, 1) + edge_2[:, 1]
201
+
202
+ return X + origin[0], Y + origin[1]
203
+
204
+ @classmethod
205
+ def from_xy_grid(
206
+ cls,
207
+ X: npt.NDArray[DType],
208
+ Y: npt.NDArray[DType],
209
+ crs_angle: float = 0.0,
210
+ crs_offset: npt.NDArray[DType] | None = None,
211
+ ) -> typing.Self:
212
+ if len(np.shape(np.squeeze(X))) == 1 and len(np.shape(np.squeeze(Y))) == 1:
213
+ return cls.from_xy_grid_vectors(
214
+ X, Y, crs_angle=crs_angle, crs_offset=crs_offset
215
+ )
216
+
217
+ assert len(np.shape(X)) == 2
218
+ assert np.shape(X) == np.shape(Y)
219
+ assert X.dtype == Y.dtype
220
+
221
+ x_col_diffs = np.diff(X, axis=1)
222
+ y_col_diffs = np.diff(Y, axis=1)
223
+ x_row_diffs = np.diff(X, axis=0)
224
+ y_row_diffs = np.diff(Y, axis=0)
225
+
226
+ tol = np.finfo(X.dtype).eps * 10
227
+ # Check that the spacing is uniform in all directions.
228
+ np.testing.assert_allclose(x_col_diffs, x_col_diffs[0, 0], atol=tol)
229
+ np.testing.assert_allclose(y_col_diffs, y_col_diffs[0, 0], atol=tol)
230
+ np.testing.assert_allclose(x_row_diffs, x_row_diffs[0, 0], atol=tol)
231
+ np.testing.assert_allclose(y_row_diffs, y_row_diffs[0, 0], atol=tol)
232
+
233
+ xvec = np.array([x_row_diffs[0, 0], y_row_diffs[0, 0]])
234
+ yvec = np.array([x_col_diffs[0, 0], y_col_diffs[0, 0]])
235
+
236
+ spacing = np.array([np.linalg.norm(xvec), np.linalg.norm(yvec)])
237
+ xu = xvec / spacing[0]
238
+ yu = yvec / spacing[1]
239
+
240
+ # Verify that the unit vectors are orthonormal.
241
+ np.testing.assert_allclose(abs(xu @ yu) ** 2, 0, atol=tol)
242
+
243
+ unit_vectors = np.column_stack([xu, yu])
244
+ # TODO: Remove these
245
+ np.testing.assert_equal(unit_vectors[:, 0], xu)
246
+ np.testing.assert_equal(unit_vectors[:, 1], yu)
247
+
248
+ return cls(
249
+ shape=np.shape(X),
250
+ origin=np.array([X[0, 0], Y[0, 0]]),
251
+ spacing=spacing,
252
+ unit_vectors=unit_vectors,
253
+ crs_angle=crs_angle,
254
+ crs_offset=crs_offset,
255
+ )
256
+
257
+ @classmethod
258
+ def from_xy_grid_vectors(
259
+ cls,
260
+ x: npt.NDArray[DType],
261
+ y: npt.NDArray[DType],
262
+ crs_angle: float = 0.0,
263
+ crs_offset: npt.NDArray[DType] | None = None,
264
+ ) -> typing.Self:
265
+ x = np.squeeze(x)
266
+ y = np.squeeze(y)
267
+
268
+ if len(np.shape(x)) != 1 or len(np.shape(y)) != 1:
269
+ raise ValueError(
270
+ "The 'x' and 'y' grid vectors must be 1-d arrays (after using "
271
+ "`np.squeeze`)"
272
+ )
273
+
274
+ x_spacing = np.diff(x)
275
+ y_spacing = np.diff(y)
276
+
277
+ tol = np.finfo(x.dtype).eps * 10
278
+ if not np.allclose(x_spacing, x_spacing[0], atol=tol) or not np.allclose(
279
+ y_spacing, y_spacing[0], atol=tol
280
+ ):
281
+ raise ValueError("The 'x' and 'y' grid vectors must have a uniform spacing")
282
+
283
+ assert x.dtype == y.dtype
284
+
285
+ shape = (len(x), len(y))
286
+ unit_vectors = np.eye(2).astype(x.dtype)
287
+
288
+ return cls(
289
+ shape=shape,
290
+ origin=np.array([x[0], y[0]]),
291
+ spacing=np.array([x_spacing[0], y_spacing[0]]),
292
+ unit_vectors=unit_vectors,
293
+ crs_angle=crs_angle,
294
+ crs_offset=crs_offset,
295
+ )