@@ -185,7 +185,7 @@ def __init__(self, hgrid,bc_yaml=None,boundary_segments=None):
185185 "none" : 0 ,
186186 }
187187
188- def _norm_earth (consts ):
188+ def _norm_earth (self , consts ):
189189 out = []
190190 for item in consts :
191191 if not isinstance (item , dict ):
@@ -273,7 +273,7 @@ def _norm_earth(consts):
273273 return out
274274
275275
276- def _norm_boundary (consts ):
276+ def _norm_boundary (self , consts ):
277277 """
278278 Normalize boundary (forcing) tidal constituent specifications into list of dicts with keys:
279279 name, angular_frequency, node_factor, earth_equilibrium_argument
@@ -377,6 +377,79 @@ def _norm_boundary(consts):
377377 continue
378378
379379 return out
380+
381+ def _handle_tidal_constituents_val (self ,vals ,consts_name ,const_type_str ,boundary_id ,node_id ):
382+ """
383+ Determine and return the numeric amplitude (value) for a tidal constituent given a
384+ variety of accepted input formats.
385+
386+ The function accepts a scalar numeric value, an indexable sequence (list/tuple/np.ndarray)
387+ from which a per-node value will be selected, or a string expression which will be
388+ evaluated to produce a numeric result.
389+
390+ Args:
391+ vals: add argument meaning comment here
392+ The input value(s) for the tidal constituent. Accepted types:
393+ - numbers.Number: treated as a scalar amplitude and returned as-is.
394+ - list, tuple, np.ndarray: treated as a per-node collection; the element at
395+ index `node_id` is returned. A mismatched length will raise ValueError.
396+ - str: treated as a Python expression and evaluated with eval(); the
397+ evaluated result is returned if successful.
398+ consts_name:
399+ Human-readable name or identifier of the tidal constituent (used in error
400+ messages).
401+ const_type_str: a
402+ Short description of the constituent paramter (such as elev_phase
403+ elev_amp,u_amp,u_phase) type (used in error messages).
404+ boundary_id:
405+ Identifier for the boundary to which the constituent applies (used in
406+ error messages).
407+ node_id:
408+ Integer index of the node for which a per-node value should be selected
409+ when `vals` is an indexable collection.
410+
411+ Returns:
412+ float: The resolved amplitude value for the constituent at the requested node.
413+
414+ Raises:
415+ ValueError: If
416+ - `vals` is an indexable collection but indexing with `node_id` fails
417+ (e.g., length mismatch).
418+ - `vals` is a string but its evaluation via eval() fails.
419+ - `vals` is of an unsupported type.
420+
421+ Notes:
422+ - The function uses eval() to evaluate string expressions. For safety, ensure
423+ inputs are trusted or consider replacing eval() with a safer parser if
424+ untrusted input must be supported.
425+ - The returned value is a float-compatible numeric; callers may want to cast
426+ or validate the result further if required by downstream code.
427+ """
428+ ##
429+ # amplitude value determination
430+ val = 0.0
431+ if isinstance (vals , numbers .Number ):
432+ val = vals
433+ elif isinstance (vals , (list , tuple , np .ndarray )):
434+ try :
435+ val = vals [node_id ]
436+ except Exception :
437+ raise ValueError (
438+ f"{ const_type_str } list length does not match number of nodes for \
439+ tidal constituent { consts_name } at boundary { boundary_id } \n "
440+ )
441+ elif isinstance (vals , str ):
442+ try :
443+ val = eval (vals )
444+ except Exception :
445+ raise ValueError (f"{ val } is not a supported tidal expressionfor tidal constituent \
446+ { const_type_str } for tidal constituent { consts_name } at boundary \
447+ { boundary_id } \n "
448+ )
449+ else :
450+ raise ValueError (f"Unsupported tidal type { vals } for tidal constituent { const_type_str } for\
451+ tidal constituent { consts_name } at boundary { boundary_id } \n " )
452+ return val
380453
381454
382455 def write_bctides (self , bctides_file ):
@@ -389,7 +462,6 @@ def write_bctides(self, bctides_file):
389462 # normalize tidal-constituent input formats so downstream code can
390463 # expect a list of dicts with explicit keys.
391464
392-
393465 if self .earth_tides and "tidal_constituents" in self .earth_tides :
394466 self .earth_tides ["tidal_constituents" ] = self ._norm_earth (
395467 self .earth_tides ["tidal_constituents" ]
@@ -515,6 +587,7 @@ def write_bctides(self, bctides_file):
515587 tracer_mod_pos [tracer_mods [kk ]] = kk
516588
517589 for i in range (num_open_boundaries ):
590+ print ("processing open boundary %s " % self .open_boundaries [i ]["name" ])
518591 num_nodes = len (hgrid .boundaries [i ].nodes )
519592 node_id_lst = hgrid .boundaries [i ].nodes
520593 elev_id = 0
@@ -642,106 +715,64 @@ def write_bctides(self, bctides_file):
642715 outf .write (str (elev_source ))
643716 elif (elev_id == 3 ) or (elev_id == 5 ):
644717 ## tidal forcing
645- num_tidal_constituents = elev_boundary [ "tidal_constituents" ]
718+ # write each tidal constituent: name line then amplitude/phase per node
646719 for tidal_constituent in elev_boundary ["tidal_constituents" ]:
647- outf .write (tidal_constituent ["name" ] + "\n " )
720+ # name must exist (YAML examples use 'name' after normalization)
721+ name = tidal_constituent .get ("name" )
722+ if name is None :
723+ # fallback to first key if mapping style was used
724+ if isinstance (tidal_constituent , dict ) and len (tidal_constituent ) > 0 :
725+ name = next (iter (tidal_constituent ))
726+ else :
727+ ## raise error
728+ raise ValueError (f" elev tidal constituent name is empty for open boundary { i } \n " )
729+ outf .write (name + "\n " )
730+ amp = tidal_constituent [name ][0 ]["amplitude" ]
731+ phase = tidal_constituent [name ][1 ]["phase" ]
732+
648733 for kk in range (num_nodes ):
649- amp = tidal_constituent ["amplitude" ]
650- phase = tidal_constituent ["phase" ]
651734 node_id = node_id_lst [kk ]
652735 x = hgrid .nodes [node_id , 0 ]
653736 y = hgrid .nodes [node_id , 1 ]
654- if isinstance (amp , numbers .Number ):
655- amp_val = amp
656- elif isinstance (amp , str ): # try to evalate expression
657- try :
658- amp_val = eval (amp )
659- except :
660- raise ValueError (
661- amp
662- + " is not a supported tidal amp expression\n "
663- )
664- if isinstance (phase , numbers .Number ):
665- phase_val = phase
666- elif isinstance (
667- phase , str
668- ): # try to evalate expression
669- try :
670- phase_val = eval (phase )
671- except :
672- raise ValueError (
673- phase
674- + " is not a supported tidal phase expression\n "
675- )
737+ amp_val = self ._handle_tidal_constituents_val (amp ,name ,"elev_amplitude" ,i ,kk )
738+ phase_val = self ._handle_tidal_constituents_val (phase ,name ,"elev_phase" ,i ,kk )
739+
676740 outf .write (str (amp_val ) + " " + str (phase_val ) + "\n " )
677741
678742 ## velocity boundary parameters
679743 if vel_id == 2 :
680- ## const
744+ ## constp
681745 outf .write (str (vel_source ))
682746 elif (vel_id == 3 ) or (vel_id == 5 ):
683747 ## tidal forcing
684748 for tidal_constituent in vel_boundary ["tidal_constituents" ]:
685- outf .write (tidal_constituent ["name" ] + "\n " )
749+ # name must exist (YAML examples use 'name' after normalization)
750+ name = tidal_constituent .get ("name" )
751+ if name is None :
752+ # fallback to first key if mapping style was used
753+ if isinstance (tidal_constituent , dict ) and len (tidal_constituent ) > 0 :
754+ name = next (iter (tidal_constituent ))
755+ else :
756+ ## raise error
757+ raise ValueError (f" vel tidal constituent name is empty for open boundary { i } \n " )
758+ outf .write (name + "\n " )
759+ u_amp = tidal_constituent [name ][0 ]["u_amplitude" ]
760+ u_phase = tidal_constituent [name ][1 ]["u_phase" ]
761+ v_amp = tidal_constituent [name ][2 ]["v_amplitude" ]
762+ v_phase = tidal_constituent [name ][3 ]["v_phase" ]
763+
686764 for kk in range (num_nodes ):
687- u_amp = tidal_constituent ["u_amplitude" ]
688- u_phase = tidal_constituent ["u_phase" ]
689- v_amp = tidal_constituent ["v_amplitude" ]
690- v_phase = tidal_constituent ["v_phase" ]
765+
691766 node_id = node_id_lst [kk ]
692767 x = hgrid .nodes [node_id , 0 ]
693768 y = hgrid .nodes [node_id , 1 ]
694-
695- if isinstance (u_amp , numbers .Number ):
696- u_amp_val = u_amp
697- elif isinstance (
698- u_amp , str
699- ): # try to evalate expression
700- try :
701- u_amp_val = eval (u_amp )
702- except :
703- raise ValueError (
704- u_amp
705- + " is not a supported tidal amp expression\n "
706- )
707- if isinstance (u_phase , numbers .Number ):
708- u_phase_val = u_phase
709- elif isinstance (
710- u_phase , str
711- ): # try to evalate expression
712- try :
713- u_phase_val = eval (u_phase )
714- except :
715- raise ValueError (
716- u_phase
717- + " is not a supported tidal phase expression\n "
718- )
719-
720- if isinstance (v_amp , numbers .Number ):
721- v_amp_val = v_amp
722- elif isinstance (
723- v_amp , str
724- ): # try to evalate expression
725- try :
726- v_amp_val = eval (v_amp )
727- except :
728- raise ValueError (
729- u_amp
730- + " is not a supported tidal amp expression\n "
731- )
732- if isinstance (v_phase , numbers .Number ):
733- v_phase_val = v_phase
734- elif isinstance (
735- v_phase , str
736- ): # try to evalate expression
737- try :
738- v_phase_val = eval (v_phase )
739- except :
740- raise ValueError (
741- v_phase
742- + " is not a supported tidal phase expression\n "
743- )
744769
770+ u_amp_val = self ._handle_tidal_constituents_val (u_amp ,name ,"u_amplitude" ,i ,kk )
771+ u_phase_val = self ._handle_tidal_constituents_val (u_phase ,name ,"u_phase" ,i ,kk )
772+ v_amp_val = self ._handle_tidal_constituents_val (v_amp ,name ,"v_amplitude" ,i ,kk )
773+ v_phase_val = self ._handle_tidal_constituents_val (v_phase ,name ,"v_phase" ,i ,kk )
774+
775+
745776 outf .write (
746777 str (u_amp_val )
747778 + " "
0 commit comments