11import os
22import shutil
33from collections import OrderedDict
4- from typing import List , Union
4+ from typing import Dict , List , Union
55
66import numpy as np
77import xarray as xr
@@ -69,6 +69,8 @@ class ModelStep(Step):
6969 Whether to create a yaml file with model config options and streams
7070 instead of MPAS namelist and streams files
7171
72+ streams_section : str
73+ The name of the streams section in yaml files
7274 """
7375 def __init__ (self , component , name , subdir = None , indir = None , ntasks = None ,
7476 min_tasks = None , openmp_threads = None , max_memory = None ,
@@ -173,6 +175,7 @@ def __init__(self, component, name, subdir=None, indir=None, ntasks=None,
173175 self .graph_filename = graph_filename
174176
175177 self .make_yaml = make_yaml
178+ self .streams_section = 'streams'
176179
177180 self .add_input_file (filename = '<<<model>>>' )
178181
@@ -275,7 +278,7 @@ def add_yaml_file(self, package, yaml, template_replacements=None):
275278
276279 def map_yaml_options (self , options , config_model ):
277280 """
278- A mapping between model config options between different models. This
281+ A mapping between model config options from different models. This
279282 method should be overridden for situations in which yaml config
280283 options have diverged in name or structure from their counterparts in
281284 another model (e.g. when translating from MPAS-Ocean namelist options
@@ -301,7 +304,7 @@ def map_yaml_options(self, options, config_model):
301304
302305 def map_yaml_configs (self , configs , config_model ):
303306 """
304- A mapping between model config options between different models. This
307+ A mapping between model config options from different models. This
305308 method should be overridden for situations in which yaml config
306309 options have diverged in name or structure from their counterparts in
307310 another model (e.g. when translating from MPAS-Ocean namelist options
@@ -325,6 +328,29 @@ def map_yaml_configs(self, configs, config_model):
325328 """
326329 return configs
327330
331+ def map_yaml_streams (self , streams , config_model ):
332+ """
333+ A mapping between model streams from different models. This method
334+ should be overridden for situations in which yaml streams have diverged
335+ in name or structure from their counterparts in another model (e.g.
336+ when translating from MPAS-Ocean streams to Omega IOStreams)
337+
338+ Parameters
339+ ----------
340+ streams : dict
341+ A nested dictionary of streams data
342+
343+ config_model : str or None
344+ If streams are available for multiple models, the model that the
345+ streams are from
346+
347+ Returns
348+ -------
349+ configs : dict
350+ A revised nested dictionary of streams data
351+ """
352+ return streams
353+
328354 def map_yaml_to_namelist (self , options ):
329355 """
330356 A mapping from yaml model config options to namelist options. This
@@ -440,7 +466,7 @@ def runtime_setup(self):
440466 self .dynamic_model_config (at_setup = False )
441467
442468 if self .make_yaml :
443- self ._process_yaml (quiet = quiet )
469+ self ._process_yaml (quiet = quiet , remove_unrequested_streams = False )
444470 else :
445471 self ._process_namelists (quiet = quiet )
446472 self ._process_streams (quiet = quiet , remove_unrequested = False )
@@ -481,7 +507,7 @@ def process_inputs_and_outputs(self):
481507 self ._create_model_config ()
482508
483509 if self .make_yaml :
484- self ._process_yaml (quiet = quiet )
510+ self ._process_yaml (quiet = quiet , remove_unrequested_streams = True )
485511 else :
486512 self ._process_namelists (quiet = quiet )
487513 self ._process_streams (quiet = quiet , remove_unrequested = True )
@@ -563,7 +589,8 @@ def _create_model_config(self):
563589 config = self .config
564590 if self .make_yaml :
565591 defaults_filename = config .get ('model_config' , 'defaults' )
566- self ._yaml = PolarisYaml .read (defaults_filename )
592+ self ._yaml = PolarisYaml .read (defaults_filename ,
593+ streams_section = self .streams_section )
567594 else :
568595 defaults_filename = config .get ('namelists' , 'forward' )
569596 self ._namelist = polaris .namelist .ingest (defaults_filename )
@@ -578,7 +605,8 @@ def _read_model_config(self):
578605 """
579606 if self .make_yaml :
580607 filename = os .path .join (self .work_dir , self .yaml )
581- self ._yaml = PolarisYaml .read (filename )
608+ self ._yaml = PolarisYaml .read (filename ,
609+ streams_section = self .streams_section )
582610 else :
583611 filename = os .path .join (self .work_dir , self .namelist )
584612 self ._namelist = polaris .namelist .ingest (filename )
@@ -641,10 +669,10 @@ def _process_namelists(self, quiet):
641669 options = self .map_yaml_to_namelist (options )
642670 replacements .update (options )
643671 if 'yaml' in entry :
644- yaml = PolarisYaml .read (filename = entry [ 'yaml' ],
645- package = entry ['package' ],
646- replacements = entry ['replacements' ],
647- model = config_model )
672+ yaml = PolarisYaml .read (
673+ filename = entry [ 'yaml' ], package = entry ['package' ],
674+ replacements = entry ['replacements' ], model = config_model ,
675+ streams_section = self . streams_section )
648676
649677 configs = self .map_yaml_configs (configs = yaml .configs ,
650678 config_model = config_model )
@@ -727,8 +755,7 @@ def _process_streams(self, quiet, remove_unrequested):
727755 if not found :
728756 defaults .remove (default )
729757
730- @staticmethod
731- def _process_yaml_streams (yaml_filename , package , replacements ,
758+ def _process_yaml_streams (self , yaml_filename , package , replacements ,
732759 config_model , processed_registry_filename ,
733760 tree , quiet ):
734761 if not quiet :
@@ -737,14 +764,15 @@ def _process_yaml_streams(yaml_filename, package, replacements,
737764 yaml = PolarisYaml .read (filename = yaml_filename ,
738765 package = package ,
739766 replacements = replacements ,
740- model = config_model )
767+ model = config_model ,
768+ streams_section = self .streams_section )
741769 assert processed_registry_filename is not None
742770 new_tree = yaml_to_mpas_streams (
743771 processed_registry_filename , yaml )
744772 tree = polaris .streams .update_tree (tree , new_tree )
745773 return tree
746774
747- def _process_yaml (self , quiet ):
775+ def _process_yaml (self , quiet , remove_unrequested_streams ):
748776 """
749777 Processes changes to a yaml file from the files and dictionaries
750778 in the step's ``model_config_data``.
@@ -759,6 +787,8 @@ def _process_yaml(self, quiet):
759787 if not quiet :
760788 print (f'Warning: replacing yaml options in { self .yaml } ' )
761789
790+ streams : Dict [str , Dict [str , Union [str , float , int , List [str ]]]] = {}
791+
762792 for entry in self .model_config_data :
763793 if 'namelist' in entry :
764794 raise ValueError ('Cannot generate a yaml config from an MPAS '
@@ -773,14 +803,49 @@ def _process_yaml(self, quiet):
773803 config_model = config_model )
774804 self ._yaml .update (options = options , quiet = quiet )
775805 if 'yaml' in entry :
776- yaml = PolarisYaml .read (filename = entry [ 'yaml' ],
777- package = entry ['package' ],
778- replacements = entry ['replacements' ],
779- model = config_model )
806+ yaml = PolarisYaml .read (
807+ filename = entry [ 'yaml' ], package = entry ['package' ],
808+ replacements = entry ['replacements' ], model = config_model ,
809+ streams_section = self . streams_section )
780810
781811 configs = self .map_yaml_configs (configs = yaml .configs ,
782812 config_model = config_model )
813+ new_streams = self .map_yaml_streams (
814+ streams = yaml .streams , config_model = config_model )
815+ self ._update_yaml_streams (streams , new_streams ,
816+ quiet = quiet ,
817+ remove_unrequested = False )
783818 self ._yaml .update (configs = configs , quiet = quiet )
819+ self ._update_yaml_streams (
820+ self ._yaml .streams , streams , quiet = quiet ,
821+ remove_unrequested = remove_unrequested_streams )
822+
823+ @staticmethod
824+ def _update_yaml_streams (streams , new_streams , quiet , remove_unrequested ):
825+ """
826+ Update yaml streams, optionally removing any streams that aren't in
827+ new_streams
828+ """
829+
830+ for stream_name , new_stream in new_streams .items ():
831+ if stream_name in streams :
832+ streams [stream_name ].update (new_stream )
833+ if not quiet :
834+ print (f' updating: { stream_name } ' )
835+ else :
836+ if not quiet :
837+ print (f' adding: { stream_name } ' )
838+ streams [stream_name ] = new_stream
839+
840+ if remove_unrequested :
841+ # during setup, we remove any default streams that aren't requested
842+ # but at runtime we don't want to do this because we would lose any
843+ # streams added only during setup.
844+ for stream_name in list (streams .keys ()):
845+ if stream_name not in new_streams :
846+ if not quiet :
847+ print (f' dropping: { stream_name } ' )
848+ streams .pop (stream_name )
784849
785850
786851def make_graph_file (mesh_filename , graph_filename = 'graph.info' ,
0 commit comments