@@ -185,93 +185,210 @@ def __init__(self, hgrid,bc_yaml=None,boundary_segments=None):
185185 "none" : 0 ,
186186 }
187187
188- def write_bctides (self , bctides_file ):
189-
190- with open (bctides_file , "w" ) as outf :
191- tt = self .date .strftime ("%Y-%m-%d %H:%M" )
192- outf .write (tt )
193- outf .write ("\n " )
194- # normalize tidal-constituent input formats so downstream code can
195- # expect a list of dicts with explicit keys.
196- def _norm_earth (consts ):
197- out = []
198- for item in consts :
199- if isinstance (item , dict ):
200- # either {"name":..., "amplitude":..., ...} or {"K1": [..]}
201- if "name" in item :
202- name = item .get ("name" )
203- amp = item .get ("amplitude" , item .get ("amp" , 0.0 ))
204- node = item .get ("node_factor" , 1.0 )
205- freq = item .get ("angular_frequency" , item .get ("frequency" , 0.0 ))
206- eqa = item .get ("earth_equilibrium_argument" , item .get ("eqa" , 0.0 ))
207- else :
208- key = next (iter (item ))
209- vals = item [key ]
210- name = key
211- if isinstance (vals , (list , tuple )):
212- # support common lengths used in examples
213- if len (vals ) == 5 :
214- # example: [?, amplitude, freq, node_factor, eqa]
215- amp = vals [1 ]
216- freq = vals [2 ]
217- node = vals [3 ]
218- eqa = vals [4 ]
219- elif len (vals ) == 4 :
220- # example: [amp, freq, node, eqa]
221- amp = vals [0 ]
222- freq = vals [1 ]
223- node = vals [2 ]
224- eqa = vals [3 ]
225- else :
226- amp = vals [0 ] if len (vals ) > 0 else 0.0
227- freq = vals [2 ] if len (vals ) > 2 else 0.0
228- node = vals [3 ] if len (vals ) > 3 else 1.0
229- eqa = vals [- 1 ] if len (vals ) > 0 else 0.0
230- else :
231- # unsupported format -> skip
232- continue
233- else :
234- continue
235- out .append ({
188+ def _norm_earth (consts ):
189+ out = []
190+ for item in consts :
191+ if not isinstance (item , dict ):
192+ # unsupported format -> skip
193+ continue
194+
195+ # case: explicit dict with "name" key
196+ if "name" in item :
197+ name = item .get ("name" )
198+ amp = item .get ("amplitude" , item .get ("amp" , 0.0 ))
199+ node = item .get ("node_factor" , item .get ("node" , 1.0 ))
200+ freq = item .get ("angular_frequency" , item .get ("frequency" , 0.0 ))
201+ eqa = item .get (
202+ "earth_equilibrium_argument" ,
203+ item .get ("eqa" , item .get ("phase" , 0.0 )),
204+ )
205+ out .append (
206+ {
236207 "name" : name ,
237208 "amplitude" : amp ,
238209 "node_factor" : node ,
239210 "angular_frequency" : freq ,
240211 "earth_equilibrium_argument" : eqa ,
241- })
242- return out
243-
244- def _norm_boundary (consts ):
245- out = []
246- for item in consts :
247- if isinstance (item , dict ):
248- if "name" in item :
249- name = item .get ("name" )
250- freq = item .get ("angular_frequency" , item .get ("frequency" , 0.0 ))
251- node = item .get ("node_factor" , 1.0 )
252- eqa = item .get ("earth_equilibrium_argument" , item .get ("eqa" , 0.0 ))
253- else :
254- key = next (iter (item ))
255- vals = item [key ]
256- name = key
257- if isinstance (vals , (list , tuple )):
258- if len (vals ) >= 3 :
259- freq , node , eqa = vals [0 ], vals [1 ], vals [2 ]
260- else :
261- freq = vals [0 ] if len (vals ) > 0 else 0.0
262- node = vals [1 ] if len (vals ) > 1 else 1.0
263- eqa = vals [- 1 ] if len (vals ) > 0 else 0.0
264- else :
265- continue
266- else :
267- continue
268- out .append ({
212+ }
213+ )
214+ continue
215+
216+ # case: mapping like {K1: {...}} or {K1: [..]}
217+ key = next (iter (item ))
218+ vals = item [key ]
219+ name = key
220+
221+ if isinstance (vals , dict ):
222+ amp = vals .get ("amplitude" , vals .get ("amp" , 0.0 ))
223+ node = vals .get ("node_factor" , vals .get ("node" , 1.0 ))
224+ freq = vals .get ("angular_frequency" , vals .get ("frequency" , 0.0 ))
225+ eqa = vals .get (
226+ "earth_equilibrium_argument" ,
227+ vals .get ("eqa" , vals .get ("phase" , 0.0 )),
228+ )
229+ out .append (
230+ {
269231 "name" : name ,
232+ "amplitude" : amp ,
233+ "node_factor" : node ,
270234 "angular_frequency" : freq ,
235+ "earth_equilibrium_argument" : eqa ,
236+ }
237+ )
238+ continue
239+
240+ if isinstance (vals , (list , tuple )):
241+ # support common list formats used historically
242+ if len (vals ) == 5 :
243+ # example: [?, amplitude, freq, node_factor, eqa]
244+ amp = vals [1 ]
245+ freq = vals [2 ]
246+ node = vals [3 ]
247+ eqa = vals [4 ]
248+ elif len (vals ) == 4 :
249+ # example: [amp, freq, node, eqa]
250+ amp = vals [0 ]
251+ freq = vals [1 ]
252+ node = vals [2 ]
253+ eqa = vals [3 ]
254+ else :
255+ amp = vals [0 ] if len (vals ) > 0 else 0.0
256+ freq = vals [2 ] if len (vals ) > 2 else 0.0
257+ node = vals [3 ] if len (vals ) > 3 else 1.0
258+ eqa = vals [- 1 ] if len (vals ) > 0 else 0.0
259+ out .append (
260+ {
261+ "name" : name ,
262+ "amplitude" : amp ,
271263 "node_factor" : node ,
264+ "angular_frequency" : freq ,
272265 "earth_equilibrium_argument" : eqa ,
273- })
274- return out
266+ }
267+ )
268+ continue
269+
270+ # unsupported nested format -> skip
271+ continue
272+
273+ return out
274+
275+
276+ def _norm_boundary (consts ):
277+ """
278+ Normalize boundary (forcing) tidal constituent specifications into list of dicts with keys:
279+ name, angular_frequency, node_factor, earth_equilibrium_argument
280+
281+ Supported input shapes (from YAML examples):
282+ - explicit dict with "name": {...}
283+ - mapping like {O1: {...}} or {O1: [freq, node, eqa]}
284+ - list/tuple forms [freq, node, eqa] when provided as the value for a key
285+ - simple string -> defaults
286+ """
287+ out = []
288+ for item in consts or []:
289+ try :
290+ # explicit dict with 'name'
291+ if isinstance (item , dict ) and "name" in item :
292+ name = item .get ("name" )
293+ freq = item .get ("angular_frequency" , item .get ("frequency" , 0.0 ))
294+ node = item .get ("node_factor" , item .get ("node" , 1.0 ))
295+ eqa = item .get (
296+ "earth_equilibrium_argument" , item .get ("eqa" , item .get ("phase" , 0.0 ))
297+ )
298+ out .append (
299+ {
300+ "name" : name ,
301+ "angular_frequency" : freq ,
302+ "node_factor" : node ,
303+ "earth_equilibrium_argument" : eqa ,
304+ }
305+ )
306+ continue
307+
308+ # mapping {NAME: {...}} or {NAME: [...]}
309+ if isinstance (item , dict ):
310+ key = next (iter (item ))
311+ vals = item [key ]
312+ name = key
313+
314+ if isinstance (vals , dict ):
315+ freq = vals .get ("angular_frequency" , vals .get ("frequency" , 0.0 ))
316+ node = vals .get ("node_factor" , vals .get ("node" , 1.0 ))
317+ eqa = vals .get (
318+ "earth_equilibrium_argument" , vals .get ("eqa" , vals .get ("phase" , 0.0 ))
319+ )
320+ out .append (
321+ {
322+ "name" : name ,
323+ "angular_frequency" : freq ,
324+ "node_factor" : node ,
325+ "earth_equilibrium_argument" : eqa ,
326+ }
327+ )
328+ continue
329+
330+ if isinstance (vals , (list , tuple )):
331+ if len (vals ) >= 3 :
332+ freq , node , eqa = vals [0 ], vals [1 ], vals [2 ]
333+ else :
334+ # partial lists: fill with defaults
335+ freq = vals [0 ] if len (vals ) > 0 else 0.0
336+ node = vals [1 ] if len (vals ) > 1 else 1.0
337+ eqa = vals [- 1 ] if len (vals ) > 0 else 0.0
338+ out .append (
339+ {
340+ "name" : name ,
341+ "angular_frequency" : freq ,
342+ "node_factor" : node ,
343+ "earth_equilibrium_argument" : eqa ,
344+ }
345+ )
346+ continue
347+
348+ # vals is scalar (frequency) or unsupported -> try to use as frequency
349+ if isinstance (vals , (int , float )):
350+ out .append (
351+ {
352+ "name" : name ,
353+ "angular_frequency" : vals ,
354+ "node_factor" : 1.0 ,
355+ "earth_equilibrium_argument" : 0.0 ,
356+ }
357+ )
358+ continue
359+
360+ # otherwise skip
361+ continue
362+
363+ # plain string -> treat as name with defaults
364+ if isinstance (item , str ):
365+ out .append (
366+ {
367+ "name" : item ,
368+ "angular_frequency" : 0.0 ,
369+ "node_factor" : 1.0 ,
370+ "earth_equilibrium_argument" : 0.0 ,
371+ }
372+ )
373+ continue
374+
375+ except Exception :
376+ # skip malformed entries
377+ continue
378+
379+ return out
380+
381+
382+ def write_bctides (self , bctides_file ):
383+
384+ with open (bctides_file , "w" ) as outf :
385+ tt = self .date .strftime ("%Y-%m-%d %H:%M" )
386+ outf .write (tt )
387+ outf .write ("\n " )
388+ # normalize tidal-constituent input formats so downstream code can
389+ # normalize tidal-constituent input formats so downstream code can
390+ # expect a list of dicts with explicit keys.
391+
275392
276393 if self .earth_tidals and "tidal_constituents" in self .earth_tidals :
277394 self .earth_tidals ["tidal_constituents" ] = _norm_earth (
0 commit comments