From 0ebad3a461dfa1bd42eba9cc710746a7ba7cd77e Mon Sep 17 00:00:00 2001 From: Dhruv Sondhi <66117751+DhruvSondhi@users.noreply.github.com> Date: Tue, 4 May 2021 19:07:28 +0530 Subject: [PATCH] Adding Config Value Validation for Different Parameters via Config File (#1554) * Adding Config Validation for Uniform Abundance via Schema * Adding Validation for time_explosion * Validating based on different sections * Fixing CSVY Tests Implementation * Added Configuration Validation Tests * Added Docstrings for the validation tests --- tardis/io/config_reader.py | 133 +++++++++++++++++++++++++ tardis/io/schemas/csvy_model.yml | 5 +- tardis/io/schemas/model.yml | 6 +- tardis/io/schemas/montecarlo.yml | 9 ++ tardis/io/tests/test_config_reader.py | 137 ++++++++++++++++++++++++++ 5 files changed, 287 insertions(+), 3 deletions(-) diff --git a/tardis/io/config_reader.py b/tardis/io/config_reader.py index c63fc914469..4a53d75a89b 100644 --- a/tardis/io/config_reader.py +++ b/tardis/io/config_reader.py @@ -284,6 +284,7 @@ def from_config_dict(cls, config_dict, validate=True, config_dirname=""): validated_config_dict["config_dirname"] = config_dirname + # Montecarlo Section Implementation montecarlo_section = validated_config_dict["montecarlo"] if montecarlo_section["convergence_strategy"]["type"] == "damped": montecarlo_section[ @@ -313,6 +314,138 @@ def from_config_dict(cls, config_dict, validate=True, config_dirname=""): "relativity mode. " ) + if "csvy_model" in validated_config_dict.keys(): + pass + elif "model" in validated_config_dict.keys(): + + # Model Section Validation + model_section = validated_config_dict["model"] + + if model_section["structure"]["type"] == "specific": + start_velocity = model_section["structure"]["velocity"]["start"] + stop_velocity = model_section["structure"]["velocity"]["stop"] + if stop_velocity.value < start_velocity.value: + raise ValueError( + "Stop Velocity Cannot Be Less than Start Velocity. \n" + f"Start Velocity = {start_velocity} \n" + f"Stop Velocity = {stop_velocity}" + ) + elif model_section["structure"]["type"] == "file": + v_inner_boundary = model_section["structure"][ + "v_inner_boundary" + ] + v_outer_boundary = model_section["structure"][ + "v_outer_boundary" + ] + if v_outer_boundary.value < v_inner_boundary.value: + raise ValueError( + "Outer Boundary Velocity Cannot Be Less than Inner Boundary Velocity. \n" + f"Inner Boundary Velocity = {v_inner_boundary} \n" + f"Outer Boundary Velocity = {v_outer_boundary}" + ) + if "density" in model_section["structure"].keys(): + if ( + model_section["structure"]["density"]["type"] + == "exponential" + ): + rho_0 = model_section["structure"]["density"]["rho_0"] + v_0 = model_section["structure"]["density"]["v_0"] + if not rho_0.value > 0: + raise ValueError( + f"Density Specified is Invalid, {rho_0}" + ) + if not v_0.value > 0: + raise ValueError( + f"Velocity Specified is Invalid, {v_0}" + ) + if "time_0" in model_section["structure"]["density"].keys(): + time_0 = model_section["structure"]["density"]["time_0"] + if not time_0.value > 0: + raise ValueError( + f"Time Specified is Invalid, {time_0}" + ) + elif ( + model_section["structure"]["density"]["type"] == "power_law" + ): + rho_0 = model_section["structure"]["density"]["rho_0"] + v_0 = model_section["structure"]["density"]["v_0"] + if not rho_0.value > 0: + raise ValueError( + f"Density Specified is Invalid, {rho_0}" + ) + if not v_0.value > 0: + raise ValueError( + f"Velocity Specified is Invalid, {v_0}" + ) + if "time_0" in model_section["structure"]["density"].keys(): + time_0 = model_section["structure"]["density"]["time_0"] + if not time_0.value > 0: + raise ValueError( + f"Time Specified is Invalid, {time_0}" + ) + elif model_section["structure"]["density"]["type"] == "uniform": + value = model_section["structure"]["density"]["value"] + if not value.value > 0: + raise ValueError( + f"Density Value Specified is Invalid, {value}" + ) + if "time_0" in model_section["structure"]["density"].keys(): + time_0 = model_section["structure"]["density"]["time_0"] + if not time_0.value > 0: + raise ValueError( + f"Time Specified is Invalid, {time_0}" + ) + + # SuperNova Section Validation + supernova_section = validated_config_dict["supernova"] + + time_explosion = supernova_section["time_explosion"] + luminosity_wavelength_start = supernova_section[ + "luminosity_wavelength_start" + ] + luminosity_wavelength_end = supernova_section[ + "luminosity_wavelength_end" + ] + if not time_explosion.value > 0: + raise ValueError( + f"Time Of Explosion is Invalid, {time_explosion}" + ) + if ( + luminosity_wavelength_start.value + > luminosity_wavelength_end.value + ): + raise ValueError( + "Integral Limits for Luminosity Wavelength are Invalid, Start Limit > End Limit \n" + f"Luminosity Wavelength Start : {luminosity_wavelength_start} \n" + f"Luminosity Wavelength End : {luminosity_wavelength_end}" + ) + + # Plasma Section Validation + plasma_section = validated_config_dict["plasma"] + + initial_t_inner = plasma_section["initial_t_inner"] + initial_t_rad = plasma_section["initial_t_rad"] + if not initial_t_inner.value >= -1: + raise ValueError( + f"Initial Temperature of Inner Boundary Black Body is Invalid, {initial_t_inner}" + ) + if not initial_t_rad.value >= -1: + raise ValueError( + f"Initial Radiative Temperature is Invalid, {initial_t_inner}" + ) + + # Spectrum Section Validation + spectrum_section = validated_config_dict["spectrum"] + + start = spectrum_section["start"] + stop = spectrum_section["stop"] + if start.value > stop.value: + raise ValueError( + "Start Value of Spectrum Cannot be Greater than Stop Value. \n" + f"Start : {start} \n" + f"Stop : {stop}" + ) + return cls(validated_config_dict) def __init__(self, config_dict): diff --git a/tardis/io/schemas/csvy_model.yml b/tardis/io/schemas/csvy_model.yml index dc593e01d8f..a508e869259 100644 --- a/tardis/io/schemas/csvy_model.yml +++ b/tardis/io/schemas/csvy_model.yml @@ -145,8 +145,11 @@ definitions: abundance: uniform: type: object - additionalProperties: true properties: type: enum: - uniform + additionalProperties: + type: number + minimum: 0 + maximum: 1 diff --git a/tardis/io/schemas/model.yml b/tardis/io/schemas/model.yml index 67ca554aaa8..759076d89c2 100644 --- a/tardis/io/schemas/model.yml +++ b/tardis/io/schemas/model.yml @@ -164,9 +164,11 @@ definitions: - filename uniform: type: object - additionalProperties: true properties: type: enum: - uniform - + additionalProperties: + type: number + minimum: 0 + maximum: 1 diff --git a/tardis/io/schemas/montecarlo.yml b/tardis/io/schemas/montecarlo.yml index 2e889296c56..c218f4267ce 100644 --- a/tardis/io/schemas/montecarlo.yml +++ b/tardis/io/schemas/montecarlo.yml @@ -105,6 +105,7 @@ definitions: description: the fraction of shells that have to converge to the given convergence threshold. For example, 0.8 means that 80% of shells have to converge to the threshold that convergence is established + minimum: 0 hold_iterations: type: number multipleOf: 1.0 @@ -115,11 +116,13 @@ definitions: type: number default: 1.0 description: damping constant + minimum: 0 threshold: type: number default: 0.05 description: specifies the threshold that is taken as convergence (i.e. 0.05 means that the value does not change more than 5%) + minimum: 0 t_inner: type: object additionalProperties: false @@ -128,10 +131,12 @@ definitions: type: number default: 0.5 description: damping constant + minimum: 0 threshold: type: number description: specifies the threshold that is taken as convergence (i.e. 0.05 means that the value does not change more than 5%) + minimum: 0 t_rad: type: object additionalProperties: false @@ -140,10 +145,12 @@ definitions: type: number default: 0.5 description: damping constant + minimum: 0 threshold: type: number description: specifies the threshold that is taken as convergence (i.e. 0.05 means that the value does not change more than 5%) + minimum: 0 required: - threshold w: @@ -154,10 +161,12 @@ definitions: type: number default: 0.5 description: damping constant + minimum: 0 threshold: type: number description: specifies the threshold that is taken as convergence (i.e. 0.05 means that the value does not change more than 5%) + minimum: 0 required: - threshold lock_t_inner_cycles: diff --git a/tardis/io/tests/test_config_reader.py b/tardis/io/tests/test_config_reader.py index 6b8b7dbf503..36462eb289a 100644 --- a/tardis/io/tests/test_config_reader.py +++ b/tardis/io/tests/test_config_reader.py @@ -1,5 +1,6 @@ # tests for the config reader module import os +from attr import validate import pytest import pandas as pd from numpy.testing import assert_almost_equal @@ -44,10 +45,12 @@ def test_from_config_dict(tardis_config_verysimple): tardis_config_verysimple, validate=True, config_dirname="test" ) assert conf.config_dirname == "test" + assert_almost_equal( conf.spectrum.start.value, tardis_config_verysimple["spectrum"]["start"].value, ) + assert_almost_equal( conf.spectrum.stop.value, tardis_config_verysimple["spectrum"]["stop"].value, @@ -68,3 +71,137 @@ def test_config_hdf(hdf_file_path, tardis_config_verysimple): actual = pd.read_hdf(hdf_file_path, key="/simulation/config") expected = expected.get_properties()["config"] assert actual[0] == expected[0] + + +def test_model_section_config(tardis_config_verysimple): + """ + Configuration Validation Test for Model Section of the Tardis Config YAML File + + Validates: + Density: branch85_w7 + Velocity (Start < End) + + Parameter + --------- + `tardis_config_verysimple` : YAML File + + Result + ------ + Assertion based on validation for specified values + """ + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) + + assert conf.model.structure.density.type == "branch85_w7" + + tardis_config_verysimple["model"]["structure"]["velocity"][ + "start" + ] = "2.0e4 km/s" + tardis_config_verysimple["model"]["structure"]["velocity"][ + "stop" + ] = "1.1e4 km/s" + with pytest.raises(ValueError) as ve: + if ( + conf.model.structure.velocity.start + < conf.model.structure.velocity.stop + ): + raise ValueError("Stop Value must be greater than Start Value") + assert ve.type is ValueError + + +def test_supernova_section_config(tardis_config_verysimple): + """ + Configuration Validation Test for Supernova Section of the Tardis Config YAML File + + Validates: + Time of Explosion (Must always be positive) + Luminosity Wavelength Limits (Start < End) + + Parameter + --------- + `tardis_config_verysimple` : YAML File + + Result + ------ + Assertion based on validation for specified values + """ + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) + tardis_config_verysimple["supernova"]["time_explosion"] = "-10 day" + tardis_config_verysimple["supernova"][ + "luminosity_wavelength_start" + ] = "15 angstrom" + tardis_config_verysimple["supernova"][ + "luminosity_wavelength_end" + ] = "0 angstrom" + with pytest.raises(ValueError) as ve: + if conf.supernova.time_explosion.value > 0: + raise ValueError("Time of Explosion cannot be negative") + assert ve.type is ValueError + + with pytest.raises(ValueError) as ve: + if ( + conf.supernova.luminosity_wavelength_start.value + < conf.supernova.luminosity_wavelength_end.value + ): + raise ValueError( + "End Limit must be greater than Start Limit for Luminosity" + ) + assert ve.type is ValueError + + +def test_plasma_section_config(tardis_config_verysimple): + """ + Configuration Validation Test for Plasma Section of the Tardis Config YAML File + + Validates: + Initial temperature inner (must be greater than -1K) + Initial radiative temperature (must be greater than -1K) + + Parameter + --------- + `tardis_config_verysimple` : YAML File + + Result + ------ + Assertion based on validation for specified values + """ + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) + tardis_config_verysimple["plasma"]["initial_t_inner"] = "-100 K" + tardis_config_verysimple["plasma"]["initial_t_rad"] = "-100 K" + with pytest.raises(ValueError) as ve: + if (conf.plasma.initial_t_inner.value >= -1) and ( + conf.plasma.initial_t_rad.value >= -1 + ): + raise ValueError("Initial Temperatures are Invalid") + assert ve.type is ValueError + + +def test_spectrum_section_config(tardis_config_verysimple): + """ + Configuration Validation Test for Plasma Section of the Tardis Config YAML File + + Validates: + Spectrum Start & End Limits (Start < End) + + Parameter + --------- + `tardis_config_verysimple` : YAML File + + Result + ------ + Assertion based on validation for specified values + """ + conf = Configuration.from_config_dict( + tardis_config_verysimple, validate=True, config_dirname="test" + ) + tardis_config_verysimple["spectrum"]["start"] = "2500 angstrom" + tardis_config_verysimple["spectrum"]["stop"] = "500 angstrom" + with pytest.raises(ValueError) as ve: + if not conf.spectrum.stop.value < conf.spectrum.start.value: + raise ValueError("Start Value must be less than Stop Value") + assert ve.type is ValueError