Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
cb9c947
write out exceptions
Dooruk Oct 3, 2025
c1f7119
minor changes, make TUI message clearer
Dooruk Oct 3, 2025
3ca20c4
changes in moving files between forecast cycles
Dooruk Oct 3, 2025
10ff584
take out the linking task
Dooruk Oct 3, 2025
79e6153
introduce simplified coupled tasks assuming direct execution of GEOS …
Dooruk Oct 7, 2025
b573f10
forecast suite changes
Dooruk Oct 7, 2025
a1fd030
revamp files, more changes underway
Dooruk Oct 7, 2025
55aeb11
define new coupled model only suite
Dooruk Oct 7, 2025
4c8b9a3
change forecast location and make forecast a static folder
Dooruk Dec 1, 2025
2f8ed47
format and code suggestions
Dooruk Dec 18, 2025
f44ec97
document and codestring
Dooruk Dec 18, 2025
c02bf4d
dont except exception, send exception type to logger
Dooruk Jan 26, 2026
e872e38
background error new form
Dooruk Jan 26, 2026
2aab229
get rid off deprecated keys
Dooruk Feb 2, 2026
a35440d
let go off catching exceptions
Dooruk Feb 2, 2026
4454c92
Merge branch 'develop' into feature/exec_geos_direct_part1
Dooruk Feb 2, 2026
1008a81
revert genB to original
Dooruk Feb 3, 2026
d6e5b99
get rid of unused keys, create alternative method (R2D2, albeit inact…
Dooruk Feb 3, 2026
d081529
minor changes to keep up with PRs
Dooruk Feb 3, 2026
14de66a
changes related to initial restarts
Dooruk Feb 3, 2026
c898ab1
first model specific task
Dooruk Feb 3, 2026
4facad7
Merge branch 'develop' into feature/exec_geos_direct_part1
Dooruk Feb 3, 2026
3ad2722
code style fixes
Dooruk Feb 3, 2026
1d71e1e
adapt to new obs handling
Dooruk Feb 4, 2026
eb4383a
skip save_restart while figuring R2D2 out
Dooruk Feb 4, 2026
6dc3825
deprecated keys related changes and few fixes
Dooruk Feb 4, 2026
23577cf
some changes for geosv12
Dooruk Feb 5, 2026
bad345f
changes in geos class utility for geos v12
Dooruk Feb 5, 2026
c03d9ac
changes for 3dvar_cycle to test get, prep and run
Dooruk Feb 5, 2026
68cea55
make forecast_geos use new method
Dooruk Feb 10, 2026
e6b5643
few fixes, stop using adjacent cycle
Dooruk Feb 10, 2026
cfd44be
clarify homdir expdir logic
Dooruk Feb 10, 2026
3adf0d7
netcdf specify
Dooruk Feb 10, 2026
b9a9a47
Merge branch 'develop' into feature/exec_geos_direct_part1
Dooruk Feb 10, 2026
c2317a3
better statement
Dooruk Feb 12, 2026
ba29005
take out model specific approach from this PR
Dooruk Feb 12, 2026
33a968c
apply minor code improvements
Dooruk Feb 12, 2026
23bc8a5
Merge branch 'develop' into feature/exec_geos_direct_part1
Dooruk Feb 12, 2026
f2e787d
Merge branch 'feature/exec_geos_direct_part1' into feature/exec_geos_…
Dooruk Feb 12, 2026
60b13c5
rename suites, take out obsolete parts and tasks
Dooruk Feb 12, 2026
2cffb0f
add docstrings and proper naming for tasks as forecast directory is n…
Dooruk Feb 13, 2026
b5b61da
version bump
Dooruk Feb 13, 2026
3149f05
suite changes with new task names
Dooruk Feb 18, 2026
dbe0a3f
code improvements
Dooruk Feb 18, 2026
7a2ad5f
make mom6_iau model dependent
Dooruk Feb 20, 2026
f24cd0a
cycle times hack
Dooruk Feb 20, 2026
5094cda
make experiment.yaml non-alphabetical again by using default ruamel
Dooruk Feb 20, 2026
022287b
relevant for experiment.yaml
Dooruk Feb 20, 2026
53ede0e
minor fix for MOM6 IAU
Dooruk Feb 20, 2026
714ecfe
cycle times and overrride fixes
Dooruk Feb 20, 2026
c88b448
add tier2 cycling run
Dooruk Feb 20, 2026
01905d5
Merge branch 'develop' into feature/exec_geos_direct_part2
Dooruk Feb 20, 2026
8127f55
observation changes for new gdas_marine obs
Dooruk Feb 23, 2026
6ec5a39
take csh out, add more observers for tier2
Dooruk Feb 23, 2026
fa72657
experiment.yaml original order
Dooruk Feb 23, 2026
d5b6221
observation yaml, calculate obserror in timeseries
Dooruk Feb 23, 2026
01b13b7
tier2 in 3dvar_marine_cycle
Dooruk Feb 23, 2026
afa338e
update documentation and add another example
Dooruk Feb 23, 2026
2063efa
erase obsolete tasks
Dooruk Feb 23, 2026
0436b08
marine suite configs homdir location
Dooruk Feb 23, 2026
3f82f8e
include hotstart option for model runs, take out unnecessary questions
Dooruk Feb 23, 2026
25ca9ee
fix platform defaults
Dooruk Feb 23, 2026
5396d0b
Merge branch 'feature/exec_geos_direct_part2' into feature/exec_geos_…
Dooruk Feb 23, 2026
ef63eed
erase obsolete methods, add docstrings
Dooruk Feb 23, 2026
b0e948d
pycodestyle
Dooruk Feb 23, 2026
0fdc3db
erase window_offset
Dooruk Feb 23, 2026
5d59260
last changes
Dooruk Feb 23, 2026
70d2c9b
minor fixes
Dooruk Feb 25, 2026
6057a4b
another minot fix
Dooruk Feb 25, 2026
4949606
save forecast with r2d2 option only
Dooruk Feb 26, 2026
488f5d8
save forecast peripheries
Dooruk Feb 26, 2026
0fb4c86
add store for cf and nwp
Dooruk Mar 2, 2026
c65900e
fix forecast conflicts
Dooruk Mar 9, 2026
e84c08d
Merge branch 'develop' into feature/save_tasks/save_forecast
Dooruk Mar 9, 2026
a17a044
conflict fix
Dooruk Mar 11, 2026
4c3bf14
Merge branch 'develop' into feature/save_tasks/save_forecast
Dooruk Mar 11, 2026
13134b0
fix save forecast to work with develop
Dooruk May 18, 2026
7b842b4
Merge branch 'develop' into feature/save_tasks/save_forecast
Dooruk Jun 10, 2026
105d4f6
suite config and flow changes to save forecast
Dooruk Jun 12, 2026
5483e7d
Merge branch 'develop' into feature/save_tasks/save_analysis_fix_window
Dooruk Aug 18, 2026
e4c838b
Merge develop and apply stashed changes
Dooruk Aug 18, 2026
10a005e
first version analysis
Dooruk Aug 19, 2026
517681d
fix repeat
Dooruk Aug 19, 2026
6406c66
Merge branch 'develop' into feature/save_tasks/save_analysis_fix_window
Dooruk Aug 19, 2026
06e8a8a
fix silly mistakes
Dooruk Aug 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ def r2d2(template_dict: Mapping) -> Mapping:
'file_extension': 'tar.gz',
'filename': f'{cycle_dir}/cice.res.{local_background_time}.nc'})

