Skip to content

Commit 92a8812

Browse files
committed
Function refactoring
Move handling of constituents vals to a single member function
1 parent 207a77b commit 92a8812

1 file changed

Lines changed: 116 additions & 85 deletions

File tree

schimpy/bctide.py

Lines changed: 116 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)