Oakfield API Documentation 1.0.0
Numerical core APIs
Loading...
Searching...
No Matches
coords.h
Go to the documentation of this file.
1
5#ifndef OAKFIELD_STIMULUS_COORDS_H
6#define OAKFIELD_STIMULUS_COORDS_H
7
8#include "oakfield/field.h"
10
11#include <math.h>
12#include <stdbool.h>
13
14#ifdef __cplusplus
15extern "C" {
16#endif
17
18#define STIMULUS_COORD_EPS 1.0e-12
19
33
41
49
73
77typedef struct SimStimulusCoordRow {
78 size_t x0;
79 size_t y0;
80 size_t width;
81 double x;
82 double y;
83 double x_step;
84 double y_step;
85 double sample_x;
86 double sample_y;
90
104static inline void sim_stimulus_coord_sample_xy(const SimStimulusCoordConfig *coord, double x,
105 double y, double t, double *out_x, double *out_y) {
106 if (out_x == NULL || out_y == NULL) {
107 return;
108 }
109
110 double sample_x = x;
111 double sample_y = y;
112 if (coord != NULL) {
113 sample_x -= coord->velocity_x * t;
114 sample_y -= coord->velocity_y * t;
115 }
116
117 *out_x = sample_x;
118 *out_y = sample_y;
119}
120
134static inline void sim_stimulus_coord_centered_xy(const SimStimulusCoordConfig *coord, double x,
135 double y, double t, double *out_dx,
136 double *out_dy) {
137 if (out_dx == NULL || out_dy == NULL) {
138 return;
139 }
140
141 double sample_x = x;
142 double sample_y = y;
143 double cx = 0.0;
144 double cy = 0.0;
145 if (coord != NULL) {
146 sim_stimulus_coord_sample_xy(coord, x, y, t, &sample_x, &sample_y);
147 cx = coord->center_x;
148 cy = coord->center_y;
149 }
150
151 *out_dx = sample_x - cx;
152 *out_dy = sample_y - cy;
153}
154
168static inline void sim_stimulus_coord_rotate_xy(double x, double y, double angle, double *out_u,
169 double *out_v) {
170 if (out_u == NULL || out_v == NULL) {
171 return;
172 }
173
174 double s = sin(angle);
175 double c = cos(angle);
176 *out_u = x * c + y * s;
177 *out_v = -x * s + y * c;
178}
179
192static inline void sim_stimulus_coord_elliptic_local(const SimStimulusCoordConfig *coord, double dx,
193 double dy, double *out_u, double *out_v) {
194 if (out_u == NULL || out_v == NULL) {
195 return;
196 }
197
198 double ur = dx;
199 double vr = dy;
200 double au = 1.0;
201 double av = 1.0;
202
203 if (coord != NULL) {
204 sim_stimulus_coord_rotate_xy(dx, dy, coord->angle, &ur, &vr);
205 au = coord->ellipse_u;
206 av = coord->ellipse_v;
207 }
208
209 if (fabs(au) <= STIMULUS_COORD_EPS) {
210 au = 1.0;
211 }
212 if (fabs(av) <= STIMULUS_COORD_EPS) {
213 av = 1.0;
214 }
215
216 *out_u = ur / au;
217 *out_v = vr / av;
218}
219
232static inline void sim_stimulus_coord_elliptic_polar(const SimStimulusCoordConfig *coord, double dx,
233 double dy, double *out_r, double *out_theta) {
234 double u = 0.0;
235 double v = 0.0;
236
237 sim_stimulus_coord_elliptic_local(coord, dx, dy, &u, &v);
238
239 if (out_r != NULL) {
240 *out_r = hypot(u, v);
241 }
242 if (out_theta != NULL) {
243 *out_theta = atan2(v, u);
244 }
245}
246
260static inline void sim_stimulus_coord_polar(const SimStimulusCoordConfig *coord, double x, double y,
261 double t, double *out_r, double *out_theta) {
262 double dx = 0.0;
263 double dy = 0.0;
264
265 sim_stimulus_coord_centered_xy(coord, x, y, t, &dx, &dy);
266
267 if (out_r != NULL) {
268 *out_r = hypot(dx, dy);
269 }
270 if (out_theta != NULL) {
271 *out_theta = atan2(dy, dx);
272 }
273}
274
284static inline void sim_stimulus_coord_normalize(SimStimulusCoordConfig *coord) {
285 if (coord == NULL) {
286 return;
287 }
290 }
291 if (coord->axis != SIM_STIMULUS_AXIS_X && coord->axis != SIM_STIMULUS_AXIS_Y) {
292 coord->axis = SIM_STIMULUS_AXIS_X;
293 }
297 }
298 if (!isfinite(coord->angle)) {
299 coord->angle = 0.0;
300 }
301 if (!isfinite(coord->origin_x)) {
302 coord->origin_x = 0.0;
303 }
304 if (!isfinite(coord->origin_y)) {
305 coord->origin_y = 0.0;
306 }
307 if (!isfinite(coord->spacing_x) || fabs(coord->spacing_x) <= STIMULUS_COORD_EPS) {
308 coord->spacing_x = 1.0;
309 }
310 if (!isfinite(coord->spacing_y) || fabs(coord->spacing_y) <= STIMULUS_COORD_EPS) {
311 coord->spacing_y = coord->spacing_x;
312 }
313 if (!isfinite(coord->center_x)) {
314 coord->center_x = 0.0;
315 }
316 if (!isfinite(coord->center_y)) {
317 coord->center_y = 0.0;
318 }
319 if (!isfinite(coord->velocity_x)) {
320 coord->velocity_x = 0.0;
321 }
322 if (!isfinite(coord->velocity_y)) {
323 coord->velocity_y = 0.0;
324 }
325 if (!isfinite(coord->ellipse_u) || fabs(coord->ellipse_u) <= STIMULUS_COORD_EPS) {
326 coord->ellipse_u = 1.0;
327 }
328 coord->ellipse_u = fabs(coord->ellipse_u);
329 if (!isfinite(coord->ellipse_v) || fabs(coord->ellipse_v) <= STIMULUS_COORD_EPS) {
330 coord->ellipse_v = coord->ellipse_u;
331 }
332 coord->ellipse_v = fabs(coord->ellipse_v);
333 if (!isfinite(coord->spiral_arms)) {
334 coord->spiral_arms = 1.0;
335 }
336 if (!isfinite(coord->spiral_pitch)) {
337 coord->spiral_pitch = 1.0;
338 }
339 if (!isfinite(coord->spiral_phase)) {
340 coord->spiral_phase = 0.0;
341 }
342 if (!isfinite(coord->spiral_angular_velocity)) {
343 coord->spiral_angular_velocity = 0.0;
344 }
345 if (coord->mode == SIM_STIMULUS_COORD_SPIRAL &&
346 fabs(coord->spiral_arms) <= STIMULUS_COORD_EPS &&
347 fabs(coord->spiral_pitch) <= STIMULUS_COORD_EPS) {
348 coord->spiral_arms = 1.0;
349 coord->spiral_pitch = 1.0;
350 }
351}
352
362static inline bool sim_stimulus_coord_is_time_invariant(const SimStimulusCoordConfig *coord) {
363 if (coord == NULL) {
364 return true;
365 }
366 if (fabs(coord->velocity_x) > STIMULUS_COORD_EPS ||
367 fabs(coord->velocity_y) > STIMULUS_COORD_EPS) {
368 return false;
369 }
370 if (coord->mode == SIM_STIMULUS_COORD_SPIRAL &&
371 fabs(coord->spiral_angular_velocity) > STIMULUS_COORD_EPS) {
372 return false;
373 }
374 return true;
375}
376
390static inline SimResult sim_stimulus_coord_xy_at_indices(const SimStimulusCoordConfig *coord,
391 size_t ix, size_t iy, double *out_x,
392 double *out_y) {
393 if (coord == NULL || out_x == NULL || out_y == NULL) {
395 }
396
397 *out_x = coord->origin_x + (double)ix * coord->spacing_x;
398 *out_y = coord->origin_y + (double)iy * coord->spacing_y;
399 return SIM_RESULT_OK;
400}
401
415static inline SimResult sim_stimulus_coord_patch_row(const SimStimulusCoordConfig *coord,
416 const SimFieldPatch *patch, size_t row_offset,
417 double t, SimStimulusCoordRow *out_row) {
418 SimStimulusCoordRow row = {0};
419
420 if (coord == NULL || !sim_field_patch_is_valid(patch) || out_row == NULL ||
421 row_offset >= patch->height) {
423 }
424
425 row.x0 = patch->x0;
426 row.y0 = patch->y0 + row_offset;
427 row.width = patch->width;
428 row.x_step = coord->spacing_x;
429 row.y_step = 0.0;
430 row.sample_x_step = row.x_step;
431 row.sample_y_step = row.y_step;
432
433 if (sim_stimulus_coord_xy_at_indices(coord, row.x0, row.y0, &row.x, &row.y) != SIM_RESULT_OK) {
435 }
436 sim_stimulus_coord_sample_xy(coord, row.x, row.y, t, &row.sample_x, &row.sample_y);
437 *out_row = row;
438 return SIM_RESULT_OK;
439}
440
455static inline SimResult sim_stimulus_coord_xy(const SimStimulusCoordConfig *coord,
456 const SimField *field, size_t index, double *out_x,
457 double *out_y) {
458 size_t ix = 0U;
459 size_t iy = 0U;
461
462 if (coord == NULL || field == NULL || out_x == NULL || out_y == NULL) {
464 }
465
466 rc = sim_field_index_to_xy(field, index, &ix, &iy);
467 if (rc != SIM_RESULT_OK) {
468 return rc;
469 }
470
471 return sim_stimulus_coord_xy_at_indices(coord, ix, iy, out_x, out_y);
472}
473
487static inline double sim_stimulus_coord_u(const SimStimulusCoordConfig *coord, double x, double y,
488 double t) {
489 if (coord == NULL) {
490 return x;
491 }
492 double sample_x = x;
493 double sample_y = y;
494 sim_stimulus_coord_sample_xy(coord, x, y, t, &sample_x, &sample_y);
495 switch (coord->mode) {
497 return (coord->axis == SIM_STIMULUS_AXIS_Y) ? sample_y : sample_x;
499 double u = 0.0;
500 double v = 0.0;
501 sim_stimulus_coord_rotate_xy(sample_x, sample_y, coord->angle, &u, &v);
502 return u;
503 }
505 double dx = 0.0;
506 double dy = 0.0;
507 sim_stimulus_coord_centered_xy(coord, x, y, t, &dx, &dy);
508 return hypot(dx, dy);
509 }
511 double r = 0.0;
512 sim_stimulus_coord_polar(coord, x, y, t, &r, NULL);
513 return r;
514 }
516 double dx = 0.0;
517 double dy = 0.0;
518 sim_stimulus_coord_centered_xy(coord, x, y, t, &dx, &dy);
519 return atan2(dy, dx);
520 }
522 double dx = 0.0;
523 double dy = 0.0;
524 double r = 0.0;
525 sim_stimulus_coord_centered_xy(coord, x, y, t, &dx, &dy);
526 sim_stimulus_coord_elliptic_polar(coord, dx, dy, &r, NULL);
527 return r;
528 }
530 double dx = 0.0;
531 double dy = 0.0;
532 sim_stimulus_coord_centered_xy(coord, x, y, t, &dx, &dy);
533 double r = hypot(dx, dy);
534 double theta = atan2(dy, dx);
535 return coord->spiral_pitch * r + coord->spiral_arms * theta + coord->spiral_phase +
536 coord->spiral_angular_velocity * t;
537 }
539 default:
540 return sample_x;
541 }
542}
543
544#ifdef __cplusplus
545}
546#endif
547
548#endif /* OAKFIELD_STIMULUS_COORDS_H */
SimStimulusSeparableMode
Combination mode for separable X/Y stimulus coordinates.
Definition coords.h:45
@ SIM_STIMULUS_SEPARABLE_ADD
Definition coords.h:47
@ SIM_STIMULUS_SEPARABLE_MULTIPLY
Definition coords.h:46
SimStimulusCoordAxis
Cartesian axis selector for stimulus coordinates.
Definition coords.h:37
@ SIM_STIMULUS_AXIS_Y
Definition coords.h:39
@ SIM_STIMULUS_AXIS_X
Definition coords.h:38
SimStimulusCoordMode
Spatial coordinate mappings shared by stimulus operators.
Definition coords.h:23
@ SIM_STIMULUS_COORD_RADIAL
Definition coords.h:26
@ SIM_STIMULUS_COORD_AXIS
Definition coords.h:24
@ SIM_STIMULUS_COORD_SPIRAL
Definition coords.h:31
@ SIM_STIMULUS_COORD_ELLIPTIC
Definition coords.h:29
@ SIM_STIMULUS_COORD_AZIMUTH
Definition coords.h:28
@ SIM_STIMULUS_COORD_SEPARABLE
Definition coords.h:30
@ SIM_STIMULUS_COORD_ANGLE
Definition coords.h:25
@ SIM_STIMULUS_COORD_POLAR
Definition coords.h:27
Multidimensional contiguous field abstraction with configurable layout.
SimResult sim_field_index_to_xy(const SimField *field, size_t index, size_t *out_x, size_t *out_y)
Convert a linear element index into 2D coordinates.
SimResult
Return codes shared by libsimcore modules.
Definition field.h:29
@ SIM_RESULT_OK
Definition field.h:30
@ SIM_RESULT_INVALID_ARGUMENT
Definition field.h:31
Shared index-space field patch descriptors and zero-copy patch views.
bool sim_field_patch_is_valid(const SimFieldPatch *patch)
Validate patch bounds, dimensions, and source-field extents.
Public special-function entry points, including Zeta/Xi and q-method helpers.
Rectangular non-empty region of a 2D field index space.
Definition field_patch.h:32
size_t width
Definition field_patch.h:35
size_t height
Definition field_patch.h:36
size_t x0
Definition field_patch.h:33
size_t y0
Definition field_patch.h:34
Owning multidimensional field.
Definition field.h:157
Shared coordinate mapping configuration used by stimulus operators.
Definition coords.h:53
double velocity_y
Definition coords.h:65
double spacing_y
Definition coords.h:61
double center_y
Definition coords.h:63
double angle
Definition coords.h:57
double spiral_phase
Definition coords.h:70
SimStimulusSeparableMode combine
Definition coords.h:56
double origin_x
Definition coords.h:58
double spiral_pitch
Definition coords.h:69
SimStimulusCoordMode mode
Definition coords.h:54
double ellipse_u
Definition coords.h:66
double spiral_angular_velocity
Definition coords.h:71
double velocity_x
Definition coords.h:64
double origin_y
Definition coords.h:59
double spiral_arms
Definition coords.h:68
double spacing_x
Definition coords.h:60
SimStimulusCoordAxis axis
Definition coords.h:55
double ellipse_v
Definition coords.h:67
double center_x
Definition coords.h:62
Precomputed coordinate row state for efficient stimulus patch iteration.
Definition coords.h:77
double y_step
Definition coords.h:84
double sample_y_step
Definition coords.h:88
double y
Definition coords.h:82
double sample_y
Definition coords.h:86
double sample_x
Definition coords.h:85
double x_step
Definition coords.h:83
double sample_x_step
Definition coords.h:87
size_t x0
Definition coords.h:78
size_t width
Definition coords.h:80
size_t y0
Definition coords.h:79
double x
Definition coords.h:81