1616
1717
1818def read_smscg_dfs (file_dir , time_basis = None ):
19+ """Reads the three time history files for the boat lock, flashboards, and radial gates into pandas DataFrames."""
1920 boat = read_th (
2021 os .path .join (file_dir , "montezuma_boat_lock.th" ), time_basis = time_basis
2122 )
23+ boatcol = ["install" , "ndup" , "op_down" , "op_up" , "elev" ,"width" ]
24+ if boat .shape [1 ] == 7 :
25+ boatcol = boatcol + ["__comment__" ]
26+ boat .columns = boatcol
27+
2228 flash = read_th (os .path .join (file_dir , "montezuma_flash.th" ), time_basis = time_basis )
29+ flashcol = ["install" , "ndup" , "op_down" , "op_up" , "elev" ,"width" ]
30+ if flash .shape [1 ] == 7 :
31+ flashcol = flashcol + ["__comment__" ]
32+ flash .columns = flashcol
33+
2334 radial = read_th (
2435 os .path .join (file_dir , "montezuma_radial.th" ), time_basis = time_basis
2536 )
37+ radialcol = ["install" , "ndup" , "op_down" , "op_up" , "elev" ,"width" ,"height" ]
38+ if radial .shape [1 ] == 8 :
39+ radialcol = radialcol + ["__comment__" ]
40+ radial .columns = radialcol
41+
42+ for df in [boat , flash , radial ]:
43+ for col in ("install" , "ndup" ):
44+ if col in df .columns :
45+ df [col ] = df [col ].astype ("Int64" )
46+ if "__comment__" in df .columns :
47+ df ["__comment__" ] = df ["__comment__" ].astype ("string" )
2648
2749 return boat , flash , radial
2850
@@ -42,36 +64,39 @@ def elapse_smscg_dfs(smscg_dfs, params):
4264
4365
4466def align_dfs (boat , flash , radial ):
45- """Ensures alignment of the three dataframes and fills to radial index"""
46- org_indices = sorted (set (pd .concat ([boat , flash , radial ]).index ))
47- boat = extend_idx (boat , org_indices [0 ], org_indices [- 1 ])
48- flash = extend_idx (flash , org_indices [0 ], org_indices [- 1 ])
49- radial = extend_idx (radial , org_indices [0 ], org_indices [- 1 ])
50-
51- boat = boat .resample ("1min" ).asfreq ().ffill ()
52- flash = flash .resample ("1min" ).asfreq ().ffill ()
53- radial = radial .resample ("1min" ).asfreq ().ffill ()
54-
55- # Get rid of unecessary (unchanged) timestamps
56- boat = boat .loc [org_indices ]
57- flash = flash .loc [org_indices ]
58- radial = radial .loc [org_indices ]
67+ """
68+ Align three step-function-like time series on the union of their timestamps,
69+ using forward-fill semantics (no regular resampling grid).
70+
71+ Assumptions:
72+ - DatetimeIndex, sorted, unique (duplicates handled elsewhere).
73+ - Values represent state that persists until the next timestamp.
74+ - You want values defined over the full union span, so we pad each series
75+ to the global start/end using extend_idx().
76+
77+ Returns
78+ -------
79+ list[pd.DataFrame]
80+ [boat_aligned, flash_aligned, radial_aligned], each indexed by the union
81+ of original timestamps across all three inputs.
82+ """
83+ # Union of all original timestamps (these are the only times we will keep)
84+ idx = pd .Index (pd .concat ([boat , flash , radial ]).index ).unique ().sort_values ()
85+
86+ dfs = [boat , flash , radial ]
87+ start = max (df .index .min () for df in dfs )
88+ end = min (df .index .max () for df in dfs )
89+
90+ idx = idx [(idx >= start ) & (idx <= end )]
91+
92+ # Reindex to the union timestamps and forward-fill within each dataframe.
93+ # (ffill fills NaNs introduced by reindexing; start is guaranteed by extend_idx)
94+ boat = boat .reindex (idx ).ffill ()
95+ flash = flash .reindex (idx ).ffill ()
96+ radial = radial .reindex (idx ).ffill ()
5997 return [boat , flash , radial ]
6098
6199
62- def extend_idx (df , start , end ):
63- start_row = df .iloc [0 ].to_frame ().T
64- if start_row .index [0 ] > start :
65- start_row .index = [start ]
66- df = pd .concat ([start_row , df ])
67- end_row = df .iloc [- 1 ].to_frame ().T
68- if end_row .index [0 ] < end :
69- end_row .index = [end ]
70- df = pd .concat ([df , end_row ])
71-
72- return df
73-
74-
75100def compare_matches (matches , matches_hist ):
76101 """Compares two boolean series and returns a series that is True only where matches is True and matches_hist is False."""
77102
@@ -89,10 +114,10 @@ def compare_matches(matches, matches_hist):
89114
90115
91116@pytest .mark .prerun
92- def test_smscg_boatlock (sim_dir , params , elapse_smscg_dfs , historical_gates ):
117+ def test_smscg_boatlock (sim_dir , params , smscg_dfs , historical_gates ):
93118 """Checks that the boatlock is open whenever the radial gates are operated tidally (op_up=0)"""
94119
95- boat , flash , radial = elapse_smscg_dfs
120+ boat , flash , radial = smscg_dfs
96121
97122 # Compare the 4th column (op_up) of `boat` with `radial`
98123 matches = (radial .iloc [:, 3 ] == 0 ) & (boat .iloc [:, 3 ] == 0 )
@@ -125,10 +150,10 @@ def test_smscg_boatlock(sim_dir, params, elapse_smscg_dfs, historical_gates):
125150
126151
127152@pytest .mark .prerun
128- def test_smscg_flash (sim_dir , params , elapse_smscg_dfs , historical_gates ):
153+ def test_smscg_flash (sim_dir , params , smscg_dfs , historical_gates ):
129154 """Checks that the flashboards are closed when the radial gates are operated tidally (op_up=0)"""
130155
131- boat , flash , radial = elapse_smscg_dfs
156+ boat , flash , radial = smscg_dfs
132157
133158 # Compare the 4th column (op_up) of `flash` with `radial`, ensuring that when radial gates are tidally operated, the flashboards are closed
134159 matches = (radial .iloc [:, 3 ] == 0 ) & (flash .iloc [:, 3 ] != 0 )
@@ -160,10 +185,10 @@ def test_smscg_flash(sim_dir, params, elapse_smscg_dfs, historical_gates):
160185
161186
162187@pytest .mark .prerun
163- def test_smscg_radial_tides (sim_dir , params , elapse_smscg_dfs , historical_gates ):
188+ def test_smscg_radial_tides (sim_dir , params , smscg_dfs , historical_gates ):
164189 """Checks that the tidal radial operations make sense"""
165190
166- boat , flash , radial = elapse_smscg_dfs
191+ boat , flash , radial = smscg_dfs
167192
168193 # Compare the 3rd and 4th columns (op_down and op_up) of radial, ensuring tidal operation
169194 matches = (radial .iloc [:, 3 ] == 0 ) & (radial .iloc [:, 2 ] == 0 )
@@ -196,10 +221,10 @@ def test_smscg_radial_tides(sim_dir, params, elapse_smscg_dfs, historical_gates)
196221
197222
198223@pytest .mark .prerun
199- def test_smscg_radial_open (sim_dir , params , elapse_smscg_dfs , historical_gates ):
224+ def test_smscg_radial_open (sim_dir , params , smscg_dfs , historical_gates ):
200225 """Checks that the radial operations make sense"""
201226
202- boat , flash , radial = elapse_smscg_dfs
227+ boat , flash , radial = smscg_dfs
203228
204229 # Compare the 3rd and 4th columns (op_down and op_up) of radial, ensuring tidal operation
205230 matches = (radial .iloc [:, 3 ] == 1 ) & (radial .iloc [:, 2 ] != 1 )
0 commit comments