@@ -41,7 +41,7 @@ def test_calibration():
4141 }
4242
4343 # Objective configuration
44- objectives = {
44+ objective_config = {
4545 'channel_sd_mon.txt' : {
4646 'sim_col' : 'flo_out' ,
4747 'obs_col' : 'mean' ,
@@ -84,7 +84,7 @@ def test_calibration():
8484 txtinout_dir = tmp1_dir ,
8585 extract_data = extract_data ,
8686 observe_data = observe_data ,
87- objectives = objectives ,
87+ objective_config = objective_config ,
8888 algorithm = 'NSGA2' ,
8989 n_gen = 2 ,
9090 pop_size = 3
@@ -99,6 +99,23 @@ def test_calibration():
9999 assert os .path .exists (os .path .join (tmp2_dir , 'optimization_history.json' ))
100100 assert os .path .exists (os .path .join (tmp2_dir , 'optimization_result.json' ))
101101
102+ # Pass: compute sensitivity indices directly
103+ with tempfile .TemporaryDirectory () as tmp3_dir :
104+ output = pySWATPlus .SensitivityAnalyzer ().simulation_and_indices (
105+ parameters = parameters ,
106+ sample_number = 1 ,
107+ sensim_dir = tmp3_dir ,
108+ txtinout_dir = tmp1_dir ,
109+ extract_data = extract_data ,
110+ observe_data = observe_data ,
111+ metric_config = objective_config
112+ )
113+
114+ assert isinstance (output , dict )
115+ assert len (output ) == 1
116+ assert os .path .exists (os .path .join (tmp3_dir , 'sensitivity_indices.json' ))
117+ assert os .path .exists (os .path .join (tmp3_dir , 'time.json' ))
118+
102119
103120def test_error_calibration ():
104121
@@ -137,7 +154,7 @@ def test_error_calibration():
137154 }
138155
139156 # Objective configuration
140- objectives = {
157+ objective_config = {
141158 'channel_sd_mon.txt' : {
142159 'sim_col' : 'flo_out' ,
143160 'obs_col' : 'mean' ,
@@ -161,7 +178,7 @@ def test_error_calibration():
161178 txtinout_dir = tmp1_dir ,
162179 extract_data = extract_data ,
163180 observe_data = observe_data ,
164- objectives = {
181+ objective_config = {
165182 'channel_sd_monn.txt' : {
166183 'sim_col' : 'flo_out' ,
167184 'obs_col' : 'mean' ,
@@ -172,7 +189,28 @@ def test_error_calibration():
172189 n_gen = 2 ,
173190 pop_size = 5
174191 )
175- assert exc_info .value .args [0 ] == 'Mismatch of key names. Ensure extract_data, observe_data, and objectives have identical top-level keys.'
192+ assert 'Top-level keys mismatch' in exc_info .value .args [0 ]
193+
194+ # Error: PBIAS is not allowed as an indicator name
195+ with pytest .raises (Exception ) as exc_info :
196+ pySWATPlus .Calibration (
197+ parameters = parameters ,
198+ calsim_dir = tmp2_dir ,
199+ txtinout_dir = tmp1_dir ,
200+ extract_data = extract_data ,
201+ observe_data = observe_data ,
202+ objective_config = {
203+ 'channel_sd_mon.txt' : {
204+ 'sim_col' : 'flo_out' ,
205+ 'obs_col' : 'mean' ,
206+ 'indicator' : 'PBIAS'
207+ }
208+ },
209+ algorithm = 'NSGA2' ,
210+ n_gen = 2 ,
211+ pop_size = 5
212+ )
213+ assert exc_info .value .args [0 ] == 'Indicator "PBIAS" is invalid in objective_config; it lacks a defined optimization direction'
176214
177215 # Error: invalid value type of top-key in observe_data
178216 with pytest .raises (Exception ) as exc_info :
@@ -184,7 +222,7 @@ def test_error_calibration():
184222 observe_data = {
185223 'channel_sd_mon.txt' : []
186224 },
187- objectives = objectives ,
225+ objective_config = objective_config ,
188226 algorithm = 'NSGA2' ,
189227 n_gen = 2 ,
190228 pop_size = 5
@@ -203,7 +241,7 @@ def test_error_calibration():
203241 'date_format' : '%Y-%m-%d'
204242 }
205243 },
206- objectives = objectives ,
244+ objective_config = objective_config ,
207245 algorithm = 'NSGA2' ,
208246 n_gen = 2 ,
209247 pop_size = 5
@@ -223,7 +261,7 @@ def test_error_calibration():
223261 'date_formatt' : '%Y-%m-%d'
224262 }
225263 },
226- objectives = objectives ,
264+ objective_config = objective_config ,
227265 algorithm = 'NSGA2' ,
228266 n_gen = 2 ,
229267 pop_size = 5
@@ -238,14 +276,14 @@ def test_error_calibration():
238276 txtinout_dir = tmp1_dir ,
239277 extract_data = extract_data ,
240278 observe_data = observe_data ,
241- objectives = {
279+ objective_config = {
242280 'channel_sd_mon.txt' : []
243281 },
244282 algorithm = 'NSGA2' ,
245283 n_gen = 2 ,
246284 pop_size = 5
247285 )
248- assert 'Expected "channel_sd_mon.txt" in "objectives" must be a dictionary' in exc_info .value .args [0 ]
286+ assert 'Expected "channel_sd_mon.txt" in objective_config must be a dictionary' in exc_info .value .args [0 ]
249287
250288 # Error: invalid length of sub-dictionary in objectives
251289 with pytest .raises (Exception ) as exc_info :
@@ -255,7 +293,7 @@ def test_error_calibration():
255293 txtinout_dir = tmp1_dir ,
256294 extract_data = extract_data ,
257295 observe_data = observe_data ,
258- objectives = {
296+ objective_config = {
259297 'channel_sd_mon.txt' : {
260298 'sim_col' : 'flo_out' ,
261299 'obs_col' : 'mean'
@@ -265,7 +303,7 @@ def test_error_calibration():
265303 n_gen = 2 ,
266304 pop_size = 5
267305 )
268- assert 'Length of "channel_sd_mon.txt" sub-dictionary in "objectives" must be 3' in exc_info .value .args [0 ]
306+ assert 'Length of "channel_sd_mon.txt" sub-dictionary in objective_config must be 3' in exc_info .value .args [0 ]
269307
270308 # Error: invalid sub-key in objectives
271309 with pytest .raises (Exception ) as exc_info :
@@ -275,7 +313,7 @@ def test_error_calibration():
275313 txtinout_dir = tmp1_dir ,
276314 extract_data = extract_data ,
277315 observe_data = observe_data ,
278- objectives = {
316+ objective_config = {
279317 'channel_sd_mon.txt' : {
280318 'sim_col' : 'flo_out' ,
281319 'obs_col' : 'mean' ,
@@ -286,7 +324,7 @@ def test_error_calibration():
286324 n_gen = 2 ,
287325 pop_size = 5
288326 )
289- assert 'Invalid sub-key "indicatorr" for "channel_sd_mon.txt" in "objectives" ' in exc_info .value .args [0 ]
327+ assert 'Invalid sub-key "indicatorr" for "channel_sd_mon.txt" in objective_config ' in exc_info .value .args [0 ]
290328
291329 # Error: invalid indicator in objectives
292330 with pytest .raises (Exception ) as exc_info :
@@ -296,7 +334,7 @@ def test_error_calibration():
296334 txtinout_dir = tmp1_dir ,
297335 extract_data = extract_data ,
298336 observe_data = observe_data ,
299- objectives = {
337+ objective_config = {
300338 'channel_sd_mon.txt' : {
301339 'sim_col' : 'flo_out' ,
302340 'obs_col' : 'mean' ,
@@ -307,7 +345,7 @@ def test_error_calibration():
307345 n_gen = 2 ,
308346 pop_size = 5
309347 )
310- assert 'Invalid "indicator" value "RMSEE" for "channel_sd_mon.txt" in "objectives" ' in exc_info .value .args [0 ]
348+ assert 'Invalid "indicator" value "RMSEE" for "channel_sd_mon.txt" in objective_config ' in exc_info .value .args [0 ]
311349
312350 # Error: invalid algorithm name
313351 with pytest .raises (Exception ) as exc_info :
@@ -317,9 +355,54 @@ def test_error_calibration():
317355 txtinout_dir = tmp1_dir ,
318356 extract_data = extract_data ,
319357 observe_data = observe_data ,
320- objectives = objectives ,
358+ objective_config = objective_config ,
321359 algorithm = 'NSGA' ,
322360 n_gen = 2 ,
323361 pop_size = 5
324362 ).parameter_optimization ()
325363 assert 'Invalid algorithm "NSGA"' in exc_info .value .args [0 ]
364+
365+ # Error: invalid algorithm for multiple objectives
366+ with pytest .raises (Exception ) as exc_info :
367+ pySWATPlus .Calibration (
368+ parameters = parameters ,
369+ calsim_dir = tmp2_dir ,
370+ txtinout_dir = tmp1_dir ,
371+ extract_data = {
372+ 'channel_sd_day.txt' : {
373+ 'has_units' : True ,
374+ 'apply_filter' : {'name' : ['cha561' ]}
375+ },
376+ 'channel_sd_mon.txt' : {
377+ 'has_units' : True ,
378+ 'ref_day' : 1 ,
379+ 'apply_filter' : {'name' : ['cha561' ]}
380+ }
381+ },
382+ observe_data = {
383+ 'channel_sd_day.txt' : {
384+ 'obs_file' : os .path .join (txtinout_dir , 'a_observe_discharge_monthly.csv' ),
385+ 'date_format' : '%Y-%m-%d'
386+ },
387+ 'channel_sd_mon.txt' : {
388+ 'obs_file' : os .path .join (txtinout_dir , 'a_observe_discharge_monthly.csv' ),
389+ 'date_format' : '%Y-%m-%d'
390+ }
391+ },
392+ objective_config = {
393+ 'channel_sd_day.txt' : {
394+ 'sim_col' : 'flo_out' ,
395+ 'obs_col' : 'mean' ,
396+ 'indicator' : 'NSE'
397+ },
398+ 'channel_sd_mon.txt' : {
399+ 'sim_col' : 'flo_out' ,
400+ 'obs_col' : 'mean' ,
401+ 'indicator' : 'RMSE'
402+ }
403+ },
404+ algorithm = 'DE' ,
405+ n_gen = 2 ,
406+ pop_size = 5
407+ ).parameter_optimization ()
408+ assert 'Algorithm "DE" cannot handle multiple objectives' in exc_info .value .args [0 ]
0 commit comments