bspy 3.0.1__py3-none-any.whl → 4.1__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.
bspy/manifold.py ADDED
@@ -0,0 +1,391 @@
1
+ import numpy as np
2
+ from collections import namedtuple
3
+
4
+ class Manifold:
5
+ """
6
+ A manifold is an abstract base class for differentiable functions with
7
+ normals and tangent spaces whose range is one dimension higher than their domain.
8
+ """
9
+
10
+ minSeparation = 0.01
11
+ """If two points are within 0.01 of each each other, they are coincident."""
12
+
13
+ Crossing = namedtuple('Crossing', ('left','right'))
14
+ Coincidence = namedtuple('Coincidence', ('left', 'right', 'alignment', 'transform', 'inverse', 'translation'))
15
+ """Return type for intersect."""
16
+
17
+ factory = {}
18
+ """Factory dictionary for creating manifolds."""
19
+
20
+ def __init__(self):
21
+ pass
22
+
23
+ def cached_intersect(self, other, cache = None):
24
+ """
25
+ Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
26
+
27
+ Parameters
28
+ ----------
29
+ other : `Manifold`
30
+ The `Manifold` intersecting the manifold.
31
+
32
+ cache : `dict`, optional
33
+ A dictionary to cache `Manifold` intersections, speeding computation. The default is `None`.
34
+
35
+ Returns
36
+ -------
37
+ intersections : `list` (or `NotImplemented` if other is an unknown type of Manifold)
38
+ A list of intersections between the two manifolds.
39
+ Each intersection records either a crossing or a coincident region.
40
+
41
+ For a crossing, intersection is a Manifold.Crossing: (left, right)
42
+ * left : `Manifold` in the manifold's domain where the manifold and the other cross.
43
+ * right : `Manifold` in the other's domain where the manifold and the other cross.
44
+ * Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
45
+
46
+ For a coincident region, intersection is Manifold.Coincidence: (left, right, alignment, transform, inverse, translation)
47
+ * left : `Solid` in the manifold's domain within which the manifold and the other are coincident.
48
+ * right : `Solid` in the other's domain within which the manifold and the other are coincident.
49
+ * alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
50
+ * transform : `numpy.array` holding the matrix transform from the boundary's domain to the other's domain.
51
+ * inverse : `numpy.array` holding the matrix inverse transform from the other's domain to the boundary's domain.
52
+ * translation : `numpy.array` holding the 1D translation from the manifold's domain to the other's domain.
53
+ * Together transform, inverse, and translation form the mapping from the manifold's domain to the other's domain and vice-versa.
54
+
55
+ isTwin : `bool`
56
+ True if this intersection is the twin from the cache (the intersection with self and other swapped).
57
+
58
+ See Also
59
+ --------
60
+ `intersect` : Intersect two manifolds.
61
+ `Solid.slice` : slice the solid by a manifold.
62
+
63
+ Notes
64
+ -----
65
+ To invert the mapping to go from the other's domain to the manifold's domain, you first subtract the translation and then multiply by the inverse of the transform.
66
+ """
67
+ intersections = None
68
+ isTwin = False
69
+ # Check cache for previously computed manifold intersections.
70
+ if cache is not None:
71
+ # First, check for the twin (opposite order of arguments).
72
+ intersections = cache.get((other, self))
73
+ if intersections is not None:
74
+ isTwin = True
75
+ else:
76
+ # Next, check for the original order (not twin).
77
+ intersections = cache.get((self, other))
78
+
79
+ # If intersections not previously computed, compute them now.
80
+ if intersections is None:
81
+ intersections = self.intersect(other)
82
+ if intersections is NotImplemented:
83
+ # Try the other way around in case other knows how to intersect self.
84
+ intersections = other.intersect(self)
85
+ isTwin = True
86
+ # Store intersections in cache.
87
+ if cache is not None:
88
+ if isTwin:
89
+ cache[(other, self)] = intersections
90
+ else:
91
+ cache[(self, other)] = intersections
92
+
93
+ return intersections, isTwin
94
+
95
+ def complete_slice(self, slice, solid):
96
+ """
97
+ Add any missing inherent (implicit) boundaries of this manifold's domain to the given slice of the
98
+ given solid that are needed to make the slice valid and complete.
99
+
100
+ Parameters
101
+ ----------
102
+ slice : `Solid`
103
+ The slice of the given solid formed by the manifold. The slice may be incomplete, missing some of the
104
+ manifold's inherent domain boundaries. Its dimension must match `self.domain_dimension()`.
105
+
106
+ solid : `Solid`
107
+ The solid being sliced by the manifold. Its dimension must match `self.range_dimension()`.
108
+
109
+ See Also
110
+ --------
111
+ `Solid.slice` : Slice the solid by a manifold.
112
+
113
+ Notes
114
+ -----
115
+ For manifolds without inherent domain boundaries (like hyperplanes), the operation does nothing.
116
+ """
117
+ assert self.domain_dimension() == slice.dimension
118
+ assert self.range_dimension() == solid.dimension
119
+
120
+ def copy(self):
121
+ """
122
+ Copy the manifold.
123
+
124
+ Returns
125
+ -------
126
+ manifold : `Manifold`
127
+ """
128
+ return None
129
+
130
+ def domain_dimension(self):
131
+ """
132
+ Return the domain dimension.
133
+
134
+ Returns
135
+ -------
136
+ dimension : `int`
137
+ """
138
+ return None
139
+
140
+ def evaluate(self, domainPoint):
141
+ """
142
+ Return the value of the manifold (a point on the manifold).
143
+
144
+ Parameters
145
+ ----------
146
+ domainPoint : `numpy.array`
147
+ The 1D array at which to evaluate the point.
148
+
149
+ Returns
150
+ -------
151
+ point : `numpy.array`
152
+ """
153
+ return None
154
+
155
+ def flip_normal(self):
156
+ """
157
+ Flip the direction of the normal.
158
+
159
+ Returns
160
+ -------
161
+ manifold : `Manifold`
162
+ The manifold with flipped normal. The manifold retains the same tangent space.
163
+
164
+ See Also
165
+ --------
166
+ `Solid.complement` : Return the complement of the solid: whatever was inside is outside and vice-versa.
167
+ """
168
+ return None
169
+
170
+ @staticmethod
171
+ def from_dict(dictionary):
172
+ """
173
+ Create a `Manifold` from a data in a `dict`.
174
+
175
+ Parameters
176
+ ----------
177
+ dictionary : `dict`
178
+ The `dict` containing `Manifold` data.
179
+
180
+ Returns
181
+ -------
182
+ manifold : `Manifold`
183
+
184
+ See Also
185
+ --------
186
+ `to_dict` : Return a `dict` with `Manifold` data.
187
+ """
188
+ return None
189
+
190
+ def full_domain(self):
191
+ """
192
+ Return a solid that represents the full domain of the manifold.
193
+
194
+ Returns
195
+ -------
196
+ domain : `Solid`
197
+ The full (untrimmed) domain of the manifold.
198
+
199
+ See Also
200
+ --------
201
+ `Boundary` : A portion of the boundary of a solid.
202
+ """
203
+ return None
204
+
205
+ def intersect(self, other):
206
+ """
207
+ Intersect two manifolds (self and other).
208
+
209
+ Parameters
210
+ ----------
211
+ other : `Manifold`
212
+ The `Manifold` intersecting self.
213
+
214
+ Returns
215
+ -------
216
+ intersections : `list` (or `NotImplemented` if other is an unknown type of Manifold)
217
+ A list of intersections between the two manifolds.
218
+ Each intersection records either a crossing or a coincident region.
219
+
220
+ For a crossing, intersection is a `Manifold.Crossing`: (left, right)
221
+ * left : `Manifold` in the manifold's domain where the manifold and the other cross.
222
+ * right : `Manifold` in the other's domain where the manifold and the other cross.
223
+ * Both intersection manifolds have the same domain and range (the crossing between the manifold and the other).
224
+
225
+ For a coincident region, intersection is a `Manifold.Coincidence`: (left, right, alignment, transform, inverse, translation)
226
+ * left : `Solid` in the manifold's domain within which the manifold and the other are coincident.
227
+ * right : `Solid` in the other's domain within which the manifold and the other are coincident.
228
+ * alignment : scalar value holding the normal alignment between the manifold and the other (the dot product of their unit normals).
229
+ * transform : `numpy.array` holding the transform matrix from the manifold's domain to the other's domain.
230
+ * inverse : `numpy.array` holding the inverse transform matrix from the other's domain to the boundary's domain.
231
+ * translation : `numpy.array` holding the translation vector from the manifold's domain to the other's domain.
232
+ * Together transform, inverse, and translation form the mapping from the manifold's domain to the other's domain and vice-versa.
233
+
234
+ See Also
235
+ --------
236
+ `cached_intersect` : Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
237
+ `Solid.slice` : slice the solid by a manifold.
238
+
239
+ Notes
240
+ -----
241
+ To invert the mapping to go from the other's domain to the manifold's domain, you first subtract the translation and then multiply by the inverse of the transform.
242
+ """
243
+ return NotImplemented
244
+
245
+ def normal(self, domainPoint, normalize=True, indices=None):
246
+ """
247
+ Return the normal.
248
+
249
+ Parameters
250
+ ----------
251
+ domainPoint : `numpy.array`
252
+ The 1D array at which to evaluate the normal.
253
+
254
+ normalize : `boolean`, optional
255
+ If True the returned normal will have unit length (the default). Otherwise, the normal's length will
256
+ be the area of the tangent space (for two independent variables, its the length of the cross product of tangent vectors).
257
+
258
+ indices : `iterable`, optional
259
+ An iterable of normal indices to calculate. For example, `indices=(0, 3)` will return a vector of length 2
260
+ with the first and fourth values of the normal. If `None`, all normal values are returned (the default).
261
+
262
+ Returns
263
+ -------
264
+ normal : `numpy.array`
265
+ """
266
+ return None
267
+
268
+ def range_bounds(self):
269
+ """
270
+ Return the range bounds for the manifold.
271
+
272
+ Returns
273
+ -------
274
+ rangeBounds : `np.array` or `None`
275
+ The range of the manifold given as lower and upper bounds on each dependent variable.
276
+ If the manifold has an unbounded range, `None` is returned.
277
+ """
278
+ return None
279
+
280
+ def range_dimension(self):
281
+ """
282
+ Return the range dimension.
283
+
284
+ Returns
285
+ -------
286
+ dimension : `int`
287
+ """
288
+ return 0
289
+
290
+ @staticmethod
291
+ def register(manifold):
292
+ """
293
+ Class decorator for subclasses of `Manifold` that registers the subclass with the `Manifold` factory.
294
+ """
295
+ Manifold.factory[manifold.__name__] = manifold
296
+ return manifold
297
+
298
+ def tangent_space(self, domainPoint):
299
+ """
300
+ Return the tangent space.
301
+
302
+ Parameters
303
+ ----------
304
+ domainPoint : `numpy.array`
305
+ The 1D array at which to evaluate the tangent space.
306
+
307
+ Returns
308
+ -------
309
+ tangentSpace : `numpy.array`
310
+ """
311
+ return None
312
+
313
+ def to_dict(self):
314
+ """
315
+ Return a `dict` with `Manifold` data.
316
+
317
+ Returns
318
+ -------
319
+ dictionary : `dict`
320
+
321
+ See Also
322
+ --------
323
+ `from_dict` : Create a `Manifold` from a data in a `dict`.
324
+ """
325
+ return None
326
+
327
+ def transform(self, matrix, matrixInverseTranspose = None):
328
+ """
329
+ Transform the range of the manifold.
330
+
331
+ Parameters
332
+ ----------
333
+ matrix : `numpy.array`
334
+ A square matrix transformation.
335
+
336
+ matrixInverseTranspose : `numpy.array`, optional
337
+ The inverse transpose of matrix (computed if not provided).
338
+
339
+ Returns
340
+ -------
341
+ manifold : `Manifold`
342
+ The transformed manifold.
343
+
344
+ See Also
345
+ --------
346
+ `Solid.transform` : transform the range of the solid.
347
+ """
348
+ assert np.shape(matrix) == (self.range_dimension(), self.range_dimension())
349
+ return None
350
+
351
+ def translate(self, delta):
352
+ """
353
+ Translate the range of the manifold.
354
+
355
+ Parameters
356
+ ----------
357
+ delta : `numpy.array`
358
+ A 1D array translation.
359
+
360
+ Returns
361
+ -------
362
+ manifold : `Manifold`
363
+ The translated manifold.
364
+
365
+ See Also
366
+ --------
367
+ `Solid.translate` : translate the range of the solid.
368
+ """
369
+ assert len(delta) == self.range_dimension()
370
+ return None
371
+
372
+ def trimmed_range_bounds(self, domainBounds):
373
+ """
374
+ Return the trimmed range bounds for the manifold.
375
+
376
+ Parameters
377
+ ----------
378
+ domainBounds : array-like
379
+ An array with shape (domain_dimension, 2) of lower and upper and lower bounds on each manifold parameter.
380
+
381
+ Returns
382
+ -------
383
+ trimmedManifold, rangeBounds : `Manifold`, `np.array`
384
+ A manifold trimmed to the given domain bounds, and the range of the trimmed manifold given as
385
+ lower and upper bounds on each dependent variable.
386
+
387
+ Notes
388
+ -----
389
+ The returned trimmed manifold may be the original manifold, depending on the subclass of manifold.
390
+ """
391
+ return None, None