-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfind_ortho.c
376 lines (343 loc) · 11.1 KB
/
find_ortho.c
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/*
* Copyright (c) 2005 by the Society of Exploration Geophysicists.
* For more information, go to http://software.seg.org/2005/0001 .
* You must read and accept usage terms at:
* http://software.seg.org/disclaimer.txt before use.
*
* Revision history:
* Original SEG version by Joe Dellinger, BP EPTG, July 2005.
*/
/*
* Given a set of elastic constants, the routine "ortho_distance" finds the
* distance between that set of elastic constants and the nearest
* Orthorhombic medium with the XY, XZ, and YZ planes as symmetry planes.
*
* We want to find the nearest Orthorhombic medium regardless of the
* orientation of the symmetry planes. This subroutine scans over all possible
* unique orientations, and finds a trial orthorhombic orientation that
* produces a minimum distance. It then refines the search in that vicinity
* until it converges to an optimal answer.
*
* On input:
* cc is a 6x6 array of Voigt-notation elastic stiffness constants.
*
* On output:
* rmat is the 3x3 array that will rotate the input stiffness tensor
* into the canonical coordinate system of the best-approximating
* orthorhombic medium, ie, with the XYZ axes in the orthorhombic symmetry
* planes. The ambiguity of which axes should map to "X", "Y", and "Z"
* is resolved by choosing "Z" to be the one of the three that best
* serves as a transversely isotropic symmetry axis. The "Y" axis is
* then the second-best choice of the three.
*
* Return value:
* The distance between the nearest transversely orthorhombic medium
* and the input medium, in absolute units (not normalized).
*/
#include <math.h>
#include "cmat.h"
/* How much to refine after each successive search */
#define SUBDIVIDE 5
/* How much to subdivide the rotation around the axis */
#define SUB_ROT 29
/* How much to subdivide the choice of axis */
#define SUB_POS 5
#ifdef DOUBLE_PRECISION
#define END_RES (1.e-9)
#else
#define END_RES (1.e-6)
#endif
#define NOT_SET_YET 1000.
#define NO_NORM -1.
FLT_DBL
find_ortho (FLT_DBL * cc, FLT_DBL * rmat)
{
int kk;
FLT_DBL ccrot[6 * 6];
FLT_DBL ccortho[6 * 6];
FLT_DBL ccrot2[6 * 6];
FLT_DBL ccti[6 * 6];
FLT_DBL rmat_transp[9];
FLT_DBL rmat_temp[9];
FLT_DBL rmat_temp2[9];
FLT_DBL vec[3];
FLT_DBL vec2[3];
FLT_DBL dist;
FLT_DBL dista[3];
FLT_DBL qq[4], qq_best[4];
double center[4];
double range[4];
int count[4];
int qindex[4];
double inc[4];
FLT_DBL phi, theta;
FLT_DBL temp;
FLT_DBL dist_best;
/*
* Keep the compiler from complaining that this may be uninitialized.
*/
dist_best = NO_NORM;
/*
* Search over all possible orientations.
*
* Any orientation in 3-space can be specified by a unit vector,
* giving an axis to rotate around, and an angle to rotate about
* the given axis. The orientation is given with respect to some
* fixed reference orientation.
*
* Since rotating by theta degrees about (A,B,C) produces the same
* result as rotating -theta degrees about (-A,-B,-C), we only
* need to consider 180 degrees worth of angles, not 360.
*
* In this application, we are finding the orientation of an orthorhombic
* medium. Orthorhombic symmetry has three orthogonal symmetry planes,
* so any one octant defines the whole. We thus only need to search
* over rotation axes within one octant.
*
* Following the article in EDN, March 2, 1995, on page 95, author
* "Do-While Jones" (a pen name of R. David Pogge),
* "Quaternions quickly transform coordinates without error buildup",
* we use quaternions to express the rotation. The article can be read
* online here:
* http://www.reed-electronics.com/ednmag/archives/1995/030295/05df3.htm
*
* If (A,B,C) is a unit vector to rotate theta degrees about, then:
*
* q0 = Cos (theta/2)
* q1 = A * Sin(theta/2)
* q2 = B * Sin(theta/2)
* q3 = C * Sin(theta/2)
*
* so that q0^2 + q1^2 + q2^2 + q3^2 = 1. (A unit magnitude quaternion
* represents a pure rotation, with no change in scale).
*
* For our case, taking advantage of the orthorhombic symmetry to
* restrict the search space, we have:
* 0 <= A <= 1
* 0 <= B <= 1
* 0 <= C <= 1
* 0 <= theta <= 180 degrees.
* The rotation axis direction is limited to within one octant,
* and the rotation about that axis is limited to half of the full circle.
*
* In terms of quaternions, this bounds all four elements between 0 and 1,
* inclusive.
*/
/*
* How much to subdivide each quaternion axis in the original scan. These
* were somewhat arbitrarily chosen. These choices appear to be overkill,
* but that ensures we won't accidentally miss the correct result by
* insufficient sampling of the search space.
*/
/*
* We sample the rotation angle more finely than the rotation axis.
*/
count[0] = SUB_ROT;
count[1] = SUB_POS;
count[2] = SUB_POS;
count[3] = SUB_POS;
/*
* Between 0. and 1. for all 4 Q's (That is .5 +- .5.)
*/
for (kk = 0; kk < 4; kk++)
{
range[kk] = .5;
center[kk] = .5;
/*
* A number meaning "not set yet", to get us through the loop the
* first time. Needs to be much bigger than END_RES.
*/
inc[kk] = NOT_SET_YET;
}
while (inc[0] > END_RES && inc[1] > END_RES &&
inc[2] > END_RES && inc[3] > END_RES)
{
/*
* Update inc to reflect the increment for the current search
*/
for (kk = 0; kk < 4; kk++)
{
inc[kk] = (2. * range[kk]) / (FLT_DBL) (count[kk] - 1);
}
/*
* Start the 4-dimensional search. Keep track of the best result
* found so far. The distance must be non-negative; we use -1 to mean
* "not set yet".
*/
dist_best = NO_NORM;
for (qindex[3] = 0; qindex[3] < count[3]; qindex[3]++)
for (qindex[2] = 0; qindex[2] < count[2]; qindex[2]++)
for (qindex[1] = 0; qindex[1] < count[1]; qindex[1]++)
for (qindex[0] = 0; qindex[0] < count[0]; qindex[0]++)
{
/*
* Calculate the quaternion for this search point.
*/
for (kk = 0; kk < 4; kk++)
{
/*
* The term in parenthesis ranges from -1 to +1,
* inclusive, so qq ranges from (-range+center)
* to (+range + center).
*/
qq[kk] =
range[kk] *
(((FLT_DBL)
(2 * qindex[kk] -
(count[kk] -
1))) / ((FLT_DBL) (count[kk] - 1))) +
center[kk];
}
/*
* Convert from a quaternion to a rotation matrix.
* The subroutine also takes care of normalizing the
* quaternion.
*/
quaternion_to_matrix (qq, rmat);
/*
* Apply the rotation matrix to the elastic stiffness
* matrix.
*/
rotate_tensor (ccrot, cc, rmat);
/*
* Find the distance of the rotated medium from
* orthorhombic aligned with the coordinate axes.
*/
dist = ortho_distance (ccortho, ccrot);
/*
* If it's the best found so far, or the first time
* through, remember it.
*/
if (dist < dist_best || dist_best < 0.)
{
dist_best = dist;
for (kk = 0; kk < 4; kk++)
qq_best[kk] = qq[kk];
}
}
/*
* Refine for the next, finer, search. To avoid any possible problem
* caused by the optimal solution landing at an edge, we search over
* twice the distance between the two search points from the previous
* iteration.
*/
for (kk = 0; kk < 4; kk++)
{
center[kk] = qq_best[kk];
count[kk] = SUBDIVIDE;
range[kk] = inc[kk];
}
/*
* We keep refining and searching the ever finer grid until we
* achieve the required accuracy, at which point we fall out the
* bottom of the loop here.
*/
}
/*
* We've got the answer to sufficient resolution... clean it up a bit,
* then output it.
*/
/*
* Convert the best answer from a Quaternion back to a rotation matrix
*/
quaternion_to_matrix (qq_best, rmat);
/*
* To make the order of the axes unique, we sort the principal axes
* according to how well they work as a TI symmetry axis.
*
* Specifically, since after rotation the medium is canonically oriented,
* with the X, Y, and Z axes the principal axes, the INVERSE rotation
* must take the X, Y, and Z axes to the original arbitrarily oriented
* principal axes. So we first inverse-rotate a coordinate axis back to a
* principal axis. We then use vector_to_angles to give us the Euler
* angles theta and phi for the principal axis. make_rotation_matrix then
* constructs a rotation matrix that rotates that principal axis to +Z.
* We then use that matrix to rotate the tensor. We then measure its
* distance from VTI, and remember that distance.
*/
/*
* First we need to find the inverse (the same as the transpose, because
* it's _unitary_) of the rotation matrix rmat.
*/
transpose_matrix (rmat_transp, rmat);
/* Test the X axis */
vec[0] = 1.;
vec[1] = 0.;
vec[2] = 0.;
matrix_times_vector (vec2, rmat_transp, vec);
vector_to_angles (vec2, &phi, &theta);
make_rotation_matrix (theta, phi, 0., rmat_temp);
rotate_tensor (ccrot2, cc, rmat_temp);
dista[0] = ti_distance (ccti, ccrot2);
/* Test the Y axis */
vec[0] = 0.;
vec[1] = 1.;
vec[2] = 0.;
matrix_times_vector (vec2, rmat_transp, vec);
vector_to_angles (vec2, &phi, &theta);
make_rotation_matrix (theta, phi, 0., rmat_temp);
rotate_tensor (ccrot2, cc, rmat_temp);
dista[1] = ti_distance (ccti, ccrot2);
/* Test the Z axis */
vec[0] = 0.;
vec[1] = 0.;
vec[2] = 1.;
matrix_times_vector (vec2, rmat_transp, vec);
vector_to_angles (vec2, &phi, &theta);
make_rotation_matrix (theta, phi, 0., rmat_temp);
rotate_tensor (ccrot2, cc, rmat_temp);
dista[2] = ti_distance (ccti, ccrot2);
/*
* See which axis best functions as a TI symmetry axis, and make that one
* the Z axis.
*/
if (dista[2] <= dista[1] && dista[2] <= dista[0])
{
/* The Z axis is already the best. No rotation needed. */
make_rotation_matrix (0., 0., 0., rmat_temp);
}
else if (dista[1] <= dista[2] && dista[1] <= dista[0])
{
/* Rotate Y to Z */
make_rotation_matrix (0., 90., 0., rmat_temp);
temp = dista[2];
dista[2] = dista[1];
dista[1] = temp;
}
else
{
/* Rotate X to Z */
make_rotation_matrix (90., 90., -90., rmat_temp);
temp = dista[2];
dista[2] = dista[0];
dista[0] = temp;
}
/*
* Accumulate this axis-relabeling rotation (rmat_temp) onto the original
* rotation (rmat).
*/
matrix_times_matrix (rmat_temp2, rmat_temp, rmat);
/*
* Now find the next-best TI symmetry axis and make that one the Y axis.
*/
if (dista[1] <= dista[0])
{
/* Already there; do nothing. */
make_rotation_matrix (0., 0., 0., rmat_temp);
}
else
{
/* Rotate X to Y */
make_rotation_matrix (90., 0., 0., rmat_temp);
temp = dista[1];
dista[1] = dista[0];
dista[0] = temp;
}
/*
* Accumulate the new axis relabeling rotation (rmat_temp) onto the
* combined previous rotation matrix (rmat_temp2) to produce the final
* desired result, rmat. The axes should now be in sorted order.
*/
matrix_times_matrix (rmat, rmat_temp, rmat_temp2);
return dist_best;
}