@@ -1266,6 +1266,9 @@ def __init_subclass__(cls):
1266
1266
def __add__ (self , other ):
1267
1267
"""
1268
1268
Compose two transforms together so that *self* is followed by *other*.
1269
+
1270
+ ``A + B`` returns a transform ``C`` so that
1271
+ ``C.transform(x) == B.transform(A.transform(x))``.
1269
1272
"""
1270
1273
return (composite_transform_factory (self , other )
1271
1274
if isinstance (other , Transform ) else
@@ -1340,24 +1343,32 @@ def contains_branch_seperately(self, other_transform):
1340
1343
1341
1344
def __sub__ (self , other ):
1342
1345
"""
1343
- Return a transform stack which goes all the way down self's transform
1344
- stack, and then ascends back up other's stack. If it can, this is
1345
- optimised::
1346
+ Compose *self* with the inverse of *other*, cancelling identical terms
1347
+ if any::
1346
1348
1347
- # normally
1348
- A - B == a + b.inverted()
1349
+ # In general:
1350
+ A - B == A + B.inverted()
1351
+ # (but see note regarding frozen transforms below).
1349
1352
1350
- # sometimes, when A contains the tree B there is no need to
1351
- # descend all the way down to the base of A (via B), instead we
1352
- # can just stop at B.
1353
+ # If A "ends with" B (i.e. A == A' + B for some A') we can cancel
1354
+ # out B:
1355
+ (A' + B) - B == A'
1353
1356
1354
- (A + B) - (B)^-1 == A
1357
+ # Likewise, if B "starts with" A (B = A + B'), we can cancel out A:
1358
+ A - (A + B') == B'.inverted() == B'^-1
1355
1359
1356
- # similarly, when B contains tree A, we can avoid descending A at
1357
- # all, basically:
1358
- A - (A + B) == ((B + A) - A).inverted() or B^-1
1360
+ Cancellation (rather than naively returning ``A + B.inverted()``) is
1361
+ important for multiple reasons:
1359
1362
1360
- For clarity, the result of ``(A + B) - B + B == (A + B)``.
1363
+ - It avoids floating-point inaccuracies when computing the inverse of
1364
+ B: ``B - B`` is guaranteed to cancel out exactly (resulting in the
1365
+ identity transform), whereas ``B + B.inverted()`` may differ by a
1366
+ small epsilon.
1367
+ - ``B.inverted()`` always returns a frozen transform: if one computes
1368
+ ``A + B + B.inverted()`` and later mutates ``B``, then
1369
+ ``B.inverted()`` won't be updated and the last two terms won't cancel
1370
+ out anymore; on the other hand, ``A + B - B`` will always be equal to
1371
+ ``A`` even if ``B`` is mutated.
1361
1372
"""
1362
1373
# we only know how to do this operation if other is a Transform.
1363
1374
if not isinstance (other , Transform ):
0 commit comments