Skip to content

Commit 063b02e

Browse files
authored
Merge branch 'copilot/fix-7569' (PR #7570)
This PR implements a field aliasing feature that allows users to specify custom variable names in netcdf output while maintaining the original internal field names in EAMxx This addresses the need for more user-friendly and tool-compatible variable names in model output. [BFB]
2 parents 644f4de + 836aae0 commit 063b02e

File tree

8 files changed

+549
-44
lines changed

8 files changed

+549
-44
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# EAMxx Field Aliasing Feature
2+
3+
This document demonstrates the field aliasing feature for EAMxx I/O operations.
4+
5+
## Overview
6+
7+
The field aliasing feature allows users to specify custom names for
8+
variables in netcdf output files while maintaining the original
9+
internal field names in the model. This is useful for:
10+
11+
- Creating shorter, more convenient variable names for output
12+
- Maintaining compatibility with external tools that expect specific variable names
13+
- Providing user-friendly names for complex internal field names
14+
15+
## Syntax
16+
17+
The alias syntax uses the delimiter `:=` to separate the alias name
18+
from the internal field name:
19+
20+
```yaml
21+
alias_name:=internal_field_name
22+
```
23+
24+
## YAML Configuration Examples
25+
26+
### Basic Usage
27+
28+
```yaml
29+
field_names:
30+
- "LWP:=LiqWaterPath" # Alias LWP for LiqWaterPath
31+
- "SWP:=SolidWaterPath" # Alias SWP for SolidWaterPath
32+
- "T:=T_mid" # Alias T for T_mid
33+
- "qv" # Regular field name (no alias)
34+
```
35+
36+
### Mixed Usage
37+
38+
You can mix aliased and non-aliased fields in the same configuration:
39+
40+
```yaml
41+
field_names:
42+
- "T_mid" # Regular field name
43+
- "LWP:=LiqWaterPath" # Aliased field
44+
- "p_mid" # Regular field name
45+
- "RH:=RelativeHumidity" # Aliased field
46+
```
47+
48+
## Output Behavior
49+
50+
When using aliases:
51+
52+
1. **NetCDF Variables**: The netcdf file will contain variables
53+
named according to the aliases
54+
55+
- `LWP` instead of `LiqWaterPath`
56+
- `T` instead of `T_mid`
57+
- `RH` instead of `RelativeHumidity`
58+
59+
2. **Internal Processing**: All internal model operations use the
60+
original field names
61+
62+
- Field validation uses `LiqWaterPath`, `T_mid`, etc.
63+
- Diagnostic calculations use original names
64+
- Memory management uses original field structures
65+
66+
3. **Metadata**: Variable attributes (units, long_name, etc.)
67+
are preserved from the original fields, and `eamxx_name`
68+
is added to the netcdf files to document aliasing
69+
70+
## Caveats
71+
72+
Currently, a field can be requested only once in a single stream,
73+
and either the original name or the alias name counts.
74+
75+
```yaml
76+
field_names:
77+
- "LWP:=" # Error: empty field name
78+
- ":=LiqWaterPath" # Error: empty alias name
79+
- "LWP:=Field1" # OK
80+
- "LWP:=Field2" # Error: duplicate alias LWP
81+
- "LWP1:=LiqWaterPath" # OK
82+
- "LWP2:=LiqWaterPath" # Error: duplicate field LiqWaterPath
83+
```

components/eamxx/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ nav:
2020
- 'Field contraction diagnostics': 'user/diags/field_contraction.md'
2121
- 'Conditional sampling': 'user/diags/conditional_sampling.md'
2222
- 'Presentations': 'user/presentations.md'
23+
- 'IO Aliases': 'user/io_aliases.md'
2324
- 'Developer Guide':
2425
- 'Quick-start Guide': 'developer/dev_quickstart.md'
2526
- 'Code Structure and Organization': 'developer/code_structure.md'

components/eamxx/src/share/io/eamxx_io_utils.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "share/util/eamxx_utils.hpp"
66
#include "share/eamxx_config.hpp"
77

8+
#include <ekat_string_utils.hpp>
9+
810
#include <fstream>
911
#include <regex>
1012

@@ -248,4 +250,52 @@ create_diagnostic (const std::string& diag_field_name,
248250
return diag;
249251
}
250252

253+
std::pair<std::string, std::string>
254+
parse_field_alias (const std::string& field_spec)
255+
{
256+
const std::string delimiter = ":=";
257+
auto pos = field_spec.find(delimiter);
258+
259+
if (pos == std::string::npos) {
260+
// No alias found, return the field_spec as both alias and field name
261+
std::string trimmed = ekat::trim(field_spec);
262+
return {trimmed, trimmed};
263+
}
264+
265+
// Extract and trim alias and field name
266+
std::string alias = ekat::trim(field_spec.substr(0, pos));
267+
std::string field_name = ekat::trim(field_spec.substr(pos + delimiter.length()));
268+
269+
EKAT_REQUIRE_MSG(!alias.empty() && !field_name.empty(),
270+
"Error! Invalid field alias specification: '" + field_spec + "'\n"
271+
"Expected format: 'alias:=field_name' where both alias and field_name are non-empty.\n");
272+
273+
auto another_pos = field_name.find(delimiter);
274+
EKAT_REQUIRE_MSG(another_pos == std::string::npos,
275+
"Error! Invalid field alias specification: '" + field_spec + "'\n"
276+
"Multiple ':=' tokens found.\n");
277+
return {alias, field_name};
278+
}
279+
280+
std::pair<std::map<std::string, std::string>, std::vector<std::string>>
281+
process_field_aliases (const std::vector<std::string>& field_specs)
282+
{
283+
std::map<std::string, std::string> alias_to_field_map;
284+
std::vector<std::string> alias_names;
285+
286+
for (const auto& spec : field_specs) {
287+
auto [alias, field_name] = parse_field_alias(spec);
288+
289+
// Check for duplicate aliases
290+
EKAT_REQUIRE_MSG(alias_to_field_map.find(alias) == alias_to_field_map.end(),
291+
"Error! Duplicate field alias found: '" + alias + "'\n"
292+
"Each alias must be unique within the field list.\n");
293+
294+
alias_to_field_map[alias] = field_name;
295+
alias_names.push_back(alias);
296+
}
297+
298+
return {alias_to_field_map, alias_names};
299+
}
300+
251301
} // namespace scream

components/eamxx/src/share/io/eamxx_io_utils.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,16 @@ std::shared_ptr<AtmosphereDiagnostic>
8989
create_diagnostic (const std::string& diag_name,
9090
const std::shared_ptr<const AbstractGrid>& grid);
9191

92+
// Parse field alias from field specification string.
93+
// Format: "alias:=field_name" returns {alias, field_name}
94+
// For non-aliased fields, returns {field_name, field_name}
95+
std::pair<std::string, std::string>
96+
parse_field_alias (const std::string& field_spec);
97+
98+
// Process a list of field specifications with potential aliases.
99+
// Returns a map from alias_name -> internal_field_name and a vector of alias names
100+
std::pair<std::map<std::string, std::string>, std::vector<std::string>>
101+
process_field_aliases (const std::vector<std::string>& field_specs);
102+
92103
} // namespace scream
93104
#endif // SCREAM_IO_UTILS_HPP

0 commit comments

Comments
 (0)