Skip to content

Commit 6eb621f

Browse files
koldunovnclaude
andcommitted
Add velocity rotation for MITgcm LLC/cubed-sphere grids
Automatically rotate UVEL/VVEL from local grid coordinates to geographic (eastward/northward) when AngleCS/AngleSN (or CS/SN) grid files are present. Paired velocity fields are detected by matching names and rotated via u_east = CS*U - SN*V, v_north = SN*U + CS*V. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d92d685 commit 6eb621f

File tree

3 files changed

+370
-2
lines changed

3 files changed

+370
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ The first visit to each new depth level builds a masked interpolation (timing de
402402
- Automatic land masking via hFacC grid file
403403
- Depth levels from RC grid file
404404
- Multi-timestep iteration discovery
405-
- Note: velocity fields (UVEL/VVEL) are shown in local grid coordinates on LLC/cubed-sphere grids; geographic rotation requires AngleCS/AngleSN grid files (not yet implemented)
405+
- Automatic velocity rotation on LLC/cubed-sphere grids: if AngleCS/AngleSN (or CS/SN) grid files are present, UVEL/VVEL and other paired velocity fields are rotated from local grid coordinates to geographic (eastward/northward) components
406406
- **Zarr**: v2 format (requires `make WITH_ZARR=1`)
407407
- Compression: LZ4, Blosc (with various inner codecs), or uncompressed
408408
- Data types: Float32, Float64, Int64

src/file_mitgcm.c

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ typedef struct {
3535
int nx, ny, nz;
3636
float *depth_values; /* RC[nz] */
3737
float *hfac; /* hFacC[nz*ny*nx], NULL if unavailable */
38+
float *angle_cs; /* AngleCS[ny*nx], NULL if unavailable */
39+
float *angle_sn; /* AngleSN[ny*nx], NULL if unavailable */
3840
float missing_value;
3941
} MitgcmStore;
4042

@@ -47,6 +49,8 @@ typedef struct {
4749
int nx, ny, nz;
4850
float missing_value;
4951
int has_missing;
52+
int velocity_component; /* 0=none, 1=U-component, 2=V-component */
53+
USVar *paired_velocity; /* paired U/V variable, NULL if unpaired */
5054
} MitgcmVarData;
5155

5256
/* ---- Byte swapping ---- */
@@ -435,9 +439,38 @@ USFile *mitgcm_open(const char *path) {
435439
}
436440
}
437441

442+
/* Load AngleCS/AngleSN (or CS/SN) for velocity rotation */
443+
{
444+
size_t grid_size = (size_t)ny * nx;
445+
char cs_path[MAX_NAME_LEN], sn_path[MAX_NAME_LEN];
446+
447+
/* Try AngleCS.data / AngleSN.data first */
448+
snprintf(cs_path, sizeof(cs_path), "%s/AngleCS.data", directory);
449+
snprintf(sn_path, sizeof(sn_path), "%s/AngleSN.data", directory);
450+
if (!file_exists(cs_path) || !file_exists(sn_path)) {
451+
/* Fall back to CS.data / SN.data */
452+
snprintf(cs_path, sizeof(cs_path), "%s/CS.data", directory);
453+
snprintf(sn_path, sizeof(sn_path), "%s/SN.data", directory);
454+
}
455+
456+
if (file_exists(cs_path) && file_exists(sn_path)) {
457+
store->angle_cs = read_binary_floats(cs_path, 0, grid_size);
458+
store->angle_sn = read_binary_floats(sn_path, 0, grid_size);
459+
if (store->angle_cs && store->angle_sn) {
460+
printf("MITgcm: loaded AngleCS/AngleSN for velocity rotation\n");
461+
} else {
462+
/* Both must succeed */
463+
free(store->angle_cs); store->angle_cs = NULL;
464+
free(store->angle_sn); store->angle_sn = NULL;
465+
}
466+
}
467+
}
468+
438469
/* Create USFile */
439470
USFile *f = calloc(1, sizeof(USFile));
440-
if (!f) { free(store->depth_values); free(store->hfac); free(store); return NULL; }
471+
if (!f) { free(store->depth_values); free(store->hfac);
472+
free(store->angle_cs); free(store->angle_sn);
473+
free(store); return NULL; }
441474

442475
f->file_type = FILE_TYPE_MITGCM;
443476
strncpy(f->filename, directory, MAX_NAME_LEN - 1);
@@ -723,6 +756,34 @@ USVar *mitgcm_scan_variables(USFile *f, USMesh *m) {
723756
free(iterations);
724757
}
725758

