Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/location.py =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/location.py (revision 0) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/location.py (revision 3501) @@ -0,0 +1,289 @@ +# 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 .base_class import BaseDataClass + +from typing import List, Optional, Dict +from enum import Enum, IntEnum +import lxml.etree as et + + +class StabilityDesignMethodType(Enum): + """ """ + + OptimizedSlopeAndShoulderAdaption = "OptimizedSlopeAndShoulderAdaption" + SlopeAdaptionBeforeShoulderAdaption = "SlopeAdaptionBeforeShoulderAdaption" + + +class DesignOptions(BaseDataClass): + """ + Dataclass of DesignOptions + + :param RedesignDikeHeight: redesign value of the dike height + :param RedesignDikeShoulder: redesign value of the dike shoulder + :param ShoulderEmbankmentMaterial: material of the dike shoulder + :param StabilityShoulderGrowSlope: slope of the shoulder grow for stability + :param StabilityShoulderGrowDeltaX: delta(x) of the shoulder grow for stability + :param StabilitySlopeAdaptionDeltaX: delta(x) of the slope adaptation for stability + :param SlopeAdaptionStartCotangent: cotangent start for the slope adaptation + :param SlopeAdaptionEndCotangent: cotangent end for the slope adaptation + :param SlopeAdaptionStepCotangent: cotangent step for the slope adaptation + :param NewDikeTopWidth: new dike top width + :param NewDikeSlopeInside: new dike slope inside + :param NewDikeSlopeOutside: new dike slope outside + :param NewShoulderTopSlope: slope of top for new shoulder + :param NewShoulderBaseSlope: slope of base for new shoulder + :param NewMaxHeightShoulderAsFraction: new maximum height of shoulder inputted as fraction + :param NewMinDistanceDikeToeStartDitch: new minimum distance of dike toe at the start of the ditch + :param UseNewDitchDefinition: is the new ditch definition used + :param NewWidthDitchBottom: new width of the bottom for ditch + :param NewSlopeAngleDitch: new slope angle for ditch + :param NewDepthDitch: new depth for ditch + :param StabilityDesignMethod: stability design method + + """ + + RedesignDikeHeight: bool + RedesignDikeShoulder: bool + ShoulderEmbankmentMaterial: str + StabilityShoulderGrowSlope: float + StabilityShoulderGrowDeltaX: float + StabilitySlopeAdaptionDeltaX: float + SlopeAdaptionStartCotangent: float + SlopeAdaptionEndCotangent: float + SlopeAdaptionStepCotangent: float + NewDikeTopWidth: Optional[float] + NewDikeSlopeInside: Optional[float] + NewDikeSlopeOutside: Optional[float] + NewShoulderTopSlope: Optional[float] + NewShoulderBaseSlope: Optional[float] + NewMaxHeightShoulderAsFraction: Optional[float] + NewMinDistanceDikeToeStartDitch: Optional[float] + UseNewDitchDefinition: bool + NewWidthDitchBottom: Optional[float] + NewSlopeAngleDitch: Optional[float] + NewDepthDitch: Optional[float] + StabilityDesignMethod: StabilityDesignMethodType + + +class PhreaticLineCreationMethodType(Enum): + """ """ + + ExpertKnowledgeRRD = "ExpertKnowledgeRRD" + ExpertKnowledgeLinearInDike = "ExpertKnowledgeLinearInDike" + GaugesWithFallbackToExpertKnowledgeRRD = "GaugesWithFallbackToExpertKnowledgeRRD" + Sensors = "Sensors" + NONE = "NONE" + + +class IntrusionVerticalWaterPressureType(Enum): + """ """ + + Standard = "Standard" + Linear = "Linear" + FullHydroStatic = "FullHydroStatic" + HydroStatic = "HydroStatic" + SemiTimeDependent = "SemiTimeDependent" + + +class DikeSoilScenarioType(Enum): + """ """ + + ClayDikeOnClay = "ClayDikeOnClay" + SandDikeOnClay = "SandDikeOnClay" + ClayDikeOnSand = "ClayDikeOnSand" + SandDikeOnSand = "SandDikeOnSand" + + +class WaternetOptions(BaseDataClass): + """ + Dataclass of WaternetOptions + + :param PhreaticLineCreationMethod: phreatic line creation method + :param DampingFactorPl3: damping factor for PL3 + :param DampingFactorPl4: damping factor for PL4 + :param PenetrationLength: penetration length + :param SlopeDampingFactor: slope damping factor + :param IntrusionVerticalWaterPressure: intrusion of vertical water pressure + :param DikeSoilScenario: dike soil layering scenario + + """ + + PhreaticLineCreationMethod: PhreaticLineCreationMethodType + DampingFactorPl3: float + DampingFactorPl4: float + PenetrationLength: float + SlopeDampingFactor: float + IntrusionVerticalWaterPressure: IntrusionVerticalWaterPressureType + DikeSoilScenario: DikeSoilScenarioType + + +class General(BaseDataClass): + """ + Dataclass of General settings + + :param Description: description + :param HeadPL2: The pore pressure at the top of the intrusion zone + :param HeadPL3: Water pressure in the lower aquifer + :param HeadPL4: Water pressure in an aquifer intermediate layer + + """ + + Description: str + HeadPL2: float = 0 + HeadPL3: float = 0 + HeadPL4: float = 0 + + +class DesignScenario(BaseDataClass): + """ + Dataclass of DesignScenario + + :param Id: id of design scenario + :param RiverLevel: river level + :param RiverLevelLow: lowest river level + :param DikeTableHeight: table of height of dike + :param PlLineOffsetBelowDikeTopAtRiver: PlLine offset below dike at the top of river + :param PlLineOffsetBelowDikeTopAtPolder: PlLine offset below dike at the top of polder + :param PlLineOffsetBelowShoulderBaseInside: PlLine offset below shoulder of the base inside + :param PlLineOffsetBelowDikeToeAtPolder: PlLine offset below dike at the toe of polder + :param PlLineOffsetBelowDikeCrestMiddle: PlLine offset below the middle of the dike crest + :param PlLineOffsetFactorBelowShoulderCrest: PlLine offset factor below the shoulder crest + :param HeadPl3: Water pressure in the lower aquifer + :param HeadPl4: Water pressure in an aquifer intermediate layer + :param UpliftCriterionStability: uplift criterion for stability + :param UpliftCriterionPiping: uplift criterion for piping + :param RequiredSafetyFactorStabilityInnerSlope: safety factor for stability of inner slope + :param RequiredSafetyFactorStabilityOuterSlope: safety factor for stability of outer slope + :param RequiredSafetyFactorPiping: safety factor for piping + :param PolderLevel: polder level + :param HeadPl2: The pore pressure at the top of the intrusion zone + + + """ + + Id: str + RiverLevel: float + RiverLevelLow: Optional[float] + DikeTableHeight: Optional[float] + PlLineOffsetBelowDikeTopAtRiver: float + PlLineOffsetBelowDikeTopAtPolder: float + PlLineOffsetBelowShoulderBaseInside: float + PlLineOffsetBelowDikeToeAtPolder: float + PlLineOffsetBelowDikeCrestMiddle: Optional[float] + PlLineOffsetFactorBelowShoulderCrest: Optional[float] + HeadPl3: Optional[float] + HeadPl4: Optional[float] + UpliftCriterionStability: float + UpliftCriterionPiping: float + RequiredSafetyFactorStabilityInnerSlope: float + RequiredSafetyFactorStabilityOuterSlope: float + RequiredSafetyFactorPiping: float + PolderLevel: float + HeadPl2: Optional[float] + + +class ZoneTypeClass(IntEnum): + """ """ + + NoZones = 0 + ZoneAreas = 1 + ForbiddenZones = 2 + + +class StabilityOptions(BaseDataClass): + """ + Dataclass of StabilityOptions + + :param MapForSoilgeometries2D: map for soil for 2D geometry + :param SoilDatabaseName: soil database name + :param ZoneType: zone type + :param ForbiddenZoneFactor: factor for forbidden zone + :param ZoneAreaRestSlopeCrestWidth: zone area slope of the crest width + :param TrafficLoad: traffic load + :param MinimumCircleDepth: minimum circle depth + :param TrafficLoadDegreeOfConsolidation: degree of consolidation relating to traffic load + + """ + + MapForSoilgeometries2D: str + SoilDatabaseName: str + ZoneType: ZoneTypeClass + ForbiddenZoneFactor: Optional[float] = 0 + ZoneAreaRestSlopeCrestWidth: Optional[float] = 0 + TrafficLoad: Optional[float] = 0 + MinimumCircleDepth: Optional[float] = 0 + TrafficLoadDegreeOfConsolidation: Optional[float] = 0 + + +class Location(BaseDataClass): + """ + Dataclass of Location + + SurfaceLineName: surface line that is assigned to location + SegmentName: segment that is assigned to location + Name: name of the location + DikeEmbankmentMaterial: dike embankment material that is assigned to location + XSoilGeometry2DOrigin: origin of soil of 2D geometry + DistanceToEntryPoint: distance to entry point + OperationalOptions: operational options + DesignOptions: design options + General: general options + DesignScenarios: list of design scenarios + StabilityOptions: stability options + + + """ + + SurfaceLineName: str + SegmentName: str + Name: str + DikeEmbankmentMaterial: str + XSoilGeometry2DOrigin: Optional[float] = 0 + DistanceToEntryPoint: Optional[float] = 0 + OperationalOptions: Optional[float] + DesignOptions: Optional[DesignOptions] + General: Optional[General] + DesignScenarios: List[DesignScenario] + StabilityOptions: Optional[StabilityOptions] + + def serialize(self) -> et.Element: + """Function that serializes the Location class.""" + root_location = et.Element("Location") + names_of_initialized_values = list(self.__fields_set__) + names_of_initialized_values.sort() + for attribute_name in names_of_initialized_values: + attribute = getattr(self, attribute_name) + if isinstance(attribute, str): + root_location.set(attribute_name, attribute) + elif isinstance(attribute, float): + root_location.set(attribute_name, str(attribute)) + elif isinstance(attribute, List): + root_sub_list = et.Element(attribute_name) + for sub_value in attribute: + root_sub_list.append(sub_value.create_element_from_dictionary()) + root_location.append(root_sub_list) + elif isinstance(attribute, BaseDataClass): + root_sub_element = attribute.create_element_from_dictionary() + root_location.append(root_sub_element) + elif attribute is None: + pass + return root_location Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/base_class.py =================================================================== diff -u -r3479 -r3501 --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/base_class.py (.../base_class.py) (revision 3479) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/base_class.py (.../base_class.py) (revision 3501) @@ -21,10 +21,33 @@ from pydantic import BaseModel +from typing import Dict +from enum import Enum, IntEnum +import lxml.etree as et class BaseDataClass(BaseModel): class Config: validate_assignment = True arbitrary_types_allowed = True validate_all = True + + def create_element_from_dictionary(self) -> et.Element: + """ + Function that creates xml element based on given dictionary + """ + class_name = type(self).__name__ + root = et.Element(class_name) + dictionary_of_values = self.dict() + for field, value in dictionary_of_values.items(): + if value is None: + root.set(field, str(1)) + elif isinstance(value, IntEnum): + root.set(field, str(value.value)) + elif isinstance(value, Enum): + root.set(field, value.value) + elif isinstance(value, bool): + root.set(field, str(value).lower()) + else: + root.set(field, str(value)) + return root Index: DamClients/DamPythonInterface/trunk/src/tests/test_input.py =================================================================== diff -u -r3491 -r3501 --- DamClients/DamPythonInterface/trunk/src/tests/test_input.py (.../test_input.py) (revision 3491) +++ DamClients/DamPythonInterface/trunk/src/tests/test_input.py (.../test_input.py) (revision 3501) @@ -20,6 +20,7 @@ # All rights reserved. +from trunk.src.dampythoninterface.location import Location from typing import List import pytest from pathlib import Path @@ -45,10 +46,77 @@ Layer1D, WaterpressureInterpolationModelType, ) +from dampythoninterface.location import * + from .utils import TestUtils class TestDamInput: + def define_locations(self) -> List[Location]: + designoptions = DesignOptions( + RedesignDikeHeight=True, + RedesignDikeShoulder=True, + ShoulderEmbankmentMaterial="Ophoogmateriaal_berm", + StabilityShoulderGrowSlope="0.33", + StabilityShoulderGrowDeltaX="1", + StabilitySlopeAdaptionDeltaX="1", + SlopeAdaptionStartCotangent="3", + SlopeAdaptionEndCotangent="6", + SlopeAdaptionStepCotangent="0.5", + UseNewDitchDefinition=False, + StabilityDesignMethod=StabilityDesignMethodType.OptimizedSlopeAndShoulderAdaption, + ) + waternet_options = WaternetOptions( + PhreaticLineCreationMethod=PhreaticLineCreationMethodType.ExpertKnowledgeRRD, + DampingFactorPl3=0, + DampingFactorPl4=0, + PenetrationLength=0, + SlopeDampingFactor=0, + IntrusionVerticalWaterPressure=IntrusionVerticalWaterPressureType.Standard, + DikeSoilScenario=DikeSoilScenarioType.ClayDikeOnClay, + ) + design_scenario = DesignScenario( + Id="1", + RiverLevel=3.8, + RiverLevelLow=2.5, + DikeTableHeight=4, + PlLineOffsetBelowDikeTopAtRiver=0.5, + PlLineOffsetBelowDikeTopAtPolder=1, + PlLineOffsetBelowShoulderBaseInside=0, + PlLineOffsetBelowDikeToeAtPolder=0, + HeadPl3=3.6, + UpliftCriterionStability=1.2, + UpliftCriterionPiping=1.2, + RequiredSafetyFactorStabilityInnerSlope=1.22, + RequiredSafetyFactorStabilityOuterSlope=1.16, + RequiredSafetyFactorPiping=1.2, + PolderLevel=0, + HeadPl2=0, + ) + stabilility_options = StabilityOptions( + MapForSoilgeometries2D="", + SoilDatabaseName="NewPipingVoorbeeld10.soilmaterials.mdb", + ZoneType=ZoneTypeClass.NoZones, + ForbiddenZoneFactor=1, + TrafficLoad=13.3, + MinimumCircleDepth=1.5, + ) + # define location + return [ + Location( + SurfaceLineName="Profiel 1", + SegmentName="1", + Name="Profiel 1", + DikeEmbankmentMaterial="Ophoogmateriaal_dijk", + XSoilGeometry2DOrigin=0, + DistanceToEntryPoint=0, + StabilityOptions=stabilility_options, + DesignScenarios=[design_scenario], + WaternetOptions=waternet_options, + DesignOptions=designoptions, + ) + ] + def define_segment(self, soil_profile_names: List[str]) -> List[Segment]: # first define the soil layers profile_1 = SoilGeometryProbabilityElement( @@ -192,13 +260,15 @@ test_stabilty_parameters = self.create_stability_parameters() soil_profiles = self.create_profiles([soils[0].Name, soils[1].Name]) segments = self.define_segment([soil_profiles[0].Name, soil_profiles[0].Name]) + locations = self.define_locations() # Make input class model = DamInput( SurfaceLines=list_of_surface_lines, Soils=soils, StabilityParameters=test_stabilty_parameters, SoilProfiles1D=soil_profiles, Segments=segments, + Locations=locations, ) assert model # run test Index: DamClients/DamPythonInterface/trunk/src/tests/test_location.py =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/tests/test_location.py (revision 0) +++ DamClients/DamPythonInterface/trunk/src/tests/test_location.py (revision 3501) @@ -0,0 +1,87 @@ +# 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.location import * + +import pytest +from .utils import TestUtils +from pathlib import Path +from lxml import etree + + +class TestLocation: + @pytest.mark.integrationtest + def test_serialize_location(self): + # define stability options + design_scenario_1 = DesignScenario( + Id="1", + RiverLevel=3.2, + PlLineOffsetBelowDikeTopAtRiver=1.1, + PlLineOffsetBelowDikeTopAtPolder=2.1, + PlLineOffsetBelowShoulderBaseInside=3.1, + PlLineOffsetBelowDikeToeAtPolder=4.1, + UpliftCriterionStability=5, + UpliftCriterionPiping=0, + RequiredSafetyFactorStabilityInnerSlope=1.6, + RequiredSafetyFactorStabilityOuterSlope=1.5, + RequiredSafetyFactorPiping=2.1, + PolderLevel=4, + ) + design_scenario_2 = DesignScenario( + Id="2", + RiverLevel=2.2, + PlLineOffsetBelowDikeTopAtRiver=2.1, + PlLineOffsetBelowDikeTopAtPolder=1.1, + PlLineOffsetBelowShoulderBaseInside=5.1, + PlLineOffsetBelowDikeToeAtPolder=6.1, + UpliftCriterionStability=1, + UpliftCriterionPiping=2, + RequiredSafetyFactorStabilityInnerSlope=2.6, + RequiredSafetyFactorStabilityOuterSlope=2.5, + RequiredSafetyFactorPiping=1.1, + PolderLevel=6, + ) + # define location + model_location = Location( + SurfaceLineName="Line 1", + SegmentName="Input segment", + Name="Location of dike", + DikeEmbankmentMaterial="clay", + XSoilGeometry2DOrigin=4, + DesignScenarios=[design_scenario_1, design_scenario_2], + ) + # check initial expectations + assert model_location.Name == "Location of dike" + # run test + root = model_location.serialize() + # test output + xml_output = Path( + TestUtils.get_output_test_data_dir(""), "test_serialize_location.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_serialize_location.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_data/test_serialize_location.xml =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/tests/test_data/test_serialize_location.xml (revision 0) +++ DamClients/DamPythonInterface/trunk/src/tests/test_data/test_serialize_location.xml (revision 3501) @@ -0,0 +1,6 @@ + + + + + + Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/__init__.py =================================================================== diff -u -r3494 -r3501 --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/__init__.py (.../__init__.py) (revision 3494) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/__init__.py (.../__init__.py) (revision 3501) @@ -27,3 +27,4 @@ from .soilprofile1D import * from .stability_parameters import * from .surface_line import * +from .location import * Index: DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml =================================================================== diff -u -r3499 -r3501 --- DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml (.../test_full_output.xml) (revision 3499) +++ DamClients/DamPythonInterface/trunk/src/tests/test_output/test_full_output.xml (.../test_full_output.xml) (revision 3501) @@ -1,4 +1,13 @@ + + + + + + + + + Index: DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst =================================================================== diff -u -r3491 -r3501 --- DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst (.../input.rst) (revision 3491) +++ DamClients/DamPythonInterface/trunk/doc/source/dev/input.rst (.../input.rst) (revision 3501) @@ -52,4 +52,13 @@ .. automodule:: dampythoninterface.segment :members: + :undoc-members: + + +Location +-------- + + +.. automodule:: dampythoninterface.location + :members: :undoc-members: \ No newline at end of file Index: DamClients/DamPythonInterface/trunk/src/tests/test_base_class.py =================================================================== diff -u --- DamClients/DamPythonInterface/trunk/src/tests/test_base_class.py (revision 0) +++ DamClients/DamPythonInterface/trunk/src/tests/test_base_class.py (revision 3501) @@ -0,0 +1,47 @@ +# 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.base_class import BaseDataClass + +import pytest +from enum import IntEnum, Enum + + +class TestEnum(Enum): + First_value = "First" + Second_value = "Second" + + +class TestIntEnum(IntEnum): + First_value = 0 + Second_value = 1 + + +class TestBaseClass: + @pytest.mark.unittest + def test_create_element_from_dictionary(self): + # initialize class + model = BaseDataClass() + # test initial expectations + assert model + # run test + xml_element = model.create_element_from_dictionary() + # check results + assert xml_element.attrib == {} Index: DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py =================================================================== diff -u -r3500 -r3501 --- DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py (.../input.py) (revision 3500) +++ DamClients/DamPythonInterface/trunk/src/dampythoninterface/input.py (.../input.py) (revision 3501) @@ -31,6 +31,7 @@ from .stability_parameters import StabilityParameters from .soilprofile1D import SoilProfile1D from .segment import Segment +from .location import Location class DamInput(BaseDataClass): @@ -46,6 +47,7 @@ StabilityParameters: Optional[StabilityParameters] SoilProfiles1D: List[SoilProfile1D] = [] Segments: List[Segment] = [] + Locations: List[Location] = [] @validator("SoilProfiles1D") def soil_profiles_contain_soils(cls, v, values, **kwargs): @@ -81,6 +83,11 @@ """ input_root = et.Element("Input") + # create location line element + locations_root = et.Element("Locations") + for location in self.Locations: + locations_root.append(location.serialize()) + input_root.append(locations_root) # create surface line element SurfaceLines_root = et.Element("SurfaceLines") for surface_line in self.SurfaceLines: