bspy 4.0__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 CHANGED
@@ -14,9 +14,194 @@ class Manifold:
14
14
  Coincidence = namedtuple('Coincidence', ('left', 'right', 'alignment', 'transform', 'inverse', 'translation'))
15
15
  """Return type for intersect."""
16
16
 
17
+ factory = {}
18
+ """Factory dictionary for creating manifolds."""
19
+
17
20
  def __init__(self):
18
21
  pass
19
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
+
20
205
  def intersect(self, other):
21
206
  """
22
207
  Intersect two manifolds (self and other).
@@ -48,8 +233,8 @@ class Manifold:
48
233
 
49
234
  See Also
50
235
  --------
51
- `cached_intersect_manifold` : Intersect two manifolds, caching the result for twins (same intersection but swapping self and other).
52
- `solid.Solid.slice` : slice the solid by a manifold.
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.
53
238
 
54
239
  Notes
55
240
  -----
@@ -57,32 +242,150 @@ class Manifold:
57
242
  """
58
243
  return NotImplemented
59
244
 
60
- class Hyperplane(Manifold):
61
- """
62
- A hyperplane is a `Manifold` defined by a unit normal, a point on the hyperplane, and a tangent space orthogonal to the normal.
63
-
64
- Parameters
65
- ----------
66
- normal : array-like
67
- The unit normal.
68
-
69
- point : array-like
70
- A point on the hyperplane.
71
-
72
- tangentSpace : array-like
73
- A array of tangents that are linearly independent and orthogonal to the normal.
74
-
75
- Notes
76
- -----
77
- The number of coordinates in the normal defines the dimension of the range of the hyperplane. The point must have the same dimension. The tangent space must be shaped: (dimension, dimension-1).
78
- Thus the dimension of the domain is one less than that of the range.
79
- """
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.
80
306
 
81
- maxAlignment = 0.99 # 1 - 1/10^2
82
- """If a shift of 1 in the normal direction of one manifold yields a shift of 10 in the tangent plane intersection, the manifolds are parallel."""
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.
83
316
 
84
- def __init__(self, normal, point, tangentSpace):
85
- self._normal = np.atleast_1d(np.array(normal))
86
- self._point = np.atleast_1d(np.array(point))
87
- self._tangentSpace = np.atleast_1d(np.array(tangentSpace))
88
- if not np.allclose(self._tangentSpace.T @ self._normal, 0.0): raise ValueError("normal must be orthogonal to tangent space")
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