/
transformation.py
209 lines (142 loc) · 5.59 KB
/
transformation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import numpy as np
import weakref
from . import snapshot
class Transformation(object):
def __init__(self, f, defer=False):
if isinstance(f, snapshot.SimSnap):
self.sim = f
self.next_transformation = None
elif isinstance(f, Transformation):
self.sim = None
self.next_transformation = f
else:
raise ValueError("Transformation must either act on another Transformation or on a SimSnap")
self.applied = False
if not defer:
self.apply(force=False)
@property
def sim(self):
return self._sim()
@sim.setter
def sim(self, sim):
if sim is None:
self._sim = lambda: None
else:
self._sim = weakref.ref(sim)
def apply_to(self, f):
if self.next_transformation is not None:
# this is a chained transformation, get the SimSnap to operate on
# from the level below
f = self.next_transformation.apply_to(f)
self._apply(f)
return f
def apply_inverse_to(self, f):
self._revert(f)
if self.next_transformation is not None:
self.next_transformation.apply_inverse_to(f)
def apply(self, force=True):
if self.next_transformation is not None:
# this is a chained transformation, get the SimSnap to operate on
# from the level below
f = self.next_transformation.apply(force=force)
else:
f = self.sim
if self.applied and force:
raise RuntimeError("Transformation has already been applied")
if not self.applied:
self._apply(f)
self.applied = True
self.sim = f
return f
def revert(self):
if not self.applied:
raise RuntimeError("Transformation has not been applied")
self._revert(self.sim)
self.applied = False
if self.next_transformation is not None:
self.next_transformation.revert()
def _apply(self, f):
pass
def _revert(self, f):
pass
def __enter__(self):
self.apply(force=False)
def __exit__(self, *args):
self.revert()
class GenericTranslation(Transformation):
def __init__(self, f, arname, shift):
self.shift = shift
self.arname = arname
super(GenericTranslation, self).__init__(f)
def _apply(self, f):
f[self.arname] += self.shift
def _revert(self, f):
f[self.arname] -= self.shift
class GenericRotation(Transformation):
def __init__(self, f, matrix, ortho_tol=1.e-8):
# Check that the matrix is orthogonal
resid = np.dot(matrix, np.asarray(matrix).T) - np.eye(3)
resid = (resid ** 2).sum()
if resid > ortho_tol or resid != resid:
raise ValueError("Transformation matrix is not orthogonal")
self.matrix = matrix
super(GenericRotation, self).__init__(f)
def _apply(self, f):
f._transform(self.matrix)
def _revert(self, f):
f._transform(self.matrix.T)
def translate(f, shift):
"""Form a context manager for translating the simulation *f* by the given
spatial *shift*.
This allows you to enclose a code block within which the simulation is offset
by the specified amount. On exiting the code block, you are guaranteed the
simulation is put back to where it started, so
with translate(f, shift) :
print f['pos'][0]
is equivalent to
try:
f['pos']+=shift
print f['pos'][0]
finally:
f['pos']-=shift
On the other hand,
translate(f, shift)
print f['pos'][0]
Performs the translation but does not revert it at any point.
"""
return GenericTranslation(f, 'pos', shift)
def inverse_translate(f, shift):
"""Form a context manager for translating the simulation *f* by the spatial
vector *-shift*.
For a fuller description, see *translate*"""
return translate(f, -np.asarray(shift))
def v_translate(f, shift):
"""Form a context manager for translating the simulation *f* by the given
velocity *shift*.
For a fuller description, see *translate* (which applies to position transformations)."""
return GenericTranslation(f, 'vel', shift)
def inverse_v_translate(f, shift):
"""Form a context manager for translating the simulation *f* by the given
velocity *-shift*.
For a fuller description, see *translate* (which applies to position transformations)."""
return GenericTranslation(f, 'vel', -np.asarray(shift))
def xv_translate(f, x_shift, v_shift):
"""Form a context manager for translating the simulation *f* by the given
position *x_shift* and velocity *v_shift*.
For a fuller description, see *translate* (which applies to position transformations)."""
return translate(v_translate(f, v_shift),
x_shift)
def inverse_xv_translate(f, x_shift, v_shift):
"""Form a context manager for translating the simulation *f* by the given
position *-x_shift* and velocity *-v_shift*.
For a fuller description, see *translate* (which applies to position transformations)."""
return translate(v_translate(f, -np.asarray(v_shift)),
-np.asarray(x_shift))
def transform(f, matrix):
"""Form a context manager for rotating the simulation *f* by the given 3x3
*matrix*"""
return GenericRotation(f, matrix)
def null(f):
"""Form a context manager for the null transformation (useful to avoid messy extra logic for situations where it's
unclear whether any transformation will be applied)"""
return Transformation(f)