Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py =================================================================== diff -u -r3487 -r3490 --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py (.../input.py) (revision 3487) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py (.../input.py) (revision 3490) @@ -23,11 +23,13 @@ from typing import List from pathlib import Path import lxml.etree as et +from pydantic import validator from .surface_line import SurfaceLine from .soil import Soil from .base_class import BaseDataClass from .stability_parameters import StabilityParameters +from .soilprofile1D import SoilProfile1D class DamInput(BaseDataClass): @@ -41,7 +43,22 @@ SurfaceLines: List[SurfaceLine] = [] Soils: List[Soil] = [] StabilityParameters: StabilityParameters + SoilProfiles1D: List[SoilProfile1D] = [] + @validator("SoilProfiles1D") + def soil_profiles_contain_soils(cls, v, values, **kwargs): + soils_names = [soil.Name for soil in values["Soils"]] + soilprofiles = v + for soilprofile in soilprofiles: + for layer in soilprofile.Layers1D: + if not (layer.SoilName in soils_names): + raise ValueError( + f"Soil with name {layer.SoilName} In profile \ + {soilprofile.Name}, layer {layer.Name} is not \ + part of the Soils input" + ) + return v + def ExportToXml(self, xml_file: Path) -> None: """ Function that export the DamInput dataclass to an xml that can be used as input of the DAMengine. @@ -60,6 +77,11 @@ soils_root.append(soil.serialize_soil()) soils_aquifer.append(soil.serialize_aquifer_soil()) input_root.append(soils_root) + # add soil profiles to the xml file + soilprofiles_root = et.Element("SoilProfiles1D") + for soil_profile in self.SoilProfiles1D: + soilprofiles_root.append(soil_profile.serialize()) + input_root.append(soilprofiles_root) # add stability parameters to input root input_root.append(self.StabilityParameters.serialize()) # append soil aquifers Index: DamClients/DamPythonInterface/trunk/src/tests/test_soilprofile1D.py =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/tests/test_soilprofile1D.py (revision 0) +++ DamClients/DamPythonInterface/trunk/src/tests/test_soilprofile1D.py (revision 3490) @@ -0,0 +1,90 @@ +# Copyright (C) Stichting Deltares 2021. All rights reserved. +# +# This file is part of the Dam Python Interface. +# +# The Dam Python Interface is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# All names, logos, and references to "Deltares" are registered trademarks of +# Stichting Deltares and remain full property of Stichting Deltares at all times. +# All rights reserved. +from dampythoninterface.soilprofile1D import ( + SoilProfile1D, + Layer1D, + WaterpressureInterpolationModelType, +) + +from .utils import TestUtils + +import pytest +from pathlib import Path +from lxml import etree + + +class TestSoilProfile1D: + def define_soil_profile(self) -> SoilProfile1D: + # first define the soil layers + layer_1 = Layer1D( + Name="L0", + SoilName="Dijkmateriaal", + TopLevel=10, + IsAquifer=False, + WaterpressureInterpolationModel=WaterpressureInterpolationModelType.Automatic, + ) + layer_2 = Layer1D( + Name="L1", + SoilName="Deklaag_klei", + TopLevel=0, + IsAquifer=False, + WaterpressureInterpolationModel=WaterpressureInterpolationModelType.Automatic, + ) + layer_3 = Layer1D( + Name="L2", + SoilName="wl_zand1", + TopLevel=-2, + IsAquifer=True, + WaterpressureInterpolationModel=WaterpressureInterpolationModelType.Automatic, + ) + soil_profile = SoilProfile1D( + Name="soilprofile_01", BottomLevel=-10, Layers1D=[layer_1, layer_2, layer_3] + ) + return soil_profile + + @pytest.mark.unittest + def test_soil_profile_can_be_initialized(self): + soil_profile = self.define_soil_profile() + # test expectations + assert soil_profile.Name == "soilprofile_01" + assert len(soil_profile.Layers1D) == 3 + assert soil_profile.Layers1D[-1].SoilName == "wl_zand1" + + @pytest.mark.unittest + def test_serialize(self): + # initialize the stabilty parameters class + soil_profile = self.define_soil_profile() + # run test + root = soil_profile.serialize() + # test output + xml_output = Path( + TestUtils.get_output_test_data_dir(""), "test_soil_profile.xml" + ) + tree = etree.ElementTree(root) + tree.write(str(xml_output), pretty_print=True) + # get template file + xml_test_data = Path( + TestUtils.get_test_data_dir("", "test_data"), + "test_soil_profile.xml", + ) + # Line by line comparison + for output_file, test_file in zip(open(xml_output), open(xml_test_data)): + assert output_file.strip() == test_file.strip() Index: DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml =================================================================== diff -u -r3487 -r3490 --- DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml (.../test_full_output.xml) (revision 3487) +++ DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml (.../test_full_output.xml) (revision 3490) @@ -25,6 +25,14 @@ + + + + + + + + Index: DamClients/DamPythonInterface/trunk/src/tests/test_input.py =================================================================== diff -u -r3487 -r3490 --- DamClients/DamPythonInterface/trunk/src/tests/test_input.py (.../test_input.py) (revision 3487) +++ DamClients/DamPythonInterface/trunk/src/tests/test_input.py (.../test_input.py) (revision 3490) @@ -20,6 +20,7 @@ # All rights reserved. +from typing import List import pytest from pathlib import Path from lxml import etree @@ -33,12 +34,16 @@ BishopTangentLinesDefinitionType, GridDeterminationType, ) +from dampythoninterface.soilprofile1D import ( + SoilProfile1D, + Layer1D, + WaterpressureInterpolationModelType, +) from .utils import TestUtils class TestDamInput: - @pytest.mark.integrationtest - def test_ExportToXml(self): + def create_surface_lines(self) -> List[SurfaceLine]: # Create surface lines points_available = [ PointTypeEnum.SurfaceLevelOutside, @@ -57,8 +62,9 @@ # check initial expectations assert surfaceline_1.Name == "Surface line 1" assert surfaceline_2.Name == "Surface line 2" - list_of_surface_lines = [surfaceline_1, surfaceline_2] - # create soil class + return [surfaceline_1, surfaceline_2] + + def create_soils(self) -> List[Soil]: soil_1 = Soil( Name="soil 1", AbovePhreaticLevel=17, @@ -71,9 +77,10 @@ BelowPhreaticLevel=20, IsAquifer=False, ) - soils = [soil_1, soil_2] - # Create stability parameters - test_stabilty_parameters = StabilityParameters( + return [soil_1, soil_2] + + def create_stability_parameters(self) -> StabilityParameters: + return StabilityParameters( SearchMethod=SearchMethodType.Calculationgrid, GridDetermination=GridDeterminationType.Automatic, BishopTangentLinesDefinition=BishopTangentLinesDefinitionType.OnBoundaryLines, @@ -83,11 +90,64 @@ BishopGridHorizontalPointsCount=10, BishopGridHorizontalPointsDistance=2.5, ) + + def create_profiles(self, soil_names: List[str]) -> List[SoilProfile1D]: + soil_layer_1 = Layer1D( + Name="L0", + SoilName=soil_names[0], + TopLevel=0, + IsAquifer=False, + WaterpressureInterpolationModel=WaterpressureInterpolationModelType.Automatic, + ) + soil_layer_2 = Layer1D( + Name="L1", + SoilName=soil_names[1], + TopLevel=-2, + IsAquifer=False, + WaterpressureInterpolationModel=WaterpressureInterpolationModelType.Automatic, + ) + return [ + SoilProfile1D( + Name="Soil profile", + BottomLevel=-10, + Layers1D=[soil_layer_1, soil_layer_2], + ) + ] + + @pytest.mark.integrationtest + def test_soil_profiles_contain_soils(self): + list_of_surface_lines = self.create_surface_lines() + # create soil class + soils = self.create_soils() + test_stabilty_parameters = self.create_stability_parameters() + soil_profiles = self.create_profiles([soils[0].Name, "wrong soil name"]) + # run test + error_message = "Soil with name wrong soil name" + with pytest.raises(ValueError) as e_info: + DamInput( + SurfaceLines=list_of_surface_lines, + Soils=soils, + StabilityParameters=test_stabilty_parameters, + SoilProfiles1D=soil_profiles, + ) + generated_error_message = str(e_info.value) + assert error_message in generated_error_message + + @pytest.mark.integrationtest + def test_ExportToXml(self): + # Create surface lines + list_of_surface_lines = self.create_surface_lines() + # create soil class + soils = self.create_soils() + # Create stability parameters + test_stabilty_parameters = self.create_stability_parameters() + soil_profiles = self.create_profiles([soils[0].Name, soils[1].Name]) # Make input class model = DamInput( SurfaceLines=list_of_surface_lines, Soils=soils, StabilityParameters=test_stabilty_parameters, + SoilProfiles1D=soil_profiles, ) assert model # run test Index: DamClients/DamPythonInterface/trunk/src/tests/test_data/test_soil_profile.xml =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/tests/test_data/test_soil_profile.xml (revision 0) +++ DamClients/DamPythonInterface/trunk/src/tests/test_data/test_soil_profile.xml (revision 3490) @@ -0,0 +1,7 @@ + + + + + + + Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/soilprofile1D.py =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/soilprofile1D.py (revision 0) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/soilprofile1D.py (revision 3490) @@ -0,0 +1,102 @@ +# Copyright (C) Stichting Deltares 2021. All rights reserved. +# +# This file is part of the Dam Python Interface. +# +# The Dam Python Interface is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# All names, logos, and references to "Deltares" are registered trademarks of +# Stichting Deltares and remain full property of Stichting Deltares at all times. +# All rights reserved. + + +from typing import List +import lxml.etree as et +from enum import IntEnum + +from .base_class import BaseDataClass + + +class WaterpressureInterpolationModelType(IntEnum): + """ """ + + Automatic = 0 + Hydrostatic = 1 + + +class Layer1D(BaseDataClass): + """ + Dataclass that contains a 1D layer + + :param Name: name of the layer + :param SoilName: soil name that is assigned in the soil layer + :param TopLevel: top level of the soil layer + :param IsAquifer: value that determines if an aquifer is contained in the layer + :param WaterpressureInterpolationModel: water pressure interpolation method + + """ + + Name: str + SoilName: str + TopLevel: float + IsAquifer: bool + WaterpressureInterpolationModel: WaterpressureInterpolationModelType + + def serialize(self) -> et.Element: + """ + Function that serializes the Layer1D class. + """ + layer1d_root = et.Element("Layer1D") + dictionary_of_values = self.dict() + layer1d_root.set("Name", dictionary_of_values.get("Name")) + layer1d_root.set("SoilName", dictionary_of_values.get("SoilName")) + layer1d_root.set("TopLevel", str(dictionary_of_values.get("TopLevel"))) + layer1d_root.set( + "IsAquifer", str(dictionary_of_values.get("IsAquifer")).lower() + ) + layer1d_root.set( + "WaterpressureInterpolationModel", + str(dictionary_of_values.get("WaterpressureInterpolationModel").value), + ) + return layer1d_root + + +class SoilProfile1D(BaseDataClass): + """ + Dataclass that contains a 1D soil profile + + :param Name: name of the soil profile + :param BottomLevel: bottom level of the whole soil profile + :param Layers1D: list of layers included in the soil profile + + """ + + Name: str + BottomLevel: float + Layers1D: List[Layer1D] = [] + + def serialize(self) -> et.Element: + """ + Function that serializes the class SoilProfile1D class. + """ + soilprofile1d_root = et.Element("SoilProfile1D") + dictionary_of_values = self.dict() + soilprofile1d_root.set("Name", dictionary_of_values.get("Name")) + soilprofile1d_root.set( + "BottomLevel", str(dictionary_of_values.get("BottomLevel")) + ) + layers1d_root = et.Element("Layers1D") + for layer in self.Layers1D: + layers1d_root.append(layer.serialize()) + soilprofile1d_root.append(layers1d_root) + return soilprofile1d_root Index: DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst =================================================================== diff -u -r3489 -r3490 --- DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst (.../input.rst) (revision 3489) +++ DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst (.../input.rst) (revision 3490) @@ -35,4 +35,12 @@ .. automodule:: dampythoninterface.stability_parameters :members: + :undoc-members: + +Soil profile 1D +--------------- + + +.. automodule:: dampythoninterface.soilprofile1D + :members: :undoc-members: \ No newline at end of file