def test_detachment_steady_no_precip_changer(clock_simple, grid_1, m_sp, n_sp, depression_finder, U, K): ncnblh = NotCoreNodeBaselevelHandler(grid_1, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_1, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": 0.001, "hydraulic_conductivity": 1.0, "depression_finder": depression_finder, "m_sp": m_sp, "n_sp": n_sp, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicVs(**params) for _ in range(300): model.run_one_step(1000) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes = (U / (params["water_erodibility"] * (actual_areas**params["m_sp"])))**(1.0 / params["n_sp"]) # assert actual and predicted slopes are the same. assert_array_almost_equal( actual_slopes[model.grid.core_nodes[1:-1]], predicted_slopes[model.grid.core_nodes[1:-1]], )
def test_two_class_writers(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model model = Basic( clock_08, almost_default_grid, save_first_timestep=False, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "class": [output_writer_class_a, output_writer_class_b] }, ) model.run() # assert things were done correctly truth_file = os.path.join(_TEST_DATA_DIR, "truth_ow_class_a.20.0.txt") test_file = get_output_filepath("ow_class_a.20.0.txt") assert filecmp(test_file, truth_file) is True truth_file = os.path.join(_TEST_DATA_DIR, "truth_ow_class_b.20.0.txt") test_file = get_output_filepath("ow_class_b.20.0.txt") assert filecmp(test_file, truth_file) is True model.remove_output_netcdfs() cleanup_files("ow_class_*.txt")
def test_diffusion_only(clock_simple, grid_1, U, Model): total_time = 5.0e6 step = 1000 ncnblh = NotCoreNodeBaselevelHandler(grid_1, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_1, "clock": clock_simple, "regolith_transport_parameter": 1, "water_erodibility": 0, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = Model(**params) nts = int(total_time / step) for _ in range(nts): model.run_one_step(1000) reference_node = 9 predicted_z = model.z[model.grid.core_nodes[reference_node]] - ( U / (2.0 * params["regolith_transport_parameter"])) * ( (model.grid.x_of_node - model.grid.x_of_node[model.grid.core_nodes[reference_node]])**2) # assert actual and predicted elevations are the same. assert_array_almost_equal( predicted_z[model.grid.core_nodes], model.z[model.grid.core_nodes], decimal=2, )
def test_channel_erosion(clock_simple, grid_2, m_sp, n_sp, depression_finder, U, solver): ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) phi = 0.1 F_f = 0.0 v_sc = 0.001 Kr = 0.001 Kt = 0.005 # construct dictionary. note that D is turned off here params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_upper": Kt, "water_erodibility_lower": Kr, "settling_velocity": v_sc, "sediment_porosity": phi, "fraction_fines": F_f, "solver": solver, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicHyRt(**params) for _ in range(4000): model.run_one_step(10) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["drainage_area"] rock_predicted_slopes = np.power( ((U * v_sc) / (Kr * np.power(actual_areas, m_sp))) + (U / (Kr * np.power(actual_areas, m_sp))), 1.0 / n_sp, ) till_predicted_slopes = np.power( ((U * v_sc) / (Kt * np.power(actual_areas, m_sp))) + (U / (Kt * np.power(actual_areas, m_sp))), 1.0 / n_sp, ) # assert actual and predicted slopes are the same for rock and till # portions. assert_array_almost_equal(actual_slopes[22:37], rock_predicted_slopes[22:37], decimal=3) # assert actual and predicted slopes are the same for rock and till # portions. assert_array_almost_equal(actual_slopes[82:97], till_predicted_slopes[82:97], decimal=3)
def test_steady_Ksp_no_precip_changer(clock_simple, grid_2, depression_finder, m_sp, n_sp): U = 0.0001 ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) Kr = 0.001 Kt = 0.005 Tr = 0.0001 Tt = 0.0005 params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_lower": Kr, "water_erodibility_upper": Kt, "water_erosion_rule_upper__threshold": Tt, "water_erosion_rule_lower__threshold": Tr, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicRtTh(**params) for _ in range(200): model.run_one_step(1000) actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] # note that since we have a smooth threshold, we do not have a true # analytical solution, but a bracket within wich we expect the actual # slopes to fall. rock_predicted_slopes_upper = ((U + Tr) / (Kr * (actual_areas**m_sp)))**(1.0 / n_sp) till_predicted_slopes_upper = ((U + Tt) / (Kt * (actual_areas**m_sp)))**(1.0 / n_sp) rock_predicted_slopes_lower = ((U + 0.0) / (Kr * (actual_areas**m_sp)))**(1.0 / n_sp) till_predicted_slopes_lower = ((U + 0.0) / (Kt * (actual_areas**m_sp)))**(1.0 / n_sp) # assert actual and predicted slopes are the same for rock and till # portions. assert np.all(actual_slopes[22:37] > rock_predicted_slopes_lower[22:37]) assert np.all(actual_slopes[22:37] < rock_predicted_slopes_upper[22:37]) assert np.all(actual_slopes[82:97] > till_predicted_slopes_lower[82:97]) assert np.all(actual_slopes[82:97] < till_predicted_slopes_upper[82:97])
def test_diffusion_only(clock_08, grid_4): U = 0.001 max_soil_production_rate = 0.002 soil_production_decay_depth = 0.2 regolith_transport_parameter = 1.0 soil_transport_decay_depth = 0.5 S_c = 0.2 ncnblh = NotCoreNodeBaselevelHandler( grid_4, modify_core_nodes=True, lowering_rate=-U ) # Construct dictionary. Note that stream power is turned off params = { "grid": grid_4, "clock": clock_08, "regolith_transport_parameter": regolith_transport_parameter, "soil_transport_decay_depth": soil_transport_decay_depth, "soil_production__maximum_rate": max_soil_production_rate, "soil_production__decay_depth": soil_production_decay_depth, "critical_slope": S_c, "water_erodibility": 0, "boundary_handlers": {"NotCoreNodeBaselevelHandler": ncnblh}, } # Construct and run model model = BasicChSa(**params) for _ in range(30000): model.run_one_step(clock_08.step) # test steady state soil depth actual_depth = model.grid.at_node["soil__depth"][30] predicted_depth = -soil_production_decay_depth * np.log( U / max_soil_production_rate ) assert_array_almost_equal(actual_depth, predicted_depth, decimal=2) # Construct actual and predicted slope at right edge of domain x = 8.5 * grid_4.dx qs = U * x nterms = 11 p = np.zeros(2 * nterms - 1) for k in range(1, nterms + 1): p[2 * k - 2] = ( regolith_transport_parameter * soil_transport_decay_depth * (1 - np.exp(-predicted_depth / soil_transport_decay_depth)) * (1 / (S_c ** (2 * (k - 1)))) ) p = np.fliplr([p])[0] p = np.append(p, qs) p_roots = np.roots(p) predicted_slope = np.abs(np.real(p_roots[-1])) actual_slope = np.abs( model.grid.at_node["topographic__steepest_slope"][39] ) assert_array_almost_equal(actual_slope, predicted_slope, decimal=1)
def test_channel_erosion( clock_simple, grid_1, m_sp, n_sp, depression_finder, U, K, solver, Model, param_name, ): ncnblh = NotCoreNodeBaselevelHandler(grid_1, modify_core_nodes=True, lowering_rate=-U) phi = 0.1 F_f = 0.0 v_sc = 0.001 # construct dictionary. note that D is turned off here params = { "grid": grid_1, "clock": clock_simple, "regolith_transport_parameter": 0.0, param_name: K, "settling_velocity": v_sc, "sediment_porosity": phi, "fraction_fines": F_f, "solver": solver, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = Model(**params) for _ in range(2000): model.run_one_step(10) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes = np.power( ((U * v_sc) / (K * np.power(actual_areas, m_sp))) + (U / (K * np.power(actual_areas, m_sp))), 1.0 / n_sp, ) # assert actual and predicted slopes are the same. assert_array_almost_equal( actual_slopes[model.grid.core_nodes[1:-1]], predicted_slopes[model.grid.core_nodes[1:-1]], decimal=4, )
def test_stochastic_duration_rainfall_means(): """Test option with stochastic duration. Test is simply to get the correct total cumulative rain depth. """ U = 0.0001 K = 0.0001 m = 1.0 n = 1.0 grid = RasterModelGrid((3, 6), xy_spacing=100.0) grid.set_closed_boundaries_at_grid_edges(True, False, True, False) grid.add_zeros("node", "topographic__elevation") ncnblh = NotCoreNodeBaselevelHandler(grid, modify_core_nodes=True, lowering_rate=-U) clock = Clock(step=200, stop=400) # construct dictionary. note that D is turned off here params = { "grid": grid, "clock": clock, "regolith_transport_parameter": 0.0, "water_erodibility": K, "m_sp": m, "n_sp": n, "opt_stochastic_duration": True, "record_rain": True, "mean_storm_duration": 1.0, "mean_interstorm_duration": 1.0, "infiltration_capacity": 1.0, "random_seed": 3141, "mean_storm_depth": 1.0, "output_interval": 401, "depression_finder": "DepressionFinderAndRouter", "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicSt(**params) model.run() cum_rain_depth = np.sum( np.array(model.rain_record["event_duration"]) * np.array(model.rain_record["rainfall_rate"])) assert_equal(np.round(cum_rain_depth), 200.0) os.remove("storm_sequence.txt") fs = glob.glob(model._out_file_name + "*.nc") for f in fs: os.remove(f)
def test_multiple_frequencies(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model common_interval = 2.0 uncommon_interval = 5.0 model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "common-ow": { "class": OWStaticWrapper, "kwargs": { "add_id": False, "intervals": common_interval, }, }, "uncommon-ow": { "class": OWStaticWrapper, "kwargs": { "add_id": False, "intervals": uncommon_interval, }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) model.run() for name, interval in [ ("common-ow", common_interval), ("uncommon-ow", uncommon_interval), ]: for output_time in itertools.count(0.0, interval): if output_time > clock_08.stop: # Break the infinite iterator at the clock stop time break # assert things were done correctly filename = f"{name}_time-{output_time:012.1f}.txt" truth_file = os.path.join(_TEST_DATA_DIR, f"truth_{filename}") test_file = get_output_filepath(filename) assert filecmp(test_file, truth_file) is True model.remove_output()
def test_steady_Ksp_no_precip_changer_no_thresh_change( clock_simple, grid_2, U, K, m_sp, n_sp, depression_finder, threshold, thresh_change_per_depth, ): ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": K, "m_sp": m_sp, "n_sp": n_sp, "water_erosion_rule__threshold": threshold, "water_erosion_rule__thresh_depth_derivative": thresh_change_per_depth, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicDd(**params) for _ in range(200): model.run_one_step(1000) # construct actual and predicted slopes # note that since we have a smooth threshold, we do not have a true # analytical solution, but a bracket within wich we expect the actual # slopes to fall. actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes_upper = ((U + threshold) / (K * (actual_areas**m_sp)))**(1.0 / n_sp) predicted_slopes_lower = ((U + 0.0) / (K * (actual_areas**m_sp)))**(1.0 / n_sp) # assert actual and predicted slopes are in the correct range for the # slopes. assert np.all(actual_slopes[model.grid.core_nodes[1:-1]] > predicted_slopes_lower[model.grid.core_nodes[1:-1]]) assert np.all(actual_slopes[model.grid.core_nodes[1:-1]] < predicted_slopes_upper[model.grid.core_nodes[1:-1]])
def test_rock_till_steady_no_precip_changer_ChRtTh( clock_simple, Model, grid_2, m_sp, n_sp, depression_finder, U, Kr, Kt, flow_director, ): ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_lower": Kr, "water_erodibility_upper": Kt, "depression_finder": depression_finder, "flow_director": flow_director, "m_sp": m_sp, "n_sp": n_sp, "water_erosion_rule_upper__threshold": 0.0000001, "water_erosion_rule_lower__threshold": 0.0000001, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = Model(**params) for _ in range(200): model.run_one_step(1000) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] rock_predicted_slopes = (U / (Kr * (actual_areas**m_sp)))**(1.0 / n_sp) till_predicted_slopes = (U / (Kt * (actual_areas**m_sp)))**(1.0 / n_sp) # assert actual and predicted slopes are the same for rock and till # portions. assert_array_almost_equal(actual_slopes[22:37], rock_predicted_slopes[22:37], decimal=4) assert_array_almost_equal(actual_slopes[82:97], till_predicted_slopes[82:97], decimal=4)
def test_static_default( clock_08, almost_default_grid, times, intervals, correct_times, ): static_kwargs = { "add_id": False, "save_first_timestep": True, "save_last_timestep": True, } if times is not None: static_kwargs["times"] = times if intervals is not None: static_kwargs["intervals"] = intervals ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "out-of-phase-ow": { "class": OWStaticWrapper, "kwargs": static_kwargs, }, }, output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) static_ow = model.get_output_writer("out-of-phase-ow")[0] for t_int in correct_times: if t_int is None: print(f"checking: {t_int} is {static_ow.next_output_time}") assert static_ow.next_output_time is None else: print(f"checking: {float(t_int)} == {static_ow.next_output_time}") assert static_ow.next_output_time == float(t_int) static_ow.advance_iter() model.remove_output()
def test_steady_Kss_no_precip_changer( clock_simple, grid_2, U, K, m_sp, n_sp, depression_finder ): hydraulic_conductivity = 0.1 threshold = 0.01 ncnblh = NotCoreNodeBaselevelHandler( grid_2, modify_core_nodes=True, lowering_rate=-U ) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": K, "hydraulic_conductivity": hydraulic_conductivity, "m_sp": m_sp, "n_sp": n_sp, "water_erosion_rule__threshold": threshold, "depression_finder": depression_finder, "boundary_handlers": {"NotCoreNodeBaselevelHandler": ncnblh}, } # construct and run model model = BasicThVs(**params) for _ in range(200): model.run_one_step(1000) actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes_upper = ( (U + threshold) / (K * (actual_areas ** m_sp)) ) ** (1.0 / n_sp) predicted_slopes_lower = ((U + 0.0) / (K * (actual_areas ** m_sp))) ** ( 1.0 / n_sp ) # assert actual and predicted slopes are in the correct range for the # slopes. assert np.all( actual_slopes[model.grid.core_nodes[1:-1]] > predicted_slopes_lower[model.grid.core_nodes[1:-1]] ) assert np.all( actual_slopes[model.grid.core_nodes[1:-1]] < predicted_slopes_upper[model.grid.core_nodes[1:-1]] )
def test_out_of_phase_interval_last(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model interval = 19.5 warning_msg_sample = "time that is not divisible by the model step" with pytest.warns(UserWarning, match=warning_msg_sample): model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "out-of-phase-ow": { "class": OWStaticWrapper, "kwargs": { "add_id": False, "intervals": interval, }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) model.run() # The model should still run fine, but any out of phase output times are # delayed to the next step. # Check that there is no file for t=19.5 bad_filename = f"out-of-phase-ow_time-{19.5:012.1f}.txt" bad_filepath = get_output_filepath(bad_filename) assert not os.path.isfile(bad_filepath), f"{bad_filepath} should not exist" # Check that the output that should exist for time_int in [0, 20]: # instead of [0, 19.5, 20] filename = f"out-of-phase-ow_time-{float(time_int):012.1f}.txt" truth_file = os.path.join(_TEST_DATA_DIR, f"truth_{filename}") test_file = get_output_filepath(filename) assert filecmp(test_file, truth_file) is True model.remove_output()
def test_steady_Ksp_no_precip_changer_no_thresh_change( clock_simple, grid_2, U, Kr, Kt, m_sp, n_sp, depression_finder, threshold, thresh_change_per_depth, ): ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_lower": Kr, "water_erodibility_upper": Kt, "water_erosion_rule__threshold": threshold, "water_erosion_rule__thresh_depth_derivative": thresh_change_per_depth, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicDdRt(**params) for _ in range(100): model.run_one_step(1000) actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["drainage_area"] rock_predicted_slopes = (U / (Kr * (actual_areas**m_sp)))**(1.0 / n_sp) till_predicted_slopes = (U / (Kt * (actual_areas**m_sp)))**(1.0 / n_sp) # assert actual slopes are steeper than simple stream power prediction assert np.all(actual_slopes[22:37] > rock_predicted_slopes[22:37]) # assert actual slopes are steeper than simple stream power prediction assert np.all(actual_slopes[82:97] > till_predicted_slopes[82:97])
def test_Aeff(clock_simple, grid_2, K, U, Model): m_sp = 0.5 n_sp = 1.0 ncnblh = NotCoreNodeBaselevelHandler( grid_2, modify_core_nodes=True, lowering_rate=-U ) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": K, "hydraulic_conductivity": 0.02, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": "DepressionFinderAndRouter", "boundary_handlers": {"NotCoreNodeBaselevelHandler": ncnblh}, } # construct and run model model = Model(**params) for _ in range(1000): model.run_one_step(10) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["drainage_area"] alpha = ( params["hydraulic_conductivity"] * grid_2.at_node["soil__depth"][0] * grid_2.dx / grid_2.at_node["rainfall__flux"][0] ) A_eff_predicted = actual_areas * np.exp( -(-alpha * actual_slopes) / actual_areas ) # assert aeff internally calculated correclty assert_array_almost_equal( model.grid.at_node["surface_water__discharge"][model.grid.core_nodes], A_eff_predicted[model.grid.core_nodes], decimal=1, )
def test_diffusion_only(clock_09, grid_4, Model): U = 0.0005 D = 1.0 S_c = 0.3 ncnblh = NotCoreNodeBaselevelHandler(grid_4, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_4, "clock": clock_09, "regolith_transport_parameter": D, "water_erodibility_lower": 0, "water_erodibility_upper": 0, "critical_slope": S_c, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # Construct and run model model = Model(**params) for _ in range(20000): model.run_one_step(clock_09.step) # Construct actual and predicted slope at right edge of domain x = 8.5 * grid_4.dx qs = U * x nterms = 7 p = np.zeros(2 * nterms - 1) for k in range(1, nterms + 1): p[2 * k - 2] = D * (1 / (S_c**(2 * (k - 1)))) p = np.fliplr([p])[0] p = np.append(p, qs) p_roots = np.roots(p) predicted_slope = np.abs(np.real(p_roots[-1])) # print(predicted_slope) actual_slope = np.abs( model.grid.at_node["topographic__steepest_slope"][39]) assert_array_almost_equal(actual_slope, predicted_slope, decimal=2)
def test_steady_Kss_no_precip_changer(clock_simple, grid_2, U, Kr, Kt, m_sp, n_sp, depression_finder): hydraulic_conductivity = 0.1 ncnblh = NotCoreNodeBaselevelHandler(grid_2, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_lower": Kr, "water_erodibility_upper": Kt, "hydraulic_conductivity": hydraulic_conductivity, "m_sp": m_sp, "n_sp": n_sp, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicRtVs(**params) for _ in range(100): model.run_one_step(1000) actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] rock_predicted_slopes = (U / (Kr * (actual_areas**m_sp)))**(1.0 / n_sp) till_predicted_slopes = (U / (Kt * (actual_areas**m_sp)))**(1.0 / n_sp) # assert actual and predicted slopes are the same for rock and till. assert_array_almost_equal(actual_slopes[22:37], rock_predicted_slopes[22:37]) # assert actual and predicted slopes are the same for rock and till. assert_array_almost_equal(actual_slopes[82:97], till_predicted_slopes[82:97])
def test_save_first_last_and_multiple_times(clock_08, almost_default_grid): """Test save_first_timestep, save_last_timestep, and saving at multiple timesteps.""" ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "function": [output_writer_function_a, output_writer_function_b], "class": [output_writer_class_a, output_writer_class_b], }, output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, save_first_timestep=True, save_last_timestep=True, ) model.run() for t in ["0.0", "6.0", "12.0", "18.0", "20.0"]: # assert things were done correctly filename_bases = [ f"ow_func_a.{t}.txt", f"ow_func_b.{t}.txt", f"ow_class_a.{t}.txt", f"ow_class_b.{t}.txt", ] for filename_base in filename_bases: truth_file = os.path.join(_TEST_DATA_DIR, f"truth_{filename_base}") test_file = os.path.join(os.curdir, "output", filename_base) assert filecmp(test_file, truth_file) is True model.remove_output_netcdfs() cleanup_files(get_output_filepath("ow_func_*.txt")) cleanup_files(get_output_filepath("ow_class_*.txt"))
def test_custom_iter(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "fibonnaci": { "class": OWGenericWrapper, "kwargs": { "add_id": False, "times_iter": fibonnaci(), }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) with pytest.warns(OutputIteratorSkipWarning): model.run() for time_int in [0, 1, 2, 3, 5, 8, 13, 20]: # Note: the second 1 in the fib sequence will be skipped # assert things were done correctly filename = f"fibonnaci_time-{float(time_int):012.1f}.txt" truth_file = os.path.join(_TEST_DATA_DIR, f"truth_{filename}") test_file = get_output_filepath(filename) assert filecmp(test_file, truth_file) is True model.remove_output()
def test_steady_without_stochastic_duration( clock_simple, depression_finder, U, K, grid_2, m_sp, n_sp ): ncnblh = NotCoreNodeBaselevelHandler( grid_2, modify_core_nodes=True, lowering_rate=-U ) grid_2.at_node["soil__depth"][:] = 1.0e-9 # construct dictionary. note that D is turned off here params = { "grid": grid_2, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": K, "m_sp": m_sp, "n_sp": n_sp, "hydraulic_conductivity": 1.0e-9, "number_of_sub_time_steps": 100, "rainfall_intermittency_factor": 1.0, "rainfall__mean_rate": 1.0, "rainfall__shape_factor": 1.0, "random_seed": 3141, "depression_finder": depression_finder, "boundary_handlers": {"NotCoreNodeBaselevelHandler": ncnblh}, } # construct and run model model = BasicStVs(**params) for _ in range(100): model.run_one_step(1.0) # construct actual and predicted slopes ic = model.grid.core_nodes[1:-1] # "inner" core nodes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes = (U / (K * (actual_areas ** m_sp))) ** (1.0 / n_sp) assert_array_almost_equal( actual_slopes[ic], predicted_slopes[ic], decimal=4 )
def test_out_of_phase_interval_warns(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model times = [ 0.0, 1.0, 1.01, # next time is set to 2. Divisible warning 1.02, # new time < 2. Starting skips. Skipping warning 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 3.00, # Finds suitable time on 10th skip. ] model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "out-of-phase-skipping-ow": { "class": OWStaticWrapper, "kwargs": { "add_id": False, "times": times, }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) skip_warning_msg = "next time that is less than or equal to the current" divisible_warning_msg = "time that is not divisible by the model step" # Copied from ErosionModel.run() so I can control the steps model._itters = [] if model.save_first_timestep: model.iteration = 0 model._itters.append(0) model.write_output() model.iteration = 1 def run_one_iteration(): time_now = model._model_time next_run_pause = min( model.next_output_time, model.clock.stop, ) assert next_run_pause > time_now print(f"Iteration {model.iteration:05d} Model time {model.model_time}") print( f" Run for {next_run_pause - time_now}, (step = {model.clock.step})" ) model.run_for(model.clock.step, next_run_pause - time_now) time_now = model._model_time model._itters.append(model.iteration) model.write_output() model.iteration += 1 print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 0.0 and model.next_output_time == 1.0 warning_msg_sample = "time that is not divisible by the model step" with pytest.warns(UserWarning, match=warning_msg_sample): run_one_iteration() # times_iter returns 1.01, which is then delayed to 2.0 print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 1.00 and model.next_output_time == 2.0 # time_iter returns 1.02, which triggers skips but eventually finds 3.0 with pytest.warns(UserWarning) as all_warnings: run_one_iteration() print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 2.0 and model.next_output_time == 3.0 ignored_warnings = [RuntimeWarning] for warn_info in all_warnings: warn_type = warn_info.category if warn_type in ignored_warnings: continue try: assert warn_type == UserWarning message = warn_info.message.args[0] is_skip_warning = skip_warning_msg in message is_divisible_warning = divisible_warning_msg in message assert is_skip_warning or is_divisible_warning except AssertionError: print(warn_info) # So I can see other warnings raise run_one_iteration() # times_iter returns 20.0 print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 3.0 and model.next_output_time == 20.0 run_one_iteration() # Model finishes print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 20.0 and model.next_output_time == np.inf # now that the model is finished running, execute finalize. model.finalize() # Check that there is a file for t=3.0 good_filename = f"out-of-phase-skipping-ow_time-{3.0:012.1f}.txt" good_filepath = get_output_filepath(good_filename) assert os.path.isfile(good_filepath), f"{good_filepath} should exist" model.remove_output()
def test_diffusion_only(clock_simple, grid_4_smaller, Model, water_params): grid_4 = grid_4_smaller U = 0.001 max_soil_production_rate = 0.002 soil_production_decay_depth = 0.2 regolith_transport_parameter = 1.0 soil_transport_decay_depth = 0.5 grid_4.at_node["soil__depth"][:] = 0.0 ncnblh = NotCoreNodeBaselevelHandler( grid_4, modify_core_nodes=True, lowering_rate=-U ) params = { "grid": grid_4, "clock": clock_simple, "regolith_transport_parameter": regolith_transport_parameter, "soil_transport_decay_depth": soil_transport_decay_depth, "soil_production__maximum_rate": max_soil_production_rate, "soil_production__decay_depth": soil_production_decay_depth, "boundary_handlers": {"NotCoreNodeBaselevelHandler": ncnblh}, } dx = grid_4.dx for p in water_params: params[p] = water_params[p] # make predicted depth: predicted_depth = -soil_production_decay_depth * np.log( U / max_soil_production_rate ) # maek predicted profile. domain = np.arange(0, max(grid_4.x_of_node + dx), dx) half_domain = np.arange(0, max(domain) / 2.0 + dx, dx) one_minus_h_hstar = 1 - np.exp( -predicted_depth / soil_transport_decay_depth ) half_domain_z = ( -(half_domain ** 2) * U / ( regolith_transport_parameter * soil_transport_decay_depth * 2.0 * one_minus_h_hstar ) ) steady_z_profile = np.concatenate( (np.flipud(half_domain_z), half_domain_z[1:]) ) predicted_profile = steady_z_profile - np.min(steady_z_profile) test_dt = 1000 # construct and run model model = Model(**params) for i in range(20000): model.run_one_step(15) # at intervals of test_dt, see if tests pass, thus we break out of loop # once the tests pass. if i % test_dt == 0: try: # test steady state soil depthf actual_depth = model.grid.at_node["soil__depth"][14] assert_array_almost_equal( actual_depth, predicted_depth, decimal=2 ) # test steady state slope actual_profile = model.grid.at_node["topographic__elevation"][ 11:22 ] assert_array_almost_equal( actual_profile, predicted_profile, decimal=1 ) # if pass, then break. break except AssertionError: pass
def test_diffusion_only(clock_simple, grid_4, Model, water_params): U = 0.001 max_soil_production_rate = 0.002 soil_production_decay_depth = 0.2 regolith_transport_parameter = 1.0 soil_transport_decay_depth = 0.5 grid_4.at_node["soil__depth"][:] = 0.0 ncnblh = NotCoreNodeBaselevelHandler(grid_4, modify_core_nodes=True, lowering_rate=-U) params = { "grid": grid_4, "clock": clock_simple, "regolith_transport_parameter": regolith_transport_parameter, "soil_transport_decay_depth": soil_transport_decay_depth, "soil_production__maximum_rate": max_soil_production_rate, "soil_production__decay_depth": soil_production_decay_depth, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } for p in water_params: params[p] = water_params[p] # construct and run model model = Model(**params) for _ in range(20000): model.run_one_step(15) dx = grid_4.dx # test steady state soil depthf actual_depth = model.grid.at_node["soil__depth"][28] predicted_depth = -soil_production_decay_depth * np.log( U / max_soil_production_rate) assert_array_almost_equal(actual_depth, predicted_depth, decimal=2) # test steady state slope actual_profile = model.grid.at_node["topographic__elevation"][21:42] domain = np.arange(0, max(model.grid.node_x + dx), dx) half_domain = np.arange(0, max(domain) / 2.0 + dx, dx) one_minus_h_hstar = 1 - np.exp( -predicted_depth / soil_transport_decay_depth) half_domain_z = (-half_domain**2 * U / (regolith_transport_parameter * soil_transport_decay_depth * 2.0 * one_minus_h_hstar)) steady_z_profile = np.concatenate( (np.flipud(half_domain_z), half_domain_z[1:])) predicted_profile = steady_z_profile - np.min(steady_z_profile) assert_array_almost_equal(actual_profile, predicted_profile, decimal=1)
def test_out_of_phase_interval_fails(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model times = [ 0.0, 1.0, 1.01, # next time is set to 2. Divisible warning 1.02, # new time < 2. Starting skips. Skipping warning 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 2.00, # 10 skips. Loop exits. Raises Assertion Error 3.0, # Does not get this far ] model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "out-of-phase-ow-fails": { "class": OWStaticWrapper, "kwargs": { "add_id": False, "times": times, }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", save_first_timestep=True, save_last_timestep=True, ) skip_warning_msg = "next time that is less than or equal to the current" divisible_warning_msg = "time that is not divisible by the model step" # Copied from ErosionModel.run() so I can control the steps model._itters = [] if model.save_first_timestep: model.iteration = 0 model._itters.append(0) model.write_output() model.iteration = 1 def run_one_iteration(): time_now = model._model_time next_run_pause = min( # time_now + model.output_interval, model.clock.stop, model.next_output_time, model.clock.stop, ) assert next_run_pause > time_now print(f"Iteration {model.iteration:05d} Model time {model.model_time}") print( f" Run for {next_run_pause - time_now}, (step = {model.clock.step})" ) model.run_for(model.clock.step, next_run_pause - time_now) time_now = model._model_time model._itters.append(model.iteration) model.write_output() model.iteration += 1 print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 0.0 and model.next_output_time == 1.0 warning_msg_sample = "time that is not divisible by the model step" with pytest.warns(UserWarning, match=warning_msg_sample): run_one_iteration() # times_iter returns 1.01, which is then delayed to 2.0 print(f"t={model.model_time}, nt={model.next_output_time}") assert model.model_time == 1.00 and model.next_output_time == 2.0 # time_iter returns 1.02, which triggers skips and eventually fails with pytest.warns(UserWarning) as all_warnings, pytest.raises( AssertionError): run_one_iteration() ignored_warnings = [RuntimeWarning] for warn_info in all_warnings: warn_type = warn_info.category if warn_type in ignored_warnings: continue try: assert warn_type == UserWarning message = warn_info.message.args[0] is_skip_warning = skip_warning_msg in message is_divisible_warning = divisible_warning_msg in message assert is_skip_warning or is_divisible_warning except AssertionError: print(warn_info) # So I can see other warnings raise model.remove_output()
def test_channel_erosion(clock_simple, grid_1, m_sp, n_sp, depression_finder, U, solver): ncnblh = NotCoreNodeBaselevelHandler(grid_1, modify_core_nodes=True, lowering_rate=-U) phi = 0.1 F_f = 0.0 v_sc = 0.001 K_rock_sp = 0.001 K_sed_sp = 0.005 sp_crit_br = 0 sp_crit_sed = 0 H_star = 0.1 soil_transport_decay_depth = 1 soil_production__maximum_rate = 0 soil_production__decay_depth = 0.5 # construct dictionary. note that D is turned off here params = { "grid": grid_1, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility_rock": K_rock_sp, "water_erodibility_sediment": K_sed_sp, "sp_crit_br": sp_crit_br, "sp_crit_sed": sp_crit_sed, "m_sp": m_sp, "n_sp": n_sp, "settling_velocity": v_sc, "sediment_porosity": phi, "fraction_fines": F_f, "roughness__length_scale": H_star, "solver": solver, "soil_transport_decay_depth": soil_transport_decay_depth, "soil_production__maximum_rate": soil_production__maximum_rate, "soil_production__decay_depth": soil_production__decay_depth, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } # construct and run model model = BasicHySa(**params) for _ in range(2000): model.run_one_step(10) # construct actual and predicted slopes actual_slopes = model.grid.at_node["topographic__steepest_slope"] actual_areas = model.grid.at_node["surface_water__discharge"] predicted_slopes = np.power( ((U * v_sc) / (K_sed_sp * np.power(actual_areas, m_sp))) + (U / (K_rock_sp * np.power(actual_areas, m_sp))), 1.0 / n_sp, ) # assert actual and predicted slopes are the same. assert_array_almost_equal( actual_slopes[model.grid.core_nodes[1:-1]], predicted_slopes[model.grid.core_nodes[1:-1]], decimal=4, ) with pytest.raises(SystemExit): for _ in range(800): model.run_one_step(100000)
def test_steady_without_stochastic_duration(clock_simple, Model, extra_params, depression_finder): r"""Test steady profile solution with fixed duration. Notes ----- We use m=1 because the integral that averages over storm events has an analytical solution; it evaluates to 1/2 when mean rain intensity and infiltration capacity are both equal to unity. This is where the factor of 2 in the predicted-slope calculation below comes from. The derivation is as follows. Instantaneous erosion rate, :math:E_i: ..math:: E_i = K Q^m S^n Instantaneous water discharge depends on drainage area, :math:A, rain intensity, :math:P, and infiltration capacity, :math:I_m: ..math:: Q = R A R = P - I_m (1 - e^{-P/I_m}) Average erosion rate, :math:E, is the integral of instantaneous erosion rate over all possible rain rates times the PDF of rain rate, :math:f(P): ..math:: E = \int_0^\infty f(P) K A^m S^n [P-I_m(1-e^{-P/I_m})]^m dP = K A^m S^n \int_0^\infty f(P) [P-I_m(1-e^{-P/I_m})]^m dP = K A^m S^n \Phi where :math:\Phi represents the integral. For testing purposes, we seek an analytical solution to the integral. Take :math:`m=n=1` and :math:`P=I_m=1`. Also assume that the distribution shape factor is 1, so that :math:`f(P) = (1/Pbar) e^{-P/Pbar}`. According to the online integrator, the indefinite integral solution under these assumptions is ..math:: \Phi = e^{-P} (-\frac{1}{2} e^{-P} - P) The definite integral should therefore be 1/2. The slope-area relation is therefore ..math:: S = \frac{2U}{K A} """ U = 0.0001 K = 0.001 m = 1.0 n = 1.0 grid = RasterModelGrid((3, 6), xy_spacing=100.0) grid.set_closed_boundaries_at_grid_edges(False, True, False, True) grid.add_zeros("node", "topographic__elevation") s = grid.add_zeros("node", "soil__depth") s[:] = 1e-9 ncnblh = NotCoreNodeBaselevelHandler(grid, modify_core_nodes=True, lowering_rate=-U) # construct dictionary. note that D is turned off here params = { "grid": grid, "clock": clock_simple, "regolith_transport_parameter": 0.0, "water_erodibility": K, "m_sp": m, "n_sp": n, "number_of_sub_time_steps": 100, "rainfall_intermittency_factor": 1.0, "rainfall__mean_rate": 1.0, "rainfall__shape_factor": 1.0, "random_seed": 3141, "depression_finder": depression_finder, "boundary_handlers": { "NotCoreNodeBaselevelHandler": ncnblh }, } for p in extra_params: params[p] = extra_params[p] # construct and run model model = Model(**params) for _ in range(100): model.run_one_step(1.0) # construct actual and predicted slopes ic = model.grid.core_nodes[1:-1] # "inner" core nodes actual_slopes = model.grid.at_node["topographic__steepest_slope"][ic] actual_areas = model.grid.at_node["drainage_area"][ic] predicted_slopes = 2 * U / (K * (actual_areas)) # assert actual and predicted slopes are the same. assert_array_almost_equal(actual_slopes, predicted_slopes)
def test_deleting_output(clock_08, almost_default_grid): ncnblh = NotCoreNodeBaselevelHandler(almost_default_grid, modify_core_nodes=True, lowering_rate=-1) # construct and run model model = Basic( clock_08, almost_default_grid, water_erodibility=0.0, regolith_transport_parameter=0.0, boundary_handlers={"NotCoreNodeBaselevelHandler": ncnblh}, output_writers={ "fibonnaci-1": { "class": OWGenericWrapper, "kwargs": { "add_id": False, "times_iter": fibonnaci(), "verbose": True, }, }, "fibonnaci-2": { "class": OWGenericWrapper, "kwargs": { "add_id": False, "times_iter": fibonnaci(), "verbose": True, }, }, "fibonnaci-3": { "class": OWGenericWrapper, "kwargs": { "add_id": False, "times_iter": fibonnaci(), "verbose": True, }, }, "netcdf-1": { "class": OWSimpleNetCDF, "args": ["topographic__elevation"], "kwargs": { "intervals": 5.0, "add_id": False, "verbose": True }, }, "netcdf-2": { "class": OWSimpleNetCDF, "args": ["topographic__elevation"], "kwargs": { "intervals": 5.0, "add_id": False, "verbose": True }, }, "netcdf-3": { "class": OWSimpleNetCDF, "args": ["topographic__elevation"], "kwargs": { "intervals": 5.0, "add_id": False, "verbose": True }, }, }, # output_interval=6.0, output_dir=_TEST_OUTPUT_DIR, output_prefix="", output_default_netcdf=False, save_first_timestep=True, save_last_timestep=True, ) with pytest.warns(OutputIteratorSkipWarning): model.run() class NotAWriter: pass not_a_writer = NotAWriter() with pytest.raises(TypeError): model.remove_output(writer=not_a_writer) with pytest.raises(TypeError): model.remove_output(extension=not_a_writer) def all_exist(filepaths): assert all([os.path.isfile(fp) for fp in filepaths]) def all_deleted(filepaths): assert all([not os.path.isfile(fp) for fp in filepaths]) ow_fib_1 = model.get_output_writer("fibonnaci-1")[0] ow_fib_2 = model.get_output_writer("fibonnaci-2")[0] ow_fib_3 = model.get_output_writer("fibonnaci-3")[0] ow_nc_all = model.get_output_writer("netcdf-") ow_nc_1, ow_nc_2, ow_nc_3 = ow_nc_all assert ow_fib_1 and ow_fib_1.name == "fibonnaci-1" assert ow_fib_2 and ow_fib_2.name == "fibonnaci-2" assert ow_fib_3 and ow_fib_3.name == "fibonnaci-3" assert ow_nc_1 and ow_nc_1.name == "netcdf-1" assert ow_nc_2 and ow_nc_2.name == "netcdf-2" assert ow_nc_3 and ow_nc_3.name == "netcdf-3" out_fib_1 = model.get_output(writer=ow_fib_1) out_fib_2 = model.get_output(writer="fibonnaci-2") out_fib_3 = model.get_output(writer=["fibonnaci-3"]) out_nc_1 = model.get_output(writer=ow_nc_1) out_nc_2 = model.get_output(writer=ow_nc_2) out_nc_3 = model.get_output(writer=ow_nc_3) out_nc_2_and_3 = model.get_output(writer=["netcdf-2", "netcdf-3"]) out_nc_all = model.get_output(writer=ow_nc_all) assert (out_nc_1 + out_nc_2 + out_nc_3) == out_nc_all assert (out_nc_1 + out_nc_2_and_3) == out_nc_all assert model.get_output(extension="txt", writer=ow_nc_all) == [] all_exist(out_fib_1 + out_fib_2 + out_fib_3) all_exist(out_nc_1 + out_nc_2 + out_nc_3) # Attempt to delete a file type that the writer never made model.remove_output(extension=".pdf", writer=None) model.remove_output(extension="pdf", writer=None) model.remove_output(extension="pdf", writer=ow_fib_1) model.remove_output(extension="pdf", writer=[ow_fib_1]) all_exist(out_fib_1 + out_fib_2 + out_fib_3) all_exist(out_nc_1 + out_nc_2 + out_nc_3) # Delete with variations of the writer arg model.remove_output(extension=".txt", writer=ow_fib_1) # test the period all_exist(out_fib_2 + out_fib_3) all_deleted(out_fib_1) model.remove_output(extension="txt", writer=[ow_fib_2]) all_exist(out_fib_3) all_deleted(out_fib_1 + out_fib_2) model.remove_output(extension="txt", writer=None) all_deleted(out_fib_1 + out_fib_2 + out_fib_3) all_exist(out_nc_1 + out_nc_2 + out_nc_3) # Delete with variations of the extension arg model.remove_output(extension="nc", writer=ow_nc_1) all_exist(out_nc_2 + out_nc_3) all_deleted(out_nc_1) model.remove_output(extension=["nc"], writer=ow_nc_2) all_exist(out_nc_3) all_deleted(out_nc_1 + out_nc_2) model.remove_output(extension=None, writer=ow_nc_3) all_deleted(out_nc_1 + out_nc_2 + out_nc_3)