|
| 1 | +import collections |
| 2 | + |
| 3 | +import numpy as np |
| 4 | + |
| 5 | +from mpas_tools.mesh.creation.util import circumcenter |
| 6 | + |
| 7 | +point = collections.namedtuple('Point', ['x', 'y', 'z']) |
| 8 | + |
| 9 | + |
| 10 | +def test_circumcenter_planar(): |
| 11 | + # List of test cases: (cell0, cell1, cell2, expected) |
| 12 | + test_cases = [ |
| 13 | + # From a regular hex mesh |
| 14 | + ( |
| 15 | + (0.0, 17320.50807569, 0.0), |
| 16 | + (10000.0, 17320.50807569, 0.0), |
| 17 | + (5000.0, 25980.76211353, 0.0), |
| 18 | + (5000.0, 20207.25942164, 0.0), |
| 19 | + ), |
| 20 | + # From an Antarctic mesh |
| 21 | + ( |
| 22 | + (2194652.05105505, 1144816.29348609, 0.0), |
| 23 | + (2198726.62725077, 1145337.65538494, 0.0), |
| 24 | + (2195474.22967839, 1148983.95766333, 0.0), |
| 25 | + (2196492.12915069, 1146618.22089598, 0.0), |
| 26 | + ), |
| 27 | + # From an Antarctic mesh |
| 28 | + ( |
| 29 | + (2195474.22967839, 1148983.95766333, 0.0), |
| 30 | + (2198726.62725077, 1145337.65538494, 0.0), |
| 31 | + (2199279.33328257, 1149240.68069275, 0.0), |
| 32 | + (2197485.28573298, 1147504.08822421, 0.0), |
| 33 | + ), |
| 34 | + # From an Antarctic mesh |
| 35 | + ( |
| 36 | + (2150629.6663189, 1441451.4899963, 0.0), |
| 37 | + (2149349.39244275, 1445285.59533973, 0.0), |
| 38 | + (2146912.55160507, 1442187.732174, 0.0), |
| 39 | + (2149013.33940856, 1443042.57600619, 0.0), |
| 40 | + ), |
| 41 | + # From an Antarctic mesh |
| 42 | + ( |
| 43 | + (2570868.91779026, -97880.09977304, 0.0), |
| 44 | + (2575332.71614832, -97980.66509542, 0.0), |
| 45 | + (2573585.71564672, -95138.63592864, 0.0), |
| 46 | + (2573113.05578246, -97387.13758518, 0.0), |
| 47 | + ), |
| 48 | + ] |
| 49 | + |
| 50 | + for i, (cell0, cell1, cell2, expected) in enumerate(test_cases): |
| 51 | + x1, y1, z1 = cell0 |
| 52 | + x2, y2, z2 = cell1 |
| 53 | + x3, y3, z3 = cell2 |
| 54 | + |
| 55 | + xv, yv, zv = circumcenter(False, x1, y1, z1, x2, y2, z2, x3, y3, z3) |
| 56 | + assert np.allclose([xv, yv, zv], expected, atol=1e-8), ( |
| 57 | + f'Planar circumcenter failed for case {i}: got {[xv, yv, zv]}, ' |
| 58 | + f'expected {expected}' |
| 59 | + ) |
| 60 | + |
| 61 | + xv_old, yv_old, zv_old = _old_circumcenter( |
| 62 | + False, x1, y1, z1, x2, y2, z2, x3, y3, z3 |
| 63 | + ) |
| 64 | + print(f'Planar circumcenter (case {i}):') |
| 65 | + print(' circumcenter: ', (xv, yv, zv)) |
| 66 | + print(' _old_circumcenter: ', (xv_old, yv_old, zv_old)) |
| 67 | + print(' difference: ', (xv - xv_old, yv - yv_old, zv - zv_old)) |
| 68 | + assert np.array_equal([xv, yv, zv], [xv_old, yv_old, zv_old]), ( |
| 69 | + f'circumcenter and _old_circumcenter results differ in case {i}: ' |
| 70 | + f'({xv}, {yv}, {zv}) != ({xv_old}, {yv_old}, {zv_old})' |
| 71 | + ) |
| 72 | + |
| 73 | + |
| 74 | +def test_circumcenter_sphere(): |
| 75 | + # Use provided cell coordinates and vertex as expected output |
| 76 | + x1, y1, z1 = -3590.95704026, -684.52527287, -6371218.95125671 |
| 77 | + x2, y2, z2 = -9926.94769501, 18734.26792821, -6371184.72274307 |
| 78 | + x3, y3, z3 = 12921.92507847, 11340.69768405, -6371196.8028643 |
| 79 | + expected = (296.83807146, 11327.05862805, -6371209.92418473) |
| 80 | + |
| 81 | + xv, yv, zv = circumcenter(True, x1, y1, z1, x2, y2, z2, x3, y3, z3) |
| 82 | + assert np.allclose([xv, yv, zv], expected, atol=1e-8), ( |
| 83 | + f'Spherical circumcenter failed: got {[xv, yv, zv]}, expected ' |
| 84 | + f'{expected}' |
| 85 | + ) |
| 86 | + |
| 87 | + xv_old, yv_old, zv_old = _old_circumcenter( |
| 88 | + True, x1, y1, z1, x2, y2, z2, x3, y3, z3 |
| 89 | + ) |
| 90 | + # Print both sets and their difference |
| 91 | + print('Spherical circumcenter:') |
| 92 | + print(' circumcenter: ', (xv, yv, zv)) |
| 93 | + print(' _old_circumcenter: ', (xv_old, yv_old, zv_old)) |
| 94 | + print(' difference: ', (xv - xv_old, yv - yv_old, zv - zv_old)) |
| 95 | + # Check for exact equality |
| 96 | + assert np.array_equal([xv, yv, zv], [xv_old, yv_old, zv_old]), ( |
| 97 | + f'circumcenter and _old_circumcenter results differ: ' |
| 98 | + f'({xv}, {yv}, {zv}) != ({xv_old}, {yv_old}, {zv_old})' |
| 99 | + ) |
| 100 | + |
| 101 | + |
| 102 | +def _old_circumcenter(on_sphere, x1, y1, z1, x2, y2, z2, x3, y3, z3): |
| 103 | + """ |
| 104 | + Compute the circumcenter of the triangle (possibly on a sphere) |
| 105 | + with the three given vertices in Cartesian coordinates. |
| 106 | +
|
| 107 | + Returns |
| 108 | + ------- |
| 109 | + center : point |
| 110 | + The circumcenter of the triangle with x, y and z attributes |
| 111 | +
|
| 112 | + """ |
| 113 | + p1 = point(x1, y1, z1) |
| 114 | + p2 = point(x2, y2, z2) |
| 115 | + p3 = point(x3, y3, z3) |
| 116 | + if on_sphere: |
| 117 | + a = (p2.x - p3.x) ** 2 + (p2.y - p3.y) ** 2 + (p2.z - p3.z) ** 2 |
| 118 | + b = (p3.x - p1.x) ** 2 + (p3.y - p1.y) ** 2 + (p3.z - p1.z) ** 2 |
| 119 | + c = (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2 + (p1.z - p2.z) ** 2 |
| 120 | + |
| 121 | + pbc = a * (-a + b + c) |
| 122 | + apc = b * (a - b + c) |
| 123 | + abp = c * (a + b - c) |
| 124 | + |
| 125 | + xv = (pbc * p1.x + apc * p2.x + abp * p3.x) / (pbc + apc + abp) |
| 126 | + yv = (pbc * p1.y + apc * p2.y + abp * p3.y) / (pbc + apc + abp) |
| 127 | + zv = (pbc * p1.z + apc * p2.z + abp * p3.z) / (pbc + apc + abp) |
| 128 | + else: |
| 129 | + d = 2 * ( |
| 130 | + p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y) |
| 131 | + ) |
| 132 | + |
| 133 | + xv = ( |
| 134 | + (p1.x**2 + p1.y**2) * (p2.y - p3.y) |
| 135 | + + (p2.x**2 + p2.y**2) * (p3.y - p1.y) |
| 136 | + + (p3.x**2 + p3.y**2) * (p1.y - p2.y) |
| 137 | + ) / d |
| 138 | + yv = ( |
| 139 | + (p1.x**2 + p1.y**2) * (p3.x - p2.x) |
| 140 | + + (p2.x**2 + p2.y**2) * (p1.x - p3.x) |
| 141 | + + (p3.x**2 + p3.y**2) * (p2.x - p1.x) |
| 142 | + ) / d |
| 143 | + zv = 0.0 |
| 144 | + |
| 145 | + # Optional method to use barycenter instead. |
| 146 | + # xv = p1.x + p2.x + p3.x |
| 147 | + # xv = xv / 3.0 |
| 148 | + # yv = p1.y + p2.y + p3.y |
| 149 | + # yv = yv / 3.0 |
| 150 | + return point(xv, yv, zv) |
0 commit comments