YAC 3.21.0
Yet Another Coupler
Loading...
Searching...
No Matches
area.c
Go to the documentation of this file.
1// Copyright (c) 2024 The YAC Authors
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5#include <stdlib.h>
6#include <stdio.h>
7#include <math.h>
8#include <float.h>
9
10#include "area.h"
11#include "geometry.h"
12#include "utils_core.h"
13#include "ensure_array_size.h"
14
15static inline double scalar_product(double a[], double b[]);
16
48static double tri_area(double const * restrict a,
49 double const * restrict b,
50 double const * restrict c) {
51// NOTE: need to turn off -O2 optimization for this function, otherwise
52// accu_product_eft calls are re-ordered and some are duplicated due to compiler
53// bug s.t. returned result is too large
54#if defined __NVCOMPILER && __NVCOMPILER_MAJOR__ == 26 && __NVCOMPILER_MINOR__ \
55 <= 5
56#pragma opt 1
57#endif
58
59 /* --------------------------------------------------
60 * Compensated cross product b x c
61 * -------------------------------------------------- */
62
63 double bc[3]; // b x c
64 double bc_e[3]; // error from computation of bc
65 det2_error(b[1], b[2], c[1], c[2], &bc[0], &bc_e[0]);
66 det2_error(b[2], b[0], c[2], c[0], &bc[1], &bc_e[1]);
67 det2_error(b[0], b[1], c[0], c[1], &bc[2], &bc_e[2]);
68
69 /* --------------------------------------------------
70 * Compensated scalar triple product
71 * T = a * (b x c)
72 * -------------------------------------------------- */
73
74 double abc[3] = // a * bc
75 {a[0]*bc[0], a[1]*bc[1], a[2]*bc[2]};
76 double abc_e[3] = // error from computation of abc
77 {fma(a[0], bc[0], -abc[0]) + a[0] * bc_e[0],
78 fma(a[1], bc[1], -abc[1]) + a[1] * bc_e[1],
79 fma(a[2], bc[2], -abc[2]) + a[2] * bc_e[2]};
80
81 double triple = 0.0, triple_e = 0.0;
82
83 accu_eft(abc_e[0], &triple, &triple_e);
84 accu_eft(abc_e[1], &triple, &triple_e);
85 accu_eft(abc_e[2], &triple, &triple_e);
86 accu_eft(abc[0], &triple, &triple_e);
87 accu_eft(abc[1], &triple, &triple_e);
88 accu_eft(abc[2], &triple, &triple_e);
89
90 /* --------------------------------------------------
91 * Compensated denominator
92 * D = 1 + a·b + b·c + c·a
93 * -------------------------------------------------- */
94
95 double D = 1.0; // denominator
96 double D_e = 0.0; // error from computing D
97
98 accu_product_eft(a[0], b[0], &D, &D_e);
99 accu_product_eft(a[1], b[1], &D, &D_e);
100 accu_product_eft(a[2], b[2], &D, &D_e);
101
102 accu_product_eft(b[0], c[0], &D, &D_e);
103 accu_product_eft(b[1], c[1], &D, &D_e);
104 accu_product_eft(b[2], c[2], &D, &D_e);
105
106 accu_product_eft(c[0], a[0], &D, &D_e);
107 accu_product_eft(c[1], a[1], &D, &D_e);
108 accu_product_eft(c[2], a[2], &D, &D_e);
109
110 /* --------------------------------------------------
111 * Signed spherical excess
112 * -------------------------------------------------- */
113
114 return 2.0 * atan2(triple + triple_e, D + D_e);
115}
116
117/* ----------------------------------- */
118
119static inline int compute_norm_vector(double a[], double b[], double norm[]) {
120
121 crossproduct_kahan(a, b, norm);
122
123 double scale = sqrt(norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]);
124
125 if (scale <= yac_angle_tol) return 1;
126
127 scale = 1.0 / scale;
128
129 norm[0] *= scale;
130 norm[1] *= scale;
131 norm[2] *= scale;
132
133 return 0;
134}
135
136static inline double XYZtoLon(double a[3]) {
137 return atan2(a[1] , a[0]);
138}
139
141 double base_vec[3], double a[3], double b[3], double * middle_lat) {
142
143 //-------------------------------------------------------------------
144 // compute the area that is enclosed between a great circle edges and
145 // a lat circle edge using that go through the points a and b
146 //-------------------------------------------------------------------
147
148 double const tol = 1e-8;
149
151 fabs(a[2]-b[2]) <= tol, "latitude of both corners is not identical")
152
153 double h = fabs(a[2]);
154
155 // if we are at the equator or at a pole -> area is zero
156 if (h < tol || fabs(1.0 - h) < tol) return 0.0;
157
158 // compute the norm vector of the plane going through a and b
159 // (if the angle between a and b is to small to compute a norm vector, then
160 // the area is negligible)
161 double norm_ab[3];
162 if (compute_norm_vector(a, b, norm_ab)) return 0.0;
163
164 // compute the area of a triangle consisting of the lat edge and
165 // the reference pole
166 double lat_area = fabs((1.0 - h) * get_angle(XYZtoLon(a), XYZtoLon(b)));
167
168 // compute the same area, but use a gc edge instead of a lat edge
169 double pole[3] = {0, 0, copysign(1.0, a[2])};
170 double gc_area = fabs(tri_area(a, b, pole));
171
172 // the difference between the two triangle areas is the area enclosed
173 // between the points a and b using a lat and a gc edge
174 double correction = MAX(lat_area - gc_area, 0.0);
175
176 //-------------------------------------------------------------------
177 // now we have to decide whether this correction needs to be added or
178 // subtracted
179 //-------------------------------------------------------------------
180
181 // compute the middle point of the lat edge
182 middle_lat[0] = a[0]+b[0];
183 middle_lat[1] = a[1]+b[1];
184 middle_lat[2] = a[2];
185 double scale = sqrt(middle_lat[0]*middle_lat[0]+middle_lat[1]*middle_lat[1]);
186 YAC_ASSERT(fabs(scale) >= 1e-18, "internal error")
187 scale = sqrt(1.0 - a[2]*a[2]) / scale;
188 middle_lat[0] *= scale;
189 middle_lat[1] *= scale;
190
191 // compute the cosine between norm vector and the base vector
192 // --> their sign indicates the ordering of vertices
193 double scalar_base = scalar_product(norm_ab, base_vec);
194
195 int negative_correction_flag;
196
197 // if the base point is on the same plane as a and b
198 if (fabs(scalar_base) < 1e-11) {
199
200 // compute the norm vector of the lat edge middle point and the associated
201 // pole
202 // (if the angle between lat edge middle point and the pols is to small to
203 // compute a norm vector, then the area is negligible)
204 double norm_middle[3];
205 if (compute_norm_vector(middle_lat, pole, norm_middle)) return 0.0;
206
207 double scalar_a = scalar_product(norm_middle, a);
208 negative_correction_flag = scalar_a <= 0;
209
210 } else {
211
212 // compute the cosine between the edge norm vector and the lat edge middle
213 // point
214 double scalar_middle_lat = scalar_product(norm_ab, middle_lat);
215 negative_correction_flag = scalar_middle_lat >= 0;
216 }
217
218 return negative_correction_flag?-correction:correction;
219}
220
222 double base_vec[3], double a[3], double b[3]) {
223
224 double middle_lat[3];
225 return lat_edge_correction_(base_vec, a, b, middle_lat);
226}
227
229 double base_vec[3], double a[3], double b[3],
230 double * barycenter, double sign) {
231
232 double middle_lat[3];
233 double correction = lat_edge_correction_(base_vec, a, b, middle_lat);
234
235 if (correction == 0.0) return 0.0;
236
237 double middle_gc[3] = {a[0] + b[0], a[1] + b[1], a[2] + b[2]};
238 normalise_vector(middle_gc);
239
240 double temp_barycenter[3] =
241 {middle_lat[0] + middle_gc[0],
242 middle_lat[1] + middle_gc[1],
243 middle_lat[2] + middle_gc[2]};
244 normalise_vector(temp_barycenter);
245
246 barycenter[0] += temp_barycenter[0] * correction * sign;
247 barycenter[1] += temp_barycenter[1] * correction * sign;
248 barycenter[2] += temp_barycenter[2] * correction * sign;
249
250 return correction * sign;
251}
252
276double yac_grid_cell_area (struct yac_grid_cell cell) {
277
278 size_t M = cell.num_corners;
279
280 if (M < 2) return 0.0;
281
282 // determine whether the cell contains at least one lat circle edge
283 int lat_flag = 0;
284 for (size_t i = 0; i < M; i++)
285 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
286
287 // if this is a basic triangle only conisting of great circle edges
288 // (e.g. ICON cell)
289 if (M == 3 && !lat_flag) {
290
291 // in case of triangle only consisting of great circle edges, return
292 // absolut value of the signed area returned by tri_area
293 return
294 fabs(
296 cell.coordinates_xyz[1],
297 cell.coordinates_xyz[2]));
298 }
299
300 // loop over all subtriangles generated by triangulation using
301 // a fan approach
302 double area = 0.0;
303 for (size_t i = 1; i < M - 1; ++i) {
304
305 // signed area summation, this ensures that this routine
306 // also works for concave cells
307 area += tri_area(cell.coordinates_xyz[0],
308 cell.coordinates_xyz[i],
309 cell.coordinates_xyz[i+1]);
310 }
311
312 // if there is at least one latitude circle edge
313 if (lat_flag) {
314
315 // loop over all edges of the polygon
316 for (size_t i = 0, j = M - 1; i < M; j = i++) {
317
318 if (cell.edge_type[j] == YAC_LAT_CIRCLE_EDGE) {
319
320 // add correction for the area difference between the great circle
321 // edge and the latitude circle edge from vertex j to vertex i
322 // (uses vertex 0 as a reference to determine area sign)
323 area += lat_edge_correction(cell.coordinates_xyz[0],
324 cell.coordinates_xyz[j],
325 cell.coordinates_xyz[i]);
326 }
327 }
328 }
329
330 return fabs(area);
331}
332
353static double tri_area_info(
354 double ref[3], double a[3], double b[3],
355 double * barycenter, double sign) {
356
357 // the barycenter of the triangle is given by the sum edge norm vector
358 // scaled by half of the associated edge length
359 // This approach is exact for spherical triangles. The weighted normalized
360 // sum of the triangle vertices works for the planar triangle.
361 double * corners[3] = {b, ref, a};
362 for (int i = 0, j = 2; i < 3; j = i++) {
363
364 double cross[3];
365 crossproduct_kahan(corners[j], corners[i], cross);
366 double sin_angle =
367 sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]);
368
369 // if the edge is too short
370 if (sin_angle < yac_angle_tol) continue;
371
372 double angle = asin(sin_angle);
373
374 double scale = 0.5 * angle / sin_angle * sign;
375 for (int k = 0; k < 3; ++k) barycenter[k] += cross[k] * scale;
376 }
377
378 // return signed area
379 return tri_area(ref, a, b) * sign;
380}
381
409 struct yac_grid_cell cell, double * barycenter, double sign) {
410
411 size_t M = cell.num_corners;
412
413 if (M < 2) return 0.0;
414
415 // determine whether the cell contains at least one lat circle edge
416 int lat_flag = 0;
417 for (size_t i = 0; i < M; i++)
418 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
419
420 // if this is a basic triangle only conisting of great circle edges
421 // (e.g. ICON cell)
422 if (M == 3 && !lat_flag) {
423
424 // in case of triangle only consisting of great circle edges, return
425 // value of the signed area returned by tri_area_info and update
426 // barycenter
427 return tri_area_info(cell.coordinates_xyz[0],
428 cell.coordinates_xyz[1],
429 cell.coordinates_xyz[2],
430 barycenter, sign);
431 }
432
433 // loop over all subtriangles generated by triangulation using
434 // a fan approach
435 double area = 0.0;
436 for (size_t i = 1; i < M - 1; ++i) {
437
438 // signed area summation, this ensures that this routine
439 // also works for concave cells
440 area +=
442 cell.coordinates_xyz[0],
443 cell.coordinates_xyz[i],
444 cell.coordinates_xyz[i+1],
445 barycenter, sign);
446 }
447
448 // if there is at least one latitude circle edge
449 if (lat_flag) {
450
451 // loop over all edges of the polygon
452 for (size_t i = 0, j = M - 1; i < M; j = i++) {
453
454 if (cell.edge_type[j] == YAC_LAT_CIRCLE_EDGE) {
455
456 // Add correction for the area difference between the great circle
457 // edge and the latitude circle edge from vertex j to vertex i
458 // (uses vertex 0 as a reference to determine area sign).
459 // Also updates the barycenter accumulator with the contribution
460 // from the latitude circle edge correction.
462 cell.coordinates_xyz[0],
463 cell.coordinates_xyz[j],
464 cell.coordinates_xyz[i],
465 barycenter, sign);
466 }
467 }
468 }
469
470 return area;
471}
472
473/* ----------------------------------- */
474
475static inline double scalar_product(double a[], double b[]) {
476 return (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
477}
478
479/* ----------------------------------- */
480
#define YAC_ASSERT(exp, msg)
static int compute_norm_vector(double a[], double b[], double norm[])
Definition area.c:119
double yac_grid_cell_area(struct yac_grid_cell cell)
Area calculation of a spherical cell.
Definition area.c:276
static double scalar_product(double a[], double b[])
Definition area.c:475
static double tri_area_info(double ref[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:353
static double lat_edge_correction_info(double base_vec[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:228
double yac_grid_cell_area_info(struct yac_grid_cell cell, double *barycenter, double sign)
Definition area.c:408
static double XYZtoLon(double a[3])
Definition area.c:136
static double lat_edge_correction_(double base_vec[3], double a[3], double b[3], double *middle_lat)
Definition area.c:140
static double lat_edge_correction(double base_vec[3], double a[3], double b[3])
Definition area.c:221
static double tri_area(double const *restrict a, double const *restrict b, double const *restrict c)
Definition area.c:48
Structs and interfaces for area calculations.
static double const tol
static void det2_error(double a, double b, double c, double d, double *restrict det, double *restrict det_error)
Definition geometry.h:391
static void crossproduct_kahan(double const a[], double const b[], double cross[])
Definition geometry.h:414
static void accu_product_eft(double a, double b, double *restrict accu, double *restrict accu_error)
Definition geometry.h:366
#define yac_angle_tol
Definition geometry.h:26
static void normalise_vector(double v[])
Definition geometry.h:754
static double get_angle(double a_lon, double b_lon)
Definition geometry.h:117
static void accu_eft(double a, double *restrict accu, double *restrict error)
Definition geometry.h:343
@ YAC_LAT_CIRCLE_EDGE
latitude circle
Definition grid_cell.h:14
size_t num_corners
Definition grid_cell.h:21
enum yac_edge_type * edge_type
Definition grid_cell.h:20
double(* coordinates_xyz)[3]
Definition grid_cell.h:19
#define MAX(a, b)