an_list = [{'file_type': 'ocn.incr',
an_list = [{'file_type': 'ocn.an',
'r2d2_model': 'mom6',
'filename': f'{cycle_dir}/mom6_increment.nc'}]
# 'filename': f'{cycle_dir}/mom6_increment.nc'}]
'filename': f'{cycle_dir}/ocn.{experiment_id}.an.{analysis_time_iso}.nc'}]

if 'cice6' in template_dict['marine_models']:
an_list.append({'file_type': 'ice.incr',
an_list.append({'file_type': 'ice.an',
'r2d2_model': 'cice6',
'filename': f'{cycle_dir}/ice.{experiment_id}.incr.{analysis_time_iso}.nc'})
'filename': f'{cycle_dir}/ice.{experiment_id}.an.{analysis_time_iso}.nc'})

r2d2 = {
'fetch': {
Expand Down
14 changes: 10 additions & 4 deletions src/swell/suites/3dfgat_marine_cycle/flow.cylc
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,17 @@
{% endif %}

# Move restart to next cycle and then erase current forecast folder
SaveRestart-{{model_component}} => MoveDaRestart-{{model_component}} => CleanCycle-{{model_component}}
SaveRestart-{{model_component}} => MoveDaRestart-{{model_component}} d

{% if not skip_r2d2 %}
# Compress forecast on compute node, then store in R2D2 on login node
LinkCoupledGeosOutput-{{model_component}} => RunCompressForecast-{{model_component}} => SaveForecast-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediFgatExecutable-{{model_component}} => SaveObsDiags-{{model_component}} => CleanCycle-{{model_component}}
# Compress forecast on compute node, then save in R2D2 on login node; (state space) forecast, analysis, and (obs space) observation outputs.
LinkCoupledGeosOutput-{{model_component}} => SaveForecast-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediFgatExecutable-{{model_component}} => SaveAnalysis-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediFgatExecutable-{{model_component}} => SaveObsDiags-{{model_component}} => MoveDaRestart-{{model_component}}
{% endif %}

MoveDaRestart-{{model_component}} => CleanCycle-{{model_component}}

# Clean up large files
EvaObservations-{{model_component}} & EvaJediLog-{{model_component}} & EvaIncrement-{{model_component}} =>
CleanCycle-{{model_component}}
Expand Down Expand Up @@ -243,6 +246,9 @@
--{{key}} = {{value}}
{%- endfor %}

[[SaveAnalysis-{{model_component}}]]
script = "swell task SaveAnalysis $config -d $datetime -m {{model_component}}"

[[RunCompressForecast-{{model_component}}]]
script = "swell task RunCompressForecast $config -d $datetime -m {{model_component}}"
platform = {{platform}}
Expand Down
16 changes: 10 additions & 6 deletions src/swell/suites/3dvar_marine_cycle/flow.cylc
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@
SaveRestart-{{model_component}} => MoveDaRestart-{{model_component}} => CleanCycle-{{model_component}}

{% if not skip_r2d2 %}
# Compress forecast on compute node, then store in R2D2 on login node
LinkCoupledGeosOutput-{{model_component}} => RunCompressForecast-{{model_component}} => SaveForecast-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediVariationalExecutable-{{model_component}} => SaveObsDiags-{{model_component}} => CleanCycle-{{model_component}}
# Compress forecast on compute node, then save in R2D2 on login node; (state space) forecast, analysis, and (obs space) observation outputs.
LinkCoupledGeosOutput-{{model_component}} => SaveForecast-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediVariationalExecutable-{{model_component}} => SaveAnalysis-{{model_component}} => MoveDaRestart-{{model_component}}
RunJediVariationalExecutable-{{model_component}} => SaveObsDiags-{{model_component}} => MoveDaRestart-{{model_component}}
{% endif %}

# Clean up large files
Expand Down Expand Up @@ -251,15 +252,18 @@
--{{key}} = {{value}}
{%- endfor %}

[[SaveAnalysis-{{model_component}}]]
script = "swell task SaveAnalysis $config -d $datetime -m {{model_component}}"

[[SaveForecast-{{model_component}}]]
script = "swell task SaveForecast $config -d $datetime -m {{model_component}}"

[[SaveRestart-{{model_component}}]]
script = "swell task SaveRestart $config -d $datetime -m {{model_component}}"

[[SaveObsDiags-{{model_component}}]]
script = "swell task SaveObsDiags $config -d $datetime -m {{model_component}}"

[[SaveRestart-{{model_component}}]]
script = "swell task SaveRestart $config -d $datetime -m {{model_component}}"

[[PrepareAnalysis-{{model_component}}]]
script = "swell task PrepareAnalysis $config -d $datetime -m {{model_component}}"

Expand Down
2 changes: 1 addition & 1 deletion src/swell/tasks/get_background.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def execute(self) -> None:
# Get window parameters
local_background_time = self.da_window_params.local_background_time(window_length,
window_type)
analysis_time_iso = self.da_window_params.analysis_time_iso()
analysis_time_iso, _ = self.da_window_params.analysis_time_iso()

# Add to jedi config rendering dictionary
self.jedi_rendering.add_key('local_background_time', local_background_time)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def execute(self) -> None:
window_type = self.config.window_type()
window_length = self.config.window_length()

# Compute data assimilation window parameters
# --------------------------------------------
analysis_time = self.da_window_params.analysis_time()
analysis_time_iso = self.da_window_params.analysis_time_iso()
# Compute data assimilation window parameters, some methods return dto, which may not be needed here
# -----------------------------------------------------------------------------
analysis_time, _ = self.da_window_params.analysis_time()
analysis_time_iso, _ = self.da_window_params.analysis_time_iso()
local_background_time = self.da_window_params.local_background_time(window_length,
window_type)
local_background_time_iso = self.da_window_params.local_background_time_iso(window_length,
Expand Down
185 changes: 185 additions & 0 deletions src/swell/tasks/save_analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# (C) Copyright 2021- United States Government as represented by the Administrator of the
# National Aeronautics and Space Administration. All Rights Reserved.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.


# --------------------------------------------------------------------------------------------------


from datetime import datetime as dt
import isodate
import os
from r2d2 import store

from swell.tasks.base.task_base import taskBase
from swell.utilities.datetime_util import datetime_formats
from swell.utilities.r2d2 import load_r2d2_credentials


# --------------------------------------------------------------------------------------------------

class SaveAnalysis(taskBase):

# ----------------------------------------------------------------------------------------------

def execute(self) -> None:

"""Store analysis files for a given experiment and cycle in R2D2.

Analysis files are handled differently then forecast files in R2D2, as they are stored
at their exact valid time (the analysis time).

Additionally, for 4D/FGAT windows, all analyses within the window are stored, each at its own valid time,
rather than just the window-begin forecast as in the SaveForecast task.

The marine FGAT only calculates one analysis and increment file per model (i.e., mom6 and cice6),
valid at the local background time.

Parameters
----------
All inputs are extracted from the JEDI experiment file configuration.
See the taskBase constructor for more information.
"""

# Parse common configuration as instance variables
# -------------------------------------------------
self.marine_models = self.config.marine_models(None) or []
self.window_type = self.config.window_type()
self.window_length = self.config.window_length()
self.window_begin_iso = self.da_window_params.window_begin_iso(self.window_length)
self.background_experiment = self.experiment_id()
self.horizontal_resolution = self.config.horizontal_resolution()

# Load R2D2 credentials
# ---------------------
load_r2d2_credentials(self.logger, self.platform())

self.local_background_time, self.local_background_time_dto = self.da_window_params.local_background_time(
self.window_length,
self.window_type,
dto=True)

analysis_time_iso, analysis_time_dto = self.da_window_params.analysis_time_iso()

# Populate jedi_rendering template dictionary before rendering
# (mirrors run_jedi_variational_executable.py)
# ------------------------------------------------------------
local_background_time_iso = self.da_window_params.local_background_time_iso(
self.window_length, self.window_type)

self.jedi_rendering.add_key('local_background_time', self.local_background_time)
self.jedi_rendering.add_key('local_background_time_iso', local_background_time_iso)
self.jedi_rendering.add_key('marine_models', self.marine_models)
self.jedi_rendering.add_key('horizontal_resolution', self.horizontal_resolution)
self.jedi_rendering.add_key('analysis_time_iso', analysis_time_iso)

# Render r2d2 interface dict once, shared by all store methods
# -------------------------------------------------------------
self.r2d2_dict = self.jedi_rendering.render_interface_model('r2d2')

# Dispatch to model- and window-type-specific store methods
# ----------------------------------------------------------
model_component = self.get_model()

if model_component == 'geos_atmosphere':
if is_4d:
self.store_atmosphere_4d()
else:
self.store_atmosphere_3d()

elif model_component == 'geos_marine':
marine_model_configs = [('mom6', 'ocn_filename', 'ocn.an')]
if 'cice6' in self.marine_models:
marine_model_configs.append(('cice6', 'ice_filename', 'ice.an'))

for model_name, filename_key, file_type in marine_model_configs:
self._store_an_dict(model_name, analysis_time_dto)

else:
self.logger.abort(f'Unknown model component for SaveAnalysis: {model_component}')

# ----------------------------------------------------------------------------------------------

def _store_analysis(self,
model_name: str,
ana_dto: dt,
source_file: str,
file_type: str,
) -> None:
"""Call r2d2.store for a single analysis file at its exact valid time.

Parameters
----------
model_name : str
R2D2 model identifier (e.g. 'geos', 'mom6', 'cice6', 'geos_cf').
ana_dto : datetime
Valid datetime of the analysis file (used directly as the r2d2 date).
source_file : str
Absolute path to the analysis file to store.
file_type : str
R2D2 file_type label (e.g. 'MOM.res', 'cice.res', 'bkg').
"""

file_extension = file_type.split('.')[-1] if '.' in file_type else 'nc'

self.logger.info(f'Storing {os.path.basename(source_file)} '
f'at {ana_dto.strftime(datetime_formats["iso_format"])}')

# store(
# item='analysis',
# model=model_name,
# experiment=self.background_experiment,
# resolution=self.horizontal_resolution,
# date=ana_dto.strftime('%Y-%m-%d %H:%M:%S'),
# source_file=source_file,
# file_type=file_type,
# file_extension=file_extension,
# )

# ----------------------------------------------------------------------------------------------

def _store_an_dict(self, model_name: str, ana_dto: dt) -> None:
"""Store all analyses defined in r2d2_dict['store']['an'] for one datetime.

The filename for each entry is resolved by applying strftime to ana_dto,
ensuring it works for both static (marine, already strftime-compatible) and
datetime-templated (atmosphere) filename patterns.

Parameters
----------
model_name : str
R2D2 model identifier.
ana_dto : datetime
Valid datetime of the analysis — used as the r2d2 date and for filename resolution.
"""

for an in self.r2d2_dict['store']['an']:
if an.get('r2d2_model') != model_name:
continue
file_type = an['file_type']
source_file = ana_dto.strftime(an['filename'])
self._store_analysis(model_name, ana_dto, source_file, file_type)

# ----------------------------------------------------------------------------------------------
# Atmosphere store methods (not implemeted yet, will need to resolve datetime-templated filenames for each analysis within the window)
# ----------------------------------------------------------------------------------------------

def store_atmosphere_3d(self) -> None:
"""Store a single atmospheric analysis at the middle of a 3D window."""

self.logger.abort('Storing the window-begin atmospheric analysis is not ready yet.')

# ----------------------------------------------------------------------------------------------

def store_atmosphere_4d(self) -> None:
"""Store atmospheric analyses across a 4D (or FGAT) window.

Uses states_generator to enumerate valid analysis datetimes and stores
each file, keyed by its exact valid time.
"""

self.logger.abort('Storing the window-begin atmospheric analysis is not ready yet.')

# --------------------------------------------------------------------------------------------------
37 changes: 25 additions & 12 deletions src/swell/tasks/task_questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,18 @@ class TaskQuestions(QuestionContainer, Enum):

# --------------------------------------------------------------------------------------------------

RunCompressForecast = QuestionList(
list_name="RunCompressForecast",
questions=[
window_questions,
qd.r2d2_experiment_id(),
qd.background_frequency(),
qd.horizontal_resolution(),
]
)

# --------------------------------------------------------------------------------------------------

RunJediConvertStateSoca2ciceExecutable = QuestionList(
list_name="RunJediConvertStateSoca2ciceExecutable",
questions=[
Expand Down Expand Up @@ -851,18 +863,6 @@ class TaskQuestions(QuestionContainer, Enum):

# --------------------------------------------------------------------------------------------------

RunCompressForecast = QuestionList(
list_name="RunCompressForecast",
questions=[
window_questions,
qd.r2d2_experiment_id(),
qd.background_frequency(),
qd.horizontal_resolution(),
]
)

# --------------------------------------------------------------------------------------------------

RunJediEdaExecutable = QuestionList(
list_name="RunJediEdaExecutable",
questions=[
Expand All @@ -877,6 +877,18 @@ class TaskQuestions(QuestionContainer, Enum):

# --------------------------------------------------------------------------------------------------

SaveAnalysis = QuestionList(
list_name="SaveAnalysis",
questions=[
window_questions,
qd.r2d2_experiment_id(),
qd.background_experiment(),
qd.horizontal_resolution(),
]
)

# --------------------------------------------------------------------------------------------------

SaveForecast = QuestionList(
list_name="SaveForecast",
questions=[
Expand All @@ -887,6 +899,7 @@ class TaskQuestions(QuestionContainer, Enum):
]
)


# --------------------------------------------------------------------------------------------------

SaveForecastCf = QuestionList(
Expand Down
22 changes: 18 additions & 4 deletions src/swell/utilities/data_assimilation_window_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,31 @@ def __get_analysis_time__(self) -> datetime.datetime:

# ----------------------------------------------------------------------------------------------

def analysis_time(self) -> str:
def analysis_time(self) -> Tuple[str, datetime.datetime]:
"""Get the analysis time for the current cycle.

Returns
-------
Tuple[str, datetime.datetime]
A tuple containing the analysis time formatted as a string and the corresponding datetime object.
"""

analysis_time_dto = self.__get_analysis_time__()
return analysis_time_dto.strftime(datetime_formats['directory_format'])
return analysis_time_dto.strftime(datetime_formats['directory_format']), analysis_time_dto

# ----------------------------------------------------------------------------------------------

def analysis_time_iso(self) -> str:
def analysis_time_iso(self) -> Tuple[str, datetime.datetime]:
"""Get the analysis time for the current cycle in ISO format.

Returns
-------
Tuple[str, datetime.datetime]
A tuple containing the analysis time formatted as an ISO string and the corresponding datetime object.
"""

analysis_time_dto = self.__get_analysis_time__()
return analysis_time_dto.strftime(datetime_formats['iso_format'])
return analysis_time_dto.strftime(datetime_formats['iso_format']), analysis_time_dto

# ----------------------------------------------------------------------------------------------

Expand Down
Loading