759+
/* Detect velocity components and pair U/V variables */
760+
if (store->angle_cs) {
761+
/* Mark velocity components by name */
762+
for (USVar *v = head; v; v = v->next) {
763+
MitgcmVarData *d = (MitgcmVarData *)v->mitgcm_data;
764+
if (v->name[0] == 'U') d->velocity_component = 1;
765+
else if (v->name[0] == 'V') d->velocity_component = 2;
766+
}
767+
768+
/* Pair U/V variables: same prefix, name differs only in first char */
769+
for (USVar *u = head; u; u = u->next) {
770+
MitgcmVarData *ud = (MitgcmVarData *)u->mitgcm_data;
771+
if (ud->velocity_component != 1 || ud->paired_velocity) continue;
772+
773+
for (USVar *v = head; v; v = v->next) {
774+
MitgcmVarData *vvd = (MitgcmVarData *)v->mitgcm_data;
775+
if (vvd->velocity_component != 2 || vvd->paired_velocity) continue;
776+
if (strcmp(ud->prefix, vvd->prefix) != 0) continue;
777+
if (strcmp(u->name + 1, v->name + 1) != 0) continue;
778+
779+
ud->paired_velocity = v;
780+
vvd->paired_velocity = u;
781+
printf("MITgcm: paired %s / %s for rotation\n", u->name, v->name);
782+
break;
783+
}
784+
}
785+
}
786+
726787
f->vars = head;
727788
f->n_vars = n_vars;
728789
printf("MITgcm: found %d variables from %d diagnostic groups\n", n_vars, n_prefixes);
@@ -782,6 +843,46 @@ int mitgcm_read_slice(USVar *var, size_t time_idx, size_t depth_idx, float *data
782843
swap_endian_float(data, slab_size);
783844
}
784845

846+
/* Apply CS/SN velocity rotation */
847+
if (store->angle_cs && vd->velocity_component != 0 && vd->paired_velocity) {
848+
MitgcmVarData *pvd = (MitgcmVarData *)vd->paired_velocity->mitgcm_data;
849+
850+
/* Compute offset for the paired component in the same data file */
851+
size_t p_field_size = pvd->is_3d ? (size_t)pvd->nz * slab_size : slab_size;
852+
size_t p_offset = (size_t)pvd->field_index * p_field_size;
853+
if (pvd->is_3d) p_offset += depth_idx * slab_size;
854+
p_offset *= sizeof(float);
855+
856+
float *other = malloc(slab_size * sizeof(float));
857+
if (other) {
858+
FILE *fp2 = fopen(data_path, "rb");
859+
if (fp2) {
860+
fseek(fp2, (long)p_offset, SEEK_SET);
861+
size_t pr = fread(other, sizeof(float), slab_size, fp2);
862+
fclose(fp2);
863+
864+
if (pr == slab_size) {
865+
if (is_little_endian()) swap_endian_float(other, slab_size);
866+
867+
if (vd->velocity_component == 1) {
868+
/* This is U, other is V: u_east = CS*U - SN*V */
869+
for (size_t i = 0; i < slab_size; i++) {
870+
float u = data[i], v = other[i];
871+
data[i] = store->angle_cs[i] * u - store->angle_sn[i] * v;
872+
}
873+
} else {
874+
/* This is V, other is U: v_north = SN*U + CS*V */
875+
for (size_t i = 0; i < slab_size; i++) {
876+
float u = other[i], v = data[i];
877+
data[i] = store->angle_sn[i] * u + store->angle_cs[i] * v;
878+
}
879+
}
880+
}
881+
}
882+
free(other);
883+
}
884+
}
885+
785886
/* Apply hFacC land masking */
786887
if (store->hfac && vd->is_3d) {
787888
float *mask = store->hfac + depth_idx * slab_size;
@@ -976,6 +1077,32 @@ int mitgcm_read_timeseries(USVar *var, size_t node_idx, size_t depth_idx,
9761077
if (fread(&val, sizeof(float), 1, fp) == 1) {
9771078
if (is_little_endian()) swap_endian_float(&val, 1);
9781079

1080+
/* Apply CS/SN velocity rotation */
1081+
if (store->angle_cs && vd->velocity_component != 0 && vd->paired_velocity) {
1082+
MitgcmVarData *pvd = (MitgcmVarData *)vd->paired_velocity->mitgcm_data;
1083+
size_t p_field_size = pvd->is_3d ? (size_t)pvd->nz * slab_size : slab_size;
1084+
size_t p_off = (size_t)pvd->field_index * p_field_size;
1085+
if (pvd->is_3d) p_off += depth_idx * slab_size;
1086+
p_off += node_idx;
1087+
p_off *= sizeof(float);
1088+
1089+
float other_val;
1090+
fseek(fp, (long)p_off, SEEK_SET);
1091+
if (fread(&other_val, sizeof(float), 1, fp) == 1) {
1092+
if (is_little_endian()) swap_endian_float(&other_val, 1);
1093+
if (vd->velocity_component == 1) {
1094+
/* U: u_east = CS*U - SN*V */
1095+
val = store->angle_cs[node_idx] * val
1096+
- store->angle_sn[node_idx] * other_val;
1097+
} else {
1098+
/* V: v_north = SN*U + CS*V */
1099+
float u = other_val, v = val;
1100+
val = store->angle_sn[node_idx] * u
1101+
+ store->angle_cs[node_idx] * v;
1102+
}
1103+
}
1104+
}
1105+
9791106
/* Check hFacC mask */
9801107
int masked = 0;
9811108
if (store->hfac && vd->is_3d) {
@@ -1014,6 +1141,8 @@ void mitgcm_close(USFile *file) {
10141141
MitgcmStore *store = (MitgcmStore *)file->mitgcm_data;
10151142
free(store->depth_values);
10161143
free(store->hfac);
1144+
free(store->angle_cs);
1145+
free(store->angle_sn);
10171146
free(store);
10181147
}
10191148

0 commit comments

Comments
 (0)