From 52874e93ead9bc9bca84bd83ea93f8416a52ca3a Mon Sep 17 00:00:00 2001 From: joergbuchwald Date: Fri, 23 Aug 2024 13:28:59 +0200 Subject: [PATCH] Bugfix release 0.403 / first ogstools release --- ogs6py/_version.py | 2 +- ogs6py/classes/__init__.py | 8 + ogs6py/classes/build_tree.py | 58 +- ogs6py/classes/curves.py | 38 +- ogs6py/classes/display.py | 125 ++- ogs6py/classes/geo.py | 13 +- ogs6py/classes/linsolvers.py | 114 ++- ogs6py/classes/local_coordinate_system.py | 51 +- ogs6py/classes/media.py | 377 +++++---- ogs6py/classes/mesh.py | 48 +- ogs6py/classes/nonlinsolvers.py | 47 +- ogs6py/classes/parameters.py | 105 ++- ogs6py/classes/processes.py | 322 +++++--- ogs6py/classes/processvars.py | 194 +++-- ogs6py/classes/properties.py | 215 ++++-- ogs6py/classes/python_script.py | 18 +- ogs6py/classes/timeloop.py | 454 +++++++---- ogs6py/ogs.py | 883 +++++++++++++++------- tests/test_ogs6py.py | 2 +- 19 files changed, 2073 insertions(+), 1001 deletions(-) diff --git a/ogs6py/_version.py b/ogs6py/_version.py index d7d6487..7d7835b 100644 --- a/ogs6py/_version.py +++ b/ogs6py/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Provide a central version.""" -__version__ = "0.402" +__version__ = "0.403" diff --git a/ogs6py/classes/__init__.py b/ogs6py/classes/__init__.py index d7ba3a5..dbc7925 100644 --- a/ogs6py/classes/__init__.py +++ b/ogs6py/classes/__init__.py @@ -1,3 +1,11 @@ +# Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) +# Distributed under a Modified BSD License. +# See accompanying file LICENSE.txt or +# http://www.opengeosys.org/project/license +# + +# Author: Joerg Buchwald (Helmholtz Centre for Environmental Research GmbH - UFZ) + from . import display from . import mesh from . import geo diff --git a/ogs6py/classes/build_tree.py b/ogs6py/classes/build_tree.py index d5262ef..6a73ab8 100644 --- a/ogs6py/classes/build_tree.py +++ b/ogs6py/classes/build_tree.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. @@ -6,51 +5,73 @@ http://www.opengeosys.org/project/license """ + + +from typing import TypeAlias + from lxml import etree as ET +OptionalETElement: TypeAlias = ( + ET.Element +) # ToDo this should be Optional[ET.Element] + + # pylint: disable=C0103, R0902, R0914, R0913 class BuildTree: - """ helper class to create a nested dictionary + """helper class to create a nested dictionary representing the xml structure """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree - def _get_root(self): - root = self.tree.getroot() - return root + def _get_root(self) -> ET.Element: + return self.tree.getroot() @classmethod - def _convertargs(cls, args): + def _convertargs(cls, args: dict[str, str]) -> None: """ convert arguments that are not lists or dictionaries to strings """ for item, value in args.items(): - if not isinstance(value, (list, dict)): + if not isinstance(value, list | dict): args[item] = str(value) @classmethod - def populate_tree(cls, parent, tag, text=None, attr=None, overwrite=False): + def populate_tree( + cls, + parent: ET.Element, + tag: str, + text: str | None = None, + attr: dict[str, str] | None = None, + overwrite: bool = False, + ) -> ET.Element: """ method to create dictionary from an xml entity """ q = None - if not tag is None: + if tag is not None: if overwrite is True: for child in parent: if child.tag == tag: q = child if q is None: q = ET.SubElement(parent, tag) - if not text is None: + if text is not None: q.text = str(text) - if not attr is None: + if attr is not None: for key, val in attr.items(): q.set(key, str(val)) return q @classmethod - def get_child_tag(cls, parent, tag, attr=None, attr_val=None): + def get_child_tag( + cls, + parent: ET.Element, + tag: str, + attr: dict[str, str] | None = None, + attr_val: str | None = None, + ) -> OptionalETElement: """ search for child tag based on tag and possible attributes """ @@ -65,7 +86,9 @@ def get_child_tag(cls, parent, tag, attr=None, attr_val=None): return q @classmethod - def get_child_tag_for_type(cls, parent, tag, subtagval, subtag="type"): + def get_child_tag_for_type( + cls, parent: ET.Element, tag: str, subtagval: str, subtag: str = "type" + ) -> OptionalETElement: """ search for child tag based on subtag type """ @@ -73,7 +96,8 @@ def get_child_tag_for_type(cls, parent, tag, subtagval, subtag="type"): for child in parent: if child.tag == tag: for subchild in child: - if subchild.tag == subtag: - if subchild.text == subtagval: - q = child + if (subchild.tag == subtag) and ( + subchild.text == subtagval + ): + q = child return q diff --git a/ogs6py/classes/curves.py b/ogs6py/classes/curves.py index c3b3eec..32d676b 100644 --- a/ogs6py/classes/curves.py +++ b/ogs6py/classes/curves.py @@ -1,24 +1,29 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Curves(build_tree.BuildTree): """ Class to create the curve section of the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() self.curves = self.populate_tree(self.root, "curves", overwrite=True) - def add_curve(self, **args): + def add_curve(self, **args: Any) -> None: """ Adds a new curve. @@ -29,23 +34,28 @@ def add_curve(self, **args): values : `list` """ if "name" not in args: - raise KeyError("No curve name given.") + msg = "No curve name given." + raise KeyError(msg) if "coords" not in args: - raise KeyError("No coordinates given.") + msg = "No coordinates given." + raise KeyError(msg) if "values" not in args: - raise KeyError("No values given.") + msg = "No values given." + raise KeyError(msg) if len(args["coords"]) != len(args["values"]): - raise ValueError("Number of time coordinate points differs from number of values") + msg = """Number of time coordinate points differs \ + from number of values""" + raise ValueError(msg) curve = self.populate_tree(self.curves, "curve") - self.populate_tree(curve, "name", args['name']) + self.populate_tree(curve, "name", args["name"]) coord_str = "" value_str = "" for i, coord in enumerate(args["coords"]): - if i < (len(args["coords"])-1): + if i < (len(args["coords"]) - 1): coord_str = coord_str + str(coord) + " " value_str = value_str + str(args["values"][i]) + " " - if i == (len(args["coords"])-1): + if i == (len(args["coords"]) - 1): coord_str = coord_str + str(coord) value_str = value_str + str(args["values"][i]) - self.populate_tree(curve, 'coords', text=coord_str) - self.populate_tree(curve, 'values', text=value_str) + self.populate_tree(curve, "coords", text=coord_str) + self.populate_tree(curve, "values", text=value_str) diff --git a/ogs6py/classes/display.py b/ogs6py/classes/display.py index f7d5135..c34954a 100644 --- a/ogs6py/classes/display.py +++ b/ogs6py/classes/display.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. @@ -6,48 +5,102 @@ http://www.opengeosys.org/project/license """ +from contextlib import suppress + +from lxml import etree as ET + try: - from IPython.display import display, Markdown, Latex + from IPython.display import Markdown, display + verbose = True except ImportError: verbose = False + # pylint: disable=C0103, R0902, R0914, R0913 class Display: - """ helper class to create a nested dictionary + """helper class to create a nested dictionary representing the xml structure """ - def __init__(self,tree): + + def __init__(self, tree: ET.ElementTree): if verbose is True: - display(Markdown('## OpenGeoSys project file')) - display(Markdown('### Main Info')) - display(Markdown(f"**Process name:** {tree.find('./processes/process/name').text}")) - display(Markdown(f"**Process type:** {tree.find('./processes/process/type').text}")) - t_end = float(tree.find('./time_loop/processes/process/time_stepping/t_end').text) - t_init = float(tree.find('./time_loop/processes/process/time_stepping/t_initial').text) - display(Markdown(f"**Simulation time:** {t_end-t_init}")) - proc_vars = tree.findall("./process_variables/process_variable") - display(Markdown(f"**Output prefix:** {tree.find('./time_loop/output/prefix').text}")) - display(Markdown('### Boundary conditions')) - for var in proc_vars: - proc_var_entries = var.getchildren() - for entry in proc_var_entries: - if entry.tag == "name": - display(Markdown(f"**Process Variable:** {entry.text}")) - if entry.tag == "initial_condition": - display(Markdown(f" - **Initial Condition:** {entry.text}")) - if entry.tag == "order": - display(Markdown(f" - **Order:** {entry.text}")) - if entry.tag == "boundary_conditions": - bcs = entry.getchildren() - for bc in bcs: - bc_entries = bc.getchildren() - for subentry in bc_entries: - if subentry.tag == "type": - display(Markdown(f" - **Type:** {subentry.text}")) - if subentry.tag == "mesh": - display(Markdown(f" - **Mesh:** {subentry.text}")) - if subentry.tag == "geometrical_set": - display(Markdown(f" - **Geometrical Set:** {subentry.text}")) - if subentry.tag == "geometry": - display(Markdown(f" - **Geometry:** {subentry.text}")) + display(Markdown("## OpenGeoSys project file")) + display(Markdown("### Main Info")) + with suppress(AttributeError): + display( + Markdown( + f"**Process name:** {tree.find('./processes/process/name').text}" + ) + ) + with suppress(AttributeError): + display( + Markdown( + f"**Process type:** {tree.find('./processes/process/type').text}" + ) + ) + with suppress(AttributeError): + t_end = float( + tree.find( + "./time_loop/processes/process/time_stepping/t_end" + ).text + ) + t_init = float( + tree.find( + "./time_loop/processes/process/time_stepping/t_initial" + ).text + ) + display(Markdown(f"**Simulation time:** {t_end-t_init}")) + with suppress(AttributeError): + proc_vars = tree.findall("./process_variables/process_variable") + with suppress(AttributeError): + display( + Markdown( + f"**Output prefix:** {tree.find('./time_loop/output/prefix').text}" + ) + ) + display(Markdown("### Boundary conditions")) + for var in proc_vars: + proc_var_entries = var.getchildren() + for entry in proc_var_entries: + if entry.tag == "name": + display( + Markdown(f"**Process Variable:** {entry.text}") + ) + if entry.tag == "initial_condition": + display( + Markdown( + f" - **Initial Condition:** {entry.text}" + ) + ) + if entry.tag == "order": + display(Markdown(f" - **Order:** {entry.text}")) + if entry.tag == "boundary_conditions": + bcs = entry.getchildren() + for bc in bcs: + bc_entries = bc.getchildren() + for subentry in bc_entries: + if subentry.tag == "type": + display( + Markdown( + f" - **Type:** {subentry.text}" + ) + ) + if subentry.tag == "mesh": + display( + Markdown( + f" - **Mesh:** {subentry.text}" + ) + ) + if subentry.tag == "geometrical_set": + display( + Markdown( + f" - **Geometrical Set:** {subentry.text}" + ) + ) + if subentry.tag == "geometry": + display( + Markdown( + f" - **Geometry:** {subentry.text}" + ) + ) diff --git a/ogs6py/classes/geo.py b/ogs6py/classes/geo.py index 0dac5dc..25f8158 100644 --- a/ogs6py/classes/geo.py +++ b/ogs6py/classes/geo.py @@ -1,24 +1,27 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Geo(build_tree.BuildTree): """ Class containing the geometry file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() self.populate_tree(self.root, "geometry", overwrite=True) - def add_geometry(self, filename): + def add_geometry(self, filename: str) -> None: """ Adds a geometry file. diff --git a/ogs6py/classes/linsolvers.py b/ogs6py/classes/linsolvers.py index 3c4c987..243cb60 100644 --- a/ogs6py/classes/linsolvers.py +++ b/ogs6py/classes/linsolvers.py @@ -1,80 +1,126 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class LinSolvers(build_tree.BuildTree): """ Class for defining a linear solvers in the project file" """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.lss = self.populate_tree(self.root, 'linear_solvers', overwrite=True) + self.lss = self.populate_tree( + self.root, "linear_solvers", overwrite=True + ) - def add_lin_solver(self, **args): + def add_lin_solver(self, **args: Any) -> None: """ Adds a linear solver Parameters ---------- name : `str` + linear solver name kind : `str` - one of `petsc`, `eigen` or `lis` + one of `petsc`, `eigen` or `lis` solver_type : `str` + Eigen solver type precon_type : `str`, optional + Eigen preconditioner type max_iteration_step : `int`, optional + max. iteration step scaling : `str`, optional - 1 or 0 + scaling of diagonal error_tolerance : `float` + error tolerance prefix : `str`, optional - required for petsc solver - parameters : `str` for petsc only + prefix for petsc solver + parameters : `str` + petsc parameter configuration lis : `str` for lis only + lis parameter configuration """ self._convertargs(args) if "name" not in args: - raise KeyError("You need to provide a name for the linear solver.") - ls = self.populate_tree(self.lss, 'linear_solver', overwrite=True) - self.populate_tree(ls, 'name', text=args['name'], overwrite=True) + msg = "You need to provide a name for the linear solver." + raise KeyError(msg) + ls = self.populate_tree(self.lss, "linear_solver", overwrite=True) + self.populate_tree(ls, "name", text=args["name"], overwrite=True) if "kind" not in args: - raise KeyError("No kind given. Please specify the linear \ - solver library (e.g.: eigen, petsc, lis).") - if args['kind'] == "eigen": - eigen = self.populate_tree(ls, 'eigen', overwrite=True) - self.populate_tree(eigen, 'solver_type', text=args['solver_type'], overwrite=True) + msg = """No kind given. Please specify the linear \ + solver library (e.g.: eigen, petsc, lis).""" + + raise KeyError(msg) + if args["kind"] == "eigen": + eigen = self.populate_tree(ls, "eigen", overwrite=True) + self.populate_tree( + eigen, "solver_type", text=args["solver_type"], overwrite=True + ) if "precon_type" in args: - self.populate_tree(eigen, 'precon_type', text=args['precon_type'], overwrite=True) + self.populate_tree( + eigen, + "precon_type", + text=args["precon_type"], + overwrite=True, + ) if "max_iteration_step" in args: - self.populate_tree(eigen, 'max_iteration_step', text=args['max_iteration_step'], overwrite=True) + self.populate_tree( + eigen, + "max_iteration_step", + text=args["max_iteration_step"], + overwrite=True, + ) if "error_tolerance" in args: - self.populate_tree(eigen, 'error_tolerance', text=args['error_tolerance'], overwrite=True) + self.populate_tree( + eigen, + "error_tolerance", + text=args["error_tolerance"], + overwrite=True, + ) if "scaling" in args: - self.populate_tree(eigen, 'scaling', text=args['scaling'], overwrite=True) - elif args['kind'] == "lis": + self.populate_tree( + eigen, "scaling", text=args["scaling"], overwrite=True + ) + elif args["kind"] == "lis": if "lis" in args: lis_string = args["lis"] else: - lis_string = (f"-i {args['solver_type']} -p {args['precon_type']}" + lis_string = ( + f"-i {args['solver_type']} -p {args['precon_type']}" f" -tol {args['error_tolerance']}" - f" -maxiter {args['max_iteration_step']}") - self.populate_tree(ls, 'lis', text=lis_string, overwrite=True) - elif args['kind'] == "petsc": - petsc = self.populate_tree(ls, 'petsc', overwrite=True) + f" -maxiter {args['max_iteration_step']}" + ) + self.populate_tree(ls, "lis", text=lis_string, overwrite=True) + elif args["kind"] == "petsc": + petsc = self.populate_tree(ls, "petsc", overwrite=True) prefix = "" if "prefix" in args: - self.populate_tree(petsc, 'prefix', args['prefix'], overwrite=True) - prefix = args['prefix'] + self.populate_tree( + petsc, "prefix", args["prefix"], overwrite=True + ) + prefix = args["prefix"] if "parameters" in args: - self.populate_tree(petsc, 'parameters', args['parameters'], overwrite=True) + self.populate_tree( + petsc, "parameters", args["parameters"], overwrite=True + ) else: - petsc_string = (f"-{prefix}_ksp_type {args['solver_type']} -{prefix}_pc_type" + petsc_string = ( + f"-{prefix}_ksp_type {args['solver_type']} -{prefix}_pc_type" f" {args['precon_type']} -{prefix}_ksp_rtol {args['error_tolerance']}" - f" -{prefix}_ksp_max_it {args['max_iteration_step']}") - self.populate_tree(petsc, 'parameters', petsc_string, overwrite=True) + f" -{prefix}_ksp_max_it {args['max_iteration_step']}" + ) + self.populate_tree( + petsc, "parameters", petsc_string, overwrite=True + ) diff --git a/ogs6py/classes/local_coordinate_system.py b/ogs6py/classes/local_coordinate_system.py index 10853a9..a96af5d 100644 --- a/ogs6py/classes/local_coordinate_system.py +++ b/ogs6py/classes/local_coordinate_system.py @@ -1,24 +1,31 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class LocalCoordinateSystem(build_tree.BuildTree): """ Class for defining a local coordinate system in the project file" """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.lcs = None + self.lcs = self.populate_tree( + self.root, "local_coordinate_system", overwrite=True + ) - def add_basis_vec(self, **args): + def add_basis_vec(self, **args: Any) -> None: """ Adds a basis @@ -31,12 +38,32 @@ def add_basis_vec(self, **args): basis_vector_2 : `str` name of the parameter containing the basis vector """ - self._convertargs(args) - self.lcs = self.populate_tree(self.root, "local_coordinate_system", overwrite=True) if "basis_vector_0" not in args: - raise KeyError("no vector given") - self.populate_tree(self.lcs, "basis_vector_0", text=args["basis_vector_0"]) + msg = "No vector given." + raise KeyError(msg) + if args["basis_vector_0"] is None: + self.populate_tree( + self.lcs, "basis_vector_0", attr={"implicit": "true"} + ) + else: + self.populate_tree( + self.lcs, "basis_vector_0", text=args["basis_vector_0"] + ) if "basis_vector_1" in args: - self.populate_tree(self.lcs, "basis_vector_1", text=args["basis_vector_1"]) + if args["basis_vector_1"] is None: + self.populate_tree( + self.lcs, "basis_vector_1", attr={"implicit": "true"} + ) + else: + self.populate_tree( + self.lcs, "basis_vector_1", text=args["basis_vector_1"] + ) if "basis_vector_2" in args: - self.populate_tree(self.lcs, "basis_vector_2", text=args["basis_vector_2"]) + if args["basis_vector_2"] is None: + self.populate_tree( + self.lcs, "basis_vector_2", attr={"implicit": "true"} + ) + else: + self.populate_tree( + self.lcs, "basis_vector_2", text=args["basis_vector_2"] + ) diff --git a/ogs6py/classes/media.py b/ogs6py/classes/media.py index 2d15a00..948ad9e 100644 --- a/ogs6py/classes/media.py +++ b/ogs6py/classes/media.py @@ -1,183 +1,260 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Media(build_tree.BuildTree): """ Class for defining a media material properties." """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() self.media = self.populate_tree(self.root, "media", overwrite=True) - self.properties = {"AverageMolarMass": [], + self.properties: dict[str, list[str]] = { + "AverageMolarMass": [], "BishopsSaturationCutoff": ["cutoff_value"], "BishopsPowerLaw": ["exponent"], - "CapillaryPressureRegularizedVanGenuchten": ["exponent", - "p_b", - "residual_gas_saturation", - "residual_liquid_saturation"], - "CapillaryPressureVanGenuchten": ["exponent", - "maximum_capillary_pressure" - "p_b", - "residual_gas_saturation", - "residual_liquid_saturation"], - "ClausiusClapeyron": ["critical_pressure", - "critical_temperature", - "reference_pressure", - "reference_temperature", - "triple_pressure", - "triple_temperature"], + "CapillaryPressureRegularizedVanGenuchten": [ + "exponent", + "p_b", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "CapillaryPressureVanGenuchten": [ + "exponent", + "maximum_capillary_pressurep_b", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "ClausiusClapeyron": [ + "critical_pressure", + "critical_temperature", + "reference_pressure", + "reference_temperature", + "triple_pressure", + "triple_temperature", + ], "Constant": ["value"], - "Curve" : ["curve", "independent_variable"], + "Curve": ["curve", "independent_variable"], "DupuitPermeability": ["parameter_name"], "EffectiveThermalConductivityPorosityMixing": [], - "EmbeddedFracturePermeability": ["intrinsic_permeability", - "initial_aperture", - "mean_frac_distance", - "threshold_strain", - "fracture_normal", - "fracture_rotation_xy", - "fracture_rotation_yz"], + "EmbeddedFracturePermeability": [ + "intrinsic_permeability", + "initial_aperture", + "mean_frac_distance", + "threshold_strain", + "fracture_normal", + "fracture_rotation_xy", + "fracture_rotation_yz", + ], "Function": ["value"], - "Exponential": ["offset","reference_value"], - "GasPressureDependentPermeability": ["initial_permeability", - "a1", "a2", - "pressure_threshold", - "minimum_permeability", - "maximum_permeability"], + "Exponential": ["offset", "reference_value"], + "GasPressureDependentPermeability": [ + "initial_permeability", + "a1", + "a2", + "pressure_threshold", + "minimum_permeability", + "maximum_permeability", + ], "IdealGasLaw": [], "IdealGasLawBinaryMixture": [], "KozenyCarmanModel": ["intitial_permeability", "initial_prosity"], "Linear": ["reference_value"], - "LinearSaturationSwellingStress" : ["coefficient", "reference_saturation"], - "LinearWaterVapourLatentHeat" : [], - "OrthotropicEmbeddedFracturePermeability": ["intrinsic_permeability", - "mean_frac_distances", - "threshold_strains", - "fracture_normals", - "fracture_rotation_xy", - "fracture_rotation_yz", - "jacobian_factor"], + "LinearSaturationSwellingStress": [ + "coefficient", + "reference_saturation", + ], + "LinearWaterVapourLatentHeat": [], + "OrthotropicEmbeddedFracturePermeability": [ + "intrinsic_permeability", + "mean_frac_distances", + "threshold_strains", + "fracture_normals", + "fracture_rotation_xy", + "fracture_rotation_yz", + "jacobian_factor", + ], "Parameter": ["parameter_name"], - "PermeabilityMohrCoulombFailureIndexModel": ["cohesion", - "fitting_factor", - "friction_angle", - "initial_ppermeability", - "maximum_permeability", - "reference_permeability", - "tensile_strength_parameter"], - "PermeabilityOrthotropicPowerLaw": ["exponents", - "intrinsic_permeabilities"], - "PorosityFromMassBalance": ["initial_porosity", - "maximal_porosity", - "minimal_porosity"], - "RelPermBrooksCorey": ["lambda", - "min_relative_permeability" - "residual_gas_saturation", - "residual_liquid_saturation"], - "RelPermBrooksCoreyNonwettingPhase": ["lambda", - "min_relative_permeability" - "residual_gas_saturation", - "residual_liquid_saturation"], + "PermeabilityMohrCoulombFailureIndexModel": [ + "cohesion", + "fitting_factor", + "friction_angle", + "initial_ppermeability", + "maximum_permeability", + "reference_permeability", + "tensile_strength_parameter", + ], + "PermeabilityOrthotropicPowerLaw": [ + "exponents", + "intrinsic_permeabilities", + ], + "PorosityFromMassBalance": [ + "initial_porosity", + "maximal_porosity", + "minimal_porosity", + ], + "RelPermBrooksCorey": [ + "lambda", + "min_relative_permeabilityresidual_gas_saturation", + "residual_liquid_saturation", + ], + "RelPermBrooksCoreyNonwettingPhase": [ + "lambda", + "min_relative_permeabilityresidual_gas_saturation", + "residual_liquid_saturation", + ], "RelPermLiakopoulos": [], - "RelativePermeabilityNonWettingVanGenuchten": ["exponent", - "minimum_relative_permeability", - "residual_gas_saturation", - "residual_liquid_saturation"], - "RelativePermeabilityUdell": ["min_relative_permeability", - "residual_gas_saturation", - "residual_liquid_saturation"], - "RelativePermeabilityUdellNonwettingPhase": ["min_relative_permeability", - "residual_gas_saturation", - "residual_liquid_saturation"], - "RelativePermeabilityVanGenuchten": ["exponent", - "minimum_relative_permeability_liquid", - "residual_gas_saturation", - "residual_liquid_saturation"], - "SaturationBrooksCorey": ["entry_pressure", - "lambda", - "residual_gas_saturation", - "residual_liquid_saturation"], - "SaturationDependentSwelling": ["exponents", - "lower_saturation_limit", - "swelling_pressures", - "upper_saturation_limit"], - "SaturationDependentThermalConductivity": ["dry","wet"], - "SaturationExponential": ["exponent", - "maximum_capillary_pressure", - "residual_gas_saturation", - "residual_liquid_saturation"], + "RelativePermeabilityNonWettingVanGenuchten": [ + "exponent", + "minimum_relative_permeability", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "RelativePermeabilityUdell": [ + "min_relative_permeability", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "RelativePermeabilityUdellNonwettingPhase": [ + "min_relative_permeability", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "RelativePermeabilityVanGenuchten": [ + "exponent", + "minimum_relative_permeability_liquid", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "SaturationBrooksCorey": [ + "entry_pressure", + "lambda", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "SaturationDependentSwelling": [ + "exponents", + "lower_saturation_limit", + "swelling_pressures", + "upper_saturation_limit", + ], + "SaturationDependentThermalConductivity": ["dry", "wet"], + "SaturationExponential": [ + "exponent", + "maximum_capillary_pressure", + "residual_gas_saturation", + "residual_liquid_saturation", + ], "SaturationLiakopoulos": [], - "SaturationWeightedThermalConductivity": ["mean_type", "dry_thermal_conductivity","wet_thermal_conductivity"], - "SaturationVanGenuchten": ["exponent", - "p_b", - "residual_gas_saturation", - "residual_liquid_saturation"], - "SoilThermalConductivitySomerton": ["dry_thermal_conductivity", - "wet_thermal_conductivity"], - "StrainDependentPermeability": ["initial_permeability", - "b1", "b2", "b3", - "minimum_permeability", - "maximum_permeability"], - "TemperatureDependentDiffusion": ["activation_energy", - "reference_diffusion", - "reference_temperature"], - "TransportPorosityFromMassBalance": ["initial_porosity", - "maximal_porosity", - "minimal_porosity"], + "SaturationWeightedThermalConductivity": [ + "mean_type", + "dry_thermal_conductivity", + "wet_thermal_conductivity", + ], + "SaturationVanGenuchten": [ + "exponent", + "p_b", + "residual_gas_saturation", + "residual_liquid_saturation", + ], + "SoilThermalConductivitySomerton": [ + "dry_thermal_conductivity", + "wet_thermal_conductivity", + ], + "StrainDependentPermeability": [ + "initial_permeability", + "b1", + "b2", + "b3", + "minimum_permeability", + "maximum_permeability", + ], + "TemperatureDependentDiffusion": [ + "activation_energy", + "reference_diffusion", + "reference_temperature", + ], + "TransportPorosityFromMassBalance": [ + "initial_porosity", + "maximal_porosity", + "minimal_porosity", + ], "VapourDiffusionFEBEX": ["tortuosity"], "VapourDiffusionPMQ": [], - "VermaPruessModel": ["critical_porosity", - "exponent", - "initial_permeability", - "initial_porosity"], + "VermaPruessModel": [ + "critical_porosity", + "exponent", + "initial_permeability", + "initial_porosity", + ], "WaterVapourDensity": [], "WaterDensityIAPWSIF97Region1": [], - "WaterVapourLatentHeatWithCriticalTemperature": [] - } + "WaterVapourLatentHeatWithCriticalTemperature": [], + } - def _generate_generic_property(self, property_, args): + def _generate_generic_property( + self, property_: ET.Element, args: dict[str, Any] + ) -> None: for parameter in self.properties[args["type"]]: self.populate_tree(property_, parameter, text=args[parameter]) - def _generate_linear_property(self, property_, args): + def _generate_linear_property( + self, property_: ET.Element, args: dict[str, Any] + ) -> None: for parameter in self.properties[args["type"]]: self.populate_tree(property_, parameter, text=args[parameter]) for var, param in args["independent_variables"].items(): - ind_var = self.populate_tree(property_, 'independent_variable') + ind_var = self.populate_tree(property_, "independent_variable") self.populate_tree(ind_var, "variable_name", text=var) - attributes = ['reference_condition','slope'] + attributes = ["reference_condition", "slope"] for attrib in attributes: self.populate_tree(ind_var, attrib, text=str(param[attrib])) - def _generate_function_property(self, property_, args): + def _generate_function_property( + self, property_: ET.Element, args: dict[str, Any] + ) -> None: for parameter in self.properties[args["type"]]: - value = self.populate_tree(property_, parameter, text=args[parameter]) + value = self.populate_tree( + property_, parameter, text=args[parameter] + ) self.populate_tree(value, "expression", text=args["expression"]) for dvar in args["dvalues"]: dvalue = self.populate_tree(property_, "dvalue") self.populate_tree(dvalue, "variable_name", text=dvar) - self.populate_tree(dvalue, "expression", text=args["dvalues"][dvar]["expression"]) + self.populate_tree( + dvalue, "expression", text=args["dvalues"][dvar]["expression"] + ) - def _generate_exponential_property(self, property_, args): + def _generate_exponential_property( + self, property_: ET.Element, args: dict[str, Any] + ) -> None: for parameter in self.properties[args["type"]]: self.populate_tree(property_, parameter, text=args[parameter]) - exponent = self.populate_tree(property_, 'exponent') - self.populate_tree(exponent, "variable_name", text=args["exponent"]["variable_name"]) - attributes = ['reference_condition','factor'] + exponent = self.populate_tree(property_, "exponent") + self.populate_tree( + exponent, "variable_name", text=args["exponent"]["variable_name"] + ) + attributes = ["reference_condition", "factor"] for attrib in attributes: - self.populate_tree(exponent, attrib, text=str(args["exponent"][attrib])) + self.populate_tree( + exponent, attrib, text=str(args["exponent"][attrib]) + ) - def add_property(self, **args): + def add_property(self, **args: Any) -> None: """ Adds a property to medium/phase @@ -204,19 +281,25 @@ def add_property(self, **args): if entry.get("id") == args["medium_id"]: medium = entry if medium is None: - medium = self.populate_tree(self.media, "medium", attr={"id": args["medium_id"]}) + medium = self.populate_tree( + self.media, "medium", attr={"id": args["medium_id"]} + ) if "phase_type" in args: phases = self.get_child_tag(medium, "phases") if phases is None: phases = self.populate_tree(medium, "phases") - phase = self.get_child_tag_for_type(phases, "phase", args['phase_type']) + phase = self.get_child_tag_for_type( + phases, "phase", args["phase_type"] + ) if phase is None: phase = self.populate_tree(phases, "phase") - self.populate_tree(phase, "type", text=args['phase_type']) + self.populate_tree(phase, "type", text=args["phase_type"]) if "component_name" in args: components = self.populate_tree(phase, "components") component = self.populate_tree(components, "component") - self.populate_tree(component, "name", text=args['component_name']) + self.populate_tree( + component, "name", text=args["component_name"] + ) properties = self.populate_tree(component, "properties") else: properties = self.populate_tree(phase, "properties") @@ -225,12 +308,22 @@ def add_property(self, **args): components = self.get_child_tag(phase, "components") if components is None: components = self.populate_tree(phase, "components") - component = self.get_child_tag_for_type(components, "component", - args['component_name'], subtag="name") + component = self.get_child_tag_for_type( + components, + "component", + args["component_name"], + subtag="name", + ) if component is None: - component = self.populate_tree(components, "component") - self.populate_tree(component, "name", text=args['component_name']) - properties = self.populate_tree(component, "properties", overwrite=True) + component = self.populate_tree( + components, "component" + ) + self.populate_tree( + component, "name", text=args["component_name"] + ) + properties = self.populate_tree( + component, "properties", overwrite=True + ) else: properties = self.get_child_tag(phase, "properties") else: @@ -243,17 +336,21 @@ def add_property(self, **args): for param in base_property_param: self.populate_tree(property_, param, text=args[param]) try: - if args['type'] == "Linear": + if args["type"] == "Linear": self._generate_linear_property(property_, args) - elif args['type'] == "Exponential": + elif args["type"] == "Exponential": self._generate_exponential_property(property_, args) - elif args['type'] == "Function": + elif args["type"] == "Function": self._generate_function_property(property_, args) else: self._generate_generic_property(property_, args) except KeyError: print("Material property parameters incomplete for") if "phase_type" in args: - print(f"Medium {args['medium_id']}->{args['phase_type']}->{args['name']}[{args['type']}]") + print( + f"Medium {args['medium_id']}->{args['phase_type']}->{args['name']}[{args['type']}]" + ) else: - print(f"Medium {args['medium_id']}->{args['name']}[{args['type']}]") + print( + f"Medium {args['medium_id']}->{args['name']}[{args['type']}]" + ) diff --git a/ogs6py/classes/mesh.py b/ogs6py/classes/mesh.py index 81b3c87..82d37c8 100644 --- a/ogs6py/classes/mesh.py +++ b/ogs6py/classes/mesh.py @@ -1,19 +1,24 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Mesh(build_tree.BuildTree): """ Class for defining meshes in the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() self.geometry = self.root.find("./geometry") @@ -22,7 +27,9 @@ def __init__(self, tree): if self.meshes is None: self.mesh = self.populate_tree(self.root, "mesh", overwrite=True) - def add_mesh(self, filename, axially_symmetric=None): + def add_mesh( + self, filename: str, axially_symmetric: Any | None = None + ) -> None: """ adds a mesh to the project file @@ -40,27 +47,42 @@ def add_mesh(self, filename, axially_symmetric=None): elif isinstance(axially_symmetric, str): attr_dict = {"axially_symmetric": axially_symmetric} if self.mesh is not None: - if self.mesh.text == "" or self.mesh.text is None: - self.populate_tree(self.root, "mesh", text=filename, attr=attr_dict, overwrite=True) + if (self.mesh.text == "") or (self.mesh.text is None): + self.populate_tree( + self.root, + "mesh", + text=filename, + attr=attr_dict, + overwrite=True, + ) else: entry = self.mesh.text attrib = self.mesh.get("axially_symmetric") - mesh0attr_dict ={} + mesh0attr_dict = {} if isinstance(attrib, str): mesh0attr_dict = {"axially_symmetric": attrib} self.mesh.tag = "meshes" - self.meshes = self.populate_tree(self.root, "meshes", text="", attr={}, overwrite=True) + self.meshes = self.populate_tree( + self.root, "meshes", text="", attr={}, overwrite=True + ) self.mesh = None if self.geometry is not None: self.geometry.getparent().remove(self.geometry) self.geometry = self.root.find("./geometry") - self.populate_tree(self.meshes, "mesh", text=entry, attr=mesh0attr_dict) - self.populate_tree(self.meshes, "mesh", text=filename, attr=attr_dict) + self.populate_tree( + self.meshes, "mesh", text=entry, attr=mesh0attr_dict + ) + self.populate_tree( + self.meshes, "mesh", text=filename, attr=attr_dict + ) elif self.meshes is not None: - self.populate_tree(self.meshes, "mesh", text=filename, attr=attr_dict) + self.populate_tree( + self.meshes, "mesh", text=filename, attr=attr_dict + ) self.geometry = self.root.find("./geometry") if self.geometry is not None: self.geometry.getparent().remove(self.geometry) self.geometry = self.root.find("./geometry") else: - raise RuntimeError("This should not happpen") + msg = "This should not happen" + raise RuntimeError(msg) diff --git a/ogs6py/classes/nonlinsolvers.py b/ogs6py/classes/nonlinsolvers.py index 73a62ee..cb7f497 100644 --- a/ogs6py/classes/nonlinsolvers.py +++ b/ogs6py/classes/nonlinsolvers.py @@ -1,49 +1,64 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class NonLinSolvers(build_tree.BuildTree): """ Adds a non-linearsolver section in the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.nlss = self.populate_tree(self.root, 'nonlinear_solvers', overwrite=True) + self.nlss = self.populate_tree( + self.root, "nonlinear_solvers", overwrite=True + ) - def add_non_lin_solver(self, **args): + def add_non_lin_solver(self, **args: Any) -> None: """ add a nonlinear solver. Parameters ---------- name : `str` + name type : `str` - one of `Picard` or `Newton` + one of `Picard`, `Newton` or `PETScSNES` max_iter : `int` or `str` + maximum iteraterion linear_solver : `str` + linear solver configuration to chose damping : `float` or `str` + damping for newton step """ self._convertargs(args) if "name" not in args: - raise KeyError("Missing name of the nonlinear solver.") + msg = "Missing name of the nonlinear solver." + raise KeyError(msg) if "type" not in args: - raise KeyError("Please specify the type of the nonlinear solver.") + msg = "Please specify the type of the nonlinear solver." + raise KeyError(msg) if "max_iter" not in args: - raise KeyError("Please provide the maximum number of iterations (max_iter).") + msg = "Please provide the maximum number of iterations (max_iter)." + raise KeyError(msg) if "linear_solver" not in args: - raise KeyError("No linear_solver specified.") + msg = "No linear_solver specified." + raise KeyError(msg) nls = self.populate_tree(self.nlss, "nonlinear_solver") - self.populate_tree(nls, "name", text=args['name']) - self.populate_tree(nls, "type", text=args['type']) - self.populate_tree(nls, "max_iter", text=args['max_iter']) - self.populate_tree(nls, "linear_solver", text=args['linear_solver']) + self.populate_tree(nls, "name", text=args["name"]) + self.populate_tree(nls, "type", text=args["type"]) + self.populate_tree(nls, "max_iter", text=args["max_iter"]) + self.populate_tree(nls, "linear_solver", text=args["linear_solver"]) if "damping" in args: - self.populate_tree(nls, "damping", text=args['damping']) + self.populate_tree(nls, "damping", text=args["damping"]) diff --git a/ogs6py/classes/parameters.py b/ogs6py/classes/parameters.py index 9936c2b..0df24da 100644 --- a/ogs6py/classes/parameters.py +++ b/ogs6py/classes/parameters.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. @@ -7,18 +6,26 @@ """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Parameters(build_tree.BuildTree): """ Class for managing the parameters section of the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.parameters = self.populate_tree(self.root, 'parameters', overwrite=True) + self.parameters = self.populate_tree( + self.root, "parameters", overwrite=True + ) - def add_parameter(self, **args): + def add_parameter(self, **args: Any) -> None: """ Adds a parameter @@ -39,60 +46,74 @@ def add_parameter(self, **args): """ self._convertargs(args) if "name" not in args: - raise KeyError("No parameter name given.") + msg = "No parameter name given." + raise KeyError(msg) if "type" not in args: - raise KeyError("Parameter type not given.") - parameter = self.populate_tree(self.parameters, 'parameter') - self.populate_tree(parameter, 'name', text=args['name']) - self.populate_tree(parameter, 'type', text=args['type']) - #entries = len(self.tree['parameters']['children']) - #self.tree['parameters']['children'][ + msg = "Parameter type not given." + raise KeyError(msg) + parameter = self.populate_tree(self.parameters, "parameter") + self.populate_tree(parameter, "name", text=args["name"]) + self.populate_tree(parameter, "type", text=args["type"]) + # entries = len(self.tree['parameters']['children']) + # self.tree['parameters']['children'][ # 'param' + str(entries)] = self.populate_tree('parameter', # children={}) - #parameter = self.tree['parameters']['children']['param' + + # parameter = self.tree['parameters']['children']['param' + # str(entries)] - #parameter['children']['name'] = self.populate_tree( + # parameter['children']['name'] = self.populate_tree( # 'name', text=args['name'], children={}) - #parameter['children']['type'] = self.populate_tree( + # parameter['children']['type'] = self.populate_tree( # 'type', text=args['type'], children={}) if args["type"] == "Constant": if "value" in args: - self.populate_tree(parameter, 'value', text=args['value']) + self.populate_tree(parameter, "value", text=args["value"]) elif "values" in args: - self.populate_tree(parameter, 'values', text=args['values']) + self.populate_tree(parameter, "values", text=args["values"]) elif args["type"] == "MeshElement" or args["type"] == "MeshNode": - if 'mesh' in args: - self.populate_tree(parameter, 'mesh', text=args['mesh']) - self.populate_tree(parameter, 'field_name', text=args['field_name']) + if "mesh" in args: + self.populate_tree(parameter, "mesh", text=args["mesh"]) + self.populate_tree(parameter, "field_name", text=args["field_name"]) elif args["type"] == "Function": if "mesh" in args: - self.populate_tree(parameter, 'mesh', text=args['mesh']) - if isinstance(args['expression'], str) is True: - self.populate_tree(parameter, 'expression', text=args['expression']) - elif isinstance(args['expression'], list) is True: - for i, entry in enumerate(args['expression']): - self.populate_tree(parameter, 'expression', text=entry) + self.populate_tree(parameter, "mesh", text=args["mesh"]) + if isinstance(args["expression"], str) is True: + self.populate_tree( + parameter, "expression", text=args["expression"] + ) + elif isinstance(args["expression"], list) is True: + for entry in args["expression"]: + self.populate_tree(parameter, "expression", text=entry) elif args["type"] == "CurveScaled": if "curve" in args: - self.populate_tree(parameter, 'curve', text=args['curve']) + self.populate_tree(parameter, "curve", text=args["curve"]) if "parameter" in args: - self.populate_tree(parameter, 'parameter', text=args['parameter']) + self.populate_tree( + parameter, "parameter", text=args["parameter"] + ) elif args["type"] == "TimeDependentHeterogeneousParameter": if "time" not in args: - raise KeyError("time missing.") + msg = "time missing." + raise KeyError(msg) if "parameter_name" not in args: - raise KeyError("Parameter name missing.") - if not len(args["time"]) == len(args["parameter_name"]): - raise KeyError("parameter_name and time lists have different length.") - time_series = self.populate_tree(parameter, 'time_series') + msg = "Parameter name missing." + raise KeyError(msg) + if len(args["time"]) != len(args["parameter_name"]): + msg = "parameter_name and time lists have different length." + raise KeyError(msg) + time_series = self.populate_tree(parameter, "time_series") for i, _ in enumerate(args["parameter_name"]): - ts_pair = self.populate_tree(time_series, 'pair') - self.populate_tree(ts_pair, 'time', text=args['time'][i]) - self.populate_tree(ts_pair, 'time', text=args['parameter_name'][i]) + ts_pair = self.populate_tree(time_series, "pair") + self.populate_tree(ts_pair, "time", text=str(args["time"][i])) + self.populate_tree( + ts_pair, "parameter_name", text=args["parameter_name"][i] + ) else: - raise KeyError("Parameter type not supported (yet).") - if "use_local_coordinate_system" in args: - if (args["use_local_coordinate_system"] == "true") or ( - args["use_local_coordinate_system"] is True): - self.populate_tree(parameter, - 'use_local_coordinate_system', text='true') + msg = "Parameter type not supported (yet)." + raise KeyError(msg) + if ("use_local_coordinate_system" in args) and ( + (args["use_local_coordinate_system"] == "true") + or (args["use_local_coordinate_system"] is True) + ): + self.populate_tree( + parameter, "use_local_coordinate_system", text="true" + ) diff --git a/ogs6py/classes/processes.py b/ogs6py/classes/processes.py index 33fe976..0603e23 100644 --- a/ogs6py/classes/processes.py +++ b/ogs6py/classes/processes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. @@ -7,27 +6,37 @@ """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class Processes(build_tree.BuildTree): """ Class for managing the processes section in the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.processes = self.populate_tree(self.root, "processes", overwrite=True) - self.process = self.populate_tree(self.processes, "process", overwrite=True) + self.processes = self.populate_tree( + self.root, "processes", overwrite=True + ) + self.process = self.populate_tree( + self.processes, "process", overwrite=True + ) self.procvars = None - self.procvar = {} + self.procvar: dict[str, ET.Element] = {} self.secondvars = None - self.secondvar = {} - self.process_baseentries = {} - self.constitutive_relation = {} + self.process_baseentries: dict[str, ET.Element] = {} self.borehole_heat_exchangers = None - self.borehole_heat_exchanger = [] + self.borehole_heat_exchanger: list[ET.Element] = [] - def add_process_variable(self, process_variable="", process_variable_name=""): + def add_process_variable( + self, process_variable: str = "", process_variable_name: str = "" + ) -> None: """ Adds a process variable. @@ -36,14 +45,20 @@ def add_process_variable(self, process_variable="", process_variable_name=""): process_variable : `str` process_variable_name : `str` """ - self.procvars = self.populate_tree(self.process, "process_variables", overwrite=True) + self.procvars = self.populate_tree( + self.process, "process_variables", overwrite=True + ) if process_variable != "": if process_variable_name == "": - raise KeyError("process_variable_name missing.") - self.procvar[process_variable] = self.populate_tree(self.procvars, - process_variable, text=process_variable_name) + msg = "process_variable_name missing." + raise KeyError(msg) + self.procvar[process_variable] = self.populate_tree( + self.procvars, process_variable, text=process_variable_name + ) - def add_secondary_variable(self, internal_name, output_name): + def add_secondary_variable( + self, internal_name: str, output_name: str + ) -> None: """ Adds a secondary variable. @@ -52,13 +67,17 @@ def add_secondary_variable(self, internal_name, output_name): internal_variable : `str` output_name : `str` """ - self.secondvars = self.populate_tree(self.process, "secondary_variables", overwrite=True) + self.secondvars = self.populate_tree( + self.process, "secondary_variables", overwrite=True + ) attrs = {"internal_name": internal_name, "output_name": output_name} self.populate_tree(self.secondvars, "secondary_variable", attr=attrs) - def set_process(self, **args): + def set_process(self, **args: Any) -> None: """ Set basic process properties. + any pair tag="value" translates to + value in process section Parameters ---------- @@ -66,43 +85,72 @@ def set_process(self, **args): type : `str` integration_order : `str` darcy_gravity/specific_body_force : `list` or `tuple` - holding darcy accelleration as vector - any pair tag="value" translates to - value in process section """ self._convertargs(args) if "name" not in args: - raise KeyError("No process name given.") + msg = "No process name given." + raise KeyError(msg) if "type" not in args: - raise KeyError("type missing.") + msg = "type missing." + raise KeyError(msg) if "integration_order" not in args: - raise KeyError("integration_order missing.") + msg = "integration_order missing." + raise KeyError(msg) for key, value in args.items(): if key == "darcy_gravity": for i, entry in enumerate(args["darcy_gravity"]): if entry != 0.0: - self.process_baseentries["darcy_gravity"] = self.populate_tree( - self.process, "darcy_gravity") - self.populate_tree(self.process_baseentries["darcy_gravity"], - "axis_id",text =str(i)) - self.populate_tree(self.process_baseentries["darcy_gravity"], - "g",text = str(entry)) + self.process_baseentries[ + "darcy_gravity" + ] = self.populate_tree( + self.process, "darcy_gravity", overwrite=True + ) + self.populate_tree( + self.process_baseentries["darcy_gravity"], + "axis_id", + text=str(i), + ) + self.populate_tree( + self.process_baseentries["darcy_gravity"], + "g", + text=str(entry), + ) elif key == "specific_body_force": if isinstance(args["specific_body_force"], list): - self.populate_tree(self.process, "specific_body_force", - text=" ".join(str(x) for x in args['specific_body_force'])) + self.populate_tree( + self.process, + "specific_body_force", + text=" ".join( + str(x) for x in args["specific_body_force"] + ), + overwrite=True, + ) else: - self.populate_tree(self.process, "specific_body_force", - text=args["specific_body_force"]) + self.populate_tree( + self.process, + "specific_body_force", + text=args["specific_body_force"], + overwrite=True, + ) + elif (key == "coupling_scheme") and ( + args["type"] in ["HYDRO_MECHANICS"] + ): + cs = self.populate_tree( + self.process, "coupling_scheme", overwrite=True + ) + self.populate_tree( + cs, "type", text=args["coupling_scheme"], overwrite=True + ) else: if isinstance(value, str): - self.populate_tree(self.process, key, text=value) + self.populate_tree( + self.process, key, text=value, overwrite=True + ) else: - raise RuntimeError(f"{key} is not of type string") - + msg = f"{key} is not of type string" + raise RuntimeError(msg) - - def set_constitutive_relation(self, **args): + def set_constitutive_relation(self, **args: Any) -> None: """ Sets constituitive relation @@ -115,36 +163,48 @@ def set_constitutive_relation(self, **args): """ self._convertargs(args) if "id" in args: - const_rel = self.populate_tree(self.process, "constitutive_relation", attr={"id": args["id"]}) + const_rel = self.populate_tree( + self.process, "constitutive_relation", attr={"id": args["id"]} + ) else: - const_rel = self.populate_tree(self.process, "constitutive_relation", overwrite=True) + const_rel = self.populate_tree( + self.process, "constitutive_relation", overwrite=True + ) for key, value in args.items(): if key not in ["id"]: self.populate_tree(const_rel, key, text=value, overwrite=True) - - def add_bhe_type(self, bhe_type): + def add_bhe_type(self, bhe_type: str) -> None: """ Adds a BHE type """ self.borehole_heat_exchangers = self.populate_tree( - self.process, "borehole_heat_exchangers", overwrite=True) - self.borehole_heat_exchanger.append(self.populate_tree( - self.borehole_heat_exchangers, "borehole_heat_exchanger")) - self.populate_tree(self.borehole_heat_exchanger[-1], "type", text = bhe_type) + self.process, "borehole_heat_exchangers", overwrite=True + ) + self.borehole_heat_exchanger.append( + self.populate_tree( + self.borehole_heat_exchangers, "borehole_heat_exchanger" + ) + ) + self.populate_tree( + self.borehole_heat_exchanger[-1], "type", text=bhe_type + ) - def add_bhe_component(self, index=0, **args): + def add_bhe_component(self, index: int = 0, **args: Any) -> None: """ adds a BHE component """ self._convertargs(args) bhe_type = "" - if 'comp_type' not in args: - raise KeyError("No BHE component name specified.") - bhecomponent = self.populate_tree(self.borehole_heat_exchanger[index], args['comp_type']) + if "comp_type" not in args: + msg = "No BHE component name specified." + raise KeyError(msg) + bhecomponent = self.populate_tree( + self.borehole_heat_exchanger[index], args["comp_type"] + ) if bhecomponent.tag == "borehole": - self.populate_tree(bhecomponent, 'length', text = args['length']) - self.populate_tree(bhecomponent, 'diameter', text = args['diameter']) + self.populate_tree(bhecomponent, "length", text=args["length"]) + self.populate_tree(bhecomponent, "diameter", text=args["diameter"]) elif bhecomponent.tag == "pipes": for element in self.borehole_heat_exchanger[index]: if element.tag == "type": @@ -156,50 +216,122 @@ def add_bhe_component(self, index=0, **args): outlet_text = "outer" inlet = self.populate_tree(bhecomponent, inlet_text) outlet = self.populate_tree(bhecomponent, outlet_text) - self.populate_tree(inlet, "diameter", text=args['inlet_diameter']) - self.populate_tree(inlet, "wall_thickness", text=args['inlet_wall_thickness']) - self.populate_tree(inlet, "wall_thermal_conductivity", text=args['inlet_wall_thermal_conductivity']) - self.populate_tree(outlet, "diameter", text=args['outlet_diameter']) - self.populate_tree(outlet, "wall_thickness", text=args['outlet_wall_thickness']) - self.populate_tree(outlet, "wall_thermal_conductivity", text=args['outlet_wall_thermal_conductivity']) - self.populate_tree(bhecomponent, "distance_between_pipes", text=args['distance_between_pipes']) - self.populate_tree(bhecomponent, "longitudinal_dispersion_length", text=args['longitudinal_dispersion_length']) + self.populate_tree(inlet, "diameter", text=args["inlet_diameter"]) + self.populate_tree( + inlet, "wall_thickness", text=args["inlet_wall_thickness"] + ) + self.populate_tree( + inlet, + "wall_thermal_conductivity", + text=args["inlet_wall_thermal_conductivity"], + ) + self.populate_tree(outlet, "diameter", text=args["outlet_diameter"]) + self.populate_tree( + outlet, "wall_thickness", text=args["outlet_wall_thickness"] + ) + self.populate_tree( + outlet, + "wall_thermal_conductivity", + text=args["outlet_wall_thermal_conductivity"], + ) + self.populate_tree( + bhecomponent, + "distance_between_pipes", + text=args["distance_between_pipes"], + ) + self.populate_tree( + bhecomponent, + "longitudinal_dispersion_length", + text=args["longitudinal_dispersion_length"], + ) elif bhecomponent.tag == "flow_and_temperature_control": - self.populate_tree(bhecomponent, "type", text=args['type']) - if args['type'] == "FixedPowerConstantFlow": - self.populate_tree(bhecomponent, "power", text=args['power']) - self.populate_tree(bhecomponent, "flow_rate", text=args['flow_rate']) - elif args['type'] == "FixedPowerFlowCurve": - self.populate_tree(bhecomponent, "power", text=args['power']) - self.populate_tree(bhecomponent, "flow_rate_curve", text=args['flow_rate_curve']) - elif args['type'] == "PowerCurveConstantFlow": - self.populate_tree(bhecomponent, "power_curve", text=args['power_curve']) - self.populate_tree(bhecomponent, "flow_rate", text=args['flow_rate']) - elif args['type'] == "TemperatureCurveConstantFlow": - self.populate_tree(bhecomponent, "flow_rate", text=args['flow_rate']) - self.populate_tree(bhecomponent, "temperature_curve", text=args['temperature_curve']) - elif args['type'] == "TemperatureCurveFlowCurve": - self.populate_tree(bhecomponent, "flow_rate_curve", text=args['flow_rate_curve']) - self.populate_tree(bhecomponent, "temperature_curve", text=args['temperature_curve']) - elif args['type'] == "PowerCurveFlowCurve": - self.populate_tree(bhecomponent, "power_curve", text=args['power_curve']) - self.populate_tree(bhecomponent, "flow_rate_curve", text=args['flow_rate_curve']) + self.populate_tree(bhecomponent, "type", text=args["type"]) + if args["type"] == "FixedPowerConstantFlow": + self.populate_tree(bhecomponent, "power", text=args["power"]) + self.populate_tree( + bhecomponent, "flow_rate", text=args["flow_rate"] + ) + elif args["type"] == "FixedPowerFlowCurve": + self.populate_tree(bhecomponent, "power", text=args["power"]) + self.populate_tree( + bhecomponent, + "flow_rate_curve", + text=args["flow_rate_curve"], + ) + elif args["type"] == "PowerCurveConstantFlow": + self.populate_tree( + bhecomponent, "power_curve", text=args["power_curve"] + ) + self.populate_tree( + bhecomponent, "flow_rate", text=args["flow_rate"] + ) + elif args["type"] == "TemperatureCurveConstantFlow": + self.populate_tree( + bhecomponent, "flow_rate", text=args["flow_rate"] + ) + self.populate_tree( + bhecomponent, + "temperature_curve", + text=args["temperature_curve"], + ) + elif args["type"] == "TemperatureCurveFlowCurve": + self.populate_tree( + bhecomponent, + "flow_rate_curve", + text=args["flow_rate_curve"], + ) + self.populate_tree( + bhecomponent, + "temperature_curve", + text=args["temperature_curve"], + ) + elif args["type"] == "PowerCurveFlowCurve": + self.populate_tree( + bhecomponent, "power_curve", text=args["power_curve"] + ) + self.populate_tree( + bhecomponent, + "flow_rate_curve", + text=args["flow_rate_curve"], + ) elif bhecomponent.tag == "grout": - self.populate_tree(bhecomponent, "density", text=args['density']) - self.populate_tree(bhecomponent, "porosity", text=args['porosity']) - self.populate_tree(bhecomponent, "specific_heat_capacity", text=args['specific_heat_capacity']) - self.populate_tree(bhecomponent, "thermal_conductivity", text=args['thermal_conductivity']) + self.populate_tree(bhecomponent, "density", text=args["density"]) + self.populate_tree(bhecomponent, "porosity", text=args["porosity"]) + self.populate_tree( + bhecomponent, + "specific_heat_capacity", + text=args["specific_heat_capacity"], + ) + self.populate_tree( + bhecomponent, + "thermal_conductivity", + text=args["thermal_conductivity"], + ) elif bhecomponent.tag == "refrigerant": - self.populate_tree(bhecomponent, "density", text=args['density']) - self.populate_tree(bhecomponent, "viscosity", text=args['viscosity']) - self.populate_tree(bhecomponent, "specific_heat_capacity", text=args['specific_heat_capacity']) - self.populate_tree(bhecomponent, "thermal_conductivity", text=args['thermal_conductivity']) - self.populate_tree(bhecomponent, "reference_temperature", text=args['reference_temperature']) + self.populate_tree(bhecomponent, "density", text=args["density"]) + self.populate_tree( + bhecomponent, "viscosity", text=args["viscosity"] + ) + self.populate_tree( + bhecomponent, + "specific_heat_capacity", + text=args["specific_heat_capacity"], + ) + self.populate_tree( + bhecomponent, + "thermal_conductivity", + text=args["thermal_conductivity"], + ) + self.populate_tree( + bhecomponent, + "reference_temperature", + text=args["reference_temperature"], + ) - def add_surfaceflux(self, **args): + def add_surfaceflux(self, **args: Any) -> None: """ Add SurfaceFlux @@ -221,9 +353,13 @@ def add_surfaceflux(self, **args): self._convertargs(args) if "mesh" not in args: - raise KeyError("No surface mesh for flux analysis assigned") + msg = "No surface mesh for flux analysis assigned" + raise KeyError(msg) if "property_name" not in args: - raise KeyError("No property name, e.g specific_flux, assigned") - surfaceflux = self.populate_tree(self.process, "calculatesurfaceflux", overwrite=True) + msg = "No property name, e.g specific_flux, assigned" + raise KeyError(msg) + surfaceflux = self.populate_tree( + self.process, "calculatesurfaceflux", overwrite=True + ) self.populate_tree(surfaceflux, "mesh", args["mesh"]) self.populate_tree(surfaceflux, "property_name", args["property_name"]) diff --git a/ogs6py/classes/processvars.py b/ogs6py/classes/processvars.py index 08044bb..f738fed 100644 --- a/ogs6py/classes/processvars.py +++ b/ogs6py/classes/processvars.py @@ -1,24 +1,31 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class ProcessVars(build_tree.BuildTree): """ Managing the process variables section in the project file. """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.pvs = self.populate_tree(self.root, 'process_variables', overwrite=True) + self.pvs = self.populate_tree( + self.root, "process_variables", overwrite=True + ) - def set_ic(self, **args): + def set_ic(self, **args: Any) -> None: """ Set initial conditions. @@ -27,26 +34,41 @@ def set_ic(self, **args): process_variable_name : `str` components : `int` or `str` order : `int` or `str` - initial_condition` : `str` + initial_condition : `str` + compensate_non_equilibrium_initial_residuum : `str` """ self._convertargs(args) if "process_variable_name" not in args: - raise KeyError("No process_variable_name given") + msg = "No process_variable_name given" + raise KeyError(msg) if "components" not in args: - raise KeyError("Please provide the number of components \ - of the given process variable.") + msg = """Please provide the number of components \ + of the given process variable.""" + + raise KeyError(msg) if "order" not in args: - raise KeyError("Out of order. Please specify the polynomial order \ - of the process variable's shape functions.") + msg = """Out of order. Please specify the polynomial order \ + of the process variable's shape functions.""" + raise KeyError(msg) if "initial_condition" not in args: - raise KeyError("No initial_condition specified.") - pv = self.populate_tree(self.pvs, 'process_variable') - self.populate_tree(pv, 'name', text=args['process_variable_name']) - self.populate_tree(pv, 'components', text=args['components']) - self.populate_tree(pv, 'order', text=args['order']) - self.populate_tree(pv, 'initial_condition', text=args['initial_condition']) + msg = "No initial_condition specified." + raise KeyError(msg) + pv = self.populate_tree(self.pvs, "process_variable") + self.populate_tree(pv, "name", text=args["process_variable_name"]) + self.populate_tree(pv, "components", text=args["components"]) + self.populate_tree(pv, "order", text=args["order"]) + compens = "compensate_non_equilibrium_initial_residuum" + boolmapping = {True: "true", False: "false"} + if compens in args: + if isinstance(args[compens], bool): + self.populate_tree(pv, compens, text=boolmapping[args[compens]]) + else: + self.populate_tree(pv, compens, text=args[compens]) + self.populate_tree( + pv, "initial_condition", text=args["initial_condition"] + ) - def add_bc(self, **args): + def add_bc(self, **args: Any) -> None: """ Adds a boundary condition. @@ -63,45 +85,71 @@ def add_bc(self, **args): """ self._convertargs(args) if "process_variable_name" not in args: - raise KeyError("No process variable name specified.") + msg = "No process variable name specified." + raise KeyError(msg) if "type" not in args: - raise KeyError("No type given.") - process_variable_name = args['process_variable_name'] - pv = self.root.find(f"./process_variables/process_variable[name=\'{process_variable_name}\']") + msg = "No type given." + raise KeyError(msg) + process_variable_name = args["process_variable_name"] + pv = self.root.find( + f"./process_variables/process_variable[name='{process_variable_name}']" + ) if pv is None: - raise KeyError("You need to set initial condition for that process variable first.") + msg = "You need to set initial condition for that process variable first." + raise KeyError(msg) boundary_conditions = pv.find("./boundary_conditions") if boundary_conditions is None: - boundary_conditions = self.populate_tree(pv, 'boundary_conditions') - boundary_condition = self.populate_tree(boundary_conditions, 'boundary_condition') - self.populate_tree(boundary_condition, 'type', text=args['type']) + boundary_conditions = self.populate_tree(pv, "boundary_conditions") + boundary_condition = self.populate_tree( + boundary_conditions, "boundary_condition" + ) + self.populate_tree(boundary_condition, "type", text=args["type"]) if "geometrical_set" in args: if "geometry" not in args: - raise KeyError("You need to provide a geometry.") - self.populate_tree(boundary_condition, 'geometrical_set', text=args['geometrical_set']) - self.populate_tree(boundary_condition, 'geometry', text=args['geometry']) + msg = "You need to provide a geometry." + raise KeyError(msg) + self.populate_tree( + boundary_condition, + "geometrical_set", + text=args["geometrical_set"], + ) + self.populate_tree( + boundary_condition, "geometry", text=args["geometry"] + ) elif "mesh" in args: - self.populate_tree(boundary_condition, 'mesh', text=args['mesh']) + self.populate_tree(boundary_condition, "mesh", text=args["mesh"]) else: - raise KeyError("You should provide either a geometrical set \ - or a mesh to define BC for.") + msg = """ou should provide either a geometrical set \ + or a mesh to define BC for.""" + raise KeyError(msg) if "parameter" in args: if "component" in args: - self.populate_tree(boundary_condition, 'component', text=args['component']) - self.populate_tree(boundary_condition, 'parameter', text=args['parameter']) + self.populate_tree( + boundary_condition, "component", text=args["component"] + ) + self.populate_tree( + boundary_condition, "parameter", text=args["parameter"] + ) elif "bc_object" in args: if "component" in args: - self.populate_tree(boundary_condition, 'component', text=args['component']) - self.populate_tree(boundary_condition, 'bc_object', text=args['bc_object']) + self.populate_tree( + boundary_condition, "component", text=args["component"] + ) + self.populate_tree( + boundary_condition, "bc_object", text=args["bc_object"] + ) elif "u_0" in args: if "alpha" in args: - self.populate_tree(boundary_condition, 'alpha', text=args['alpha']) - self.populate_tree(boundary_condition, 'u_0', text=args['u_0']) + self.populate_tree( + boundary_condition, "alpha", text=args["alpha"] + ) + self.populate_tree(boundary_condition, "u_0", text=args["u_0"]) else: - raise KeyError("Please provide the parameter for Dirichlet \ - or Neumann BCs/bc_object for Python BCs") + msg = """Please provide the parameter for Dirichlet \ + or Neumann BCs/bc_object for Python BCs""" + raise KeyError(msg) - def add_st(self, **args): + def add_st(self, **args: Any) -> None: """ add a source term @@ -118,40 +166,58 @@ def add_st(self, **args): """ self._convertargs(args) if "process_variable_name" not in args: - raise KeyError("No process variable name specified.") + msg = "No process variable name specified." + raise KeyError(msg) if "type" not in args: - raise KeyError("No type given.") - process_variable_name = args['process_variable_name'] - pv = self.root.find(f"./process_variables/process_variable[name=\'{process_variable_name}\']") + msg = "No type given." + raise KeyError(msg) + process_variable_name = args["process_variable_name"] + pv = self.root.find( + f"./process_variables/process_variable[name='{process_variable_name}']" + ) if pv is None: - raise KeyError("You need to set initial condition for that process variable first.") + msg = "You need to set initial condition for that process variable first." + raise KeyError(msg) source_terms = pv.find("./source_terms") if source_terms is None: - source_terms = self.populate_tree(pv, 'source_terms') - source_term = self.populate_tree(source_terms, 'source_term') - self.populate_tree(source_term, 'type', text=args['type']) + source_terms = self.populate_tree(pv, "source_terms") + source_term = self.populate_tree(source_terms, "source_term") + self.populate_tree(source_term, "type", text=args["type"]) if "geometrical_set" in args: if "geometry" not in args: - raise KeyError("You need to provide a geometry.") - self.populate_tree(source_term, 'geometrical_set', text=args['geometrical_set']) - self.populate_tree(source_term, 'geometry', text=args['geometry']) + msg = "You need to provide a geometry." + raise KeyError(msg) + self.populate_tree( + source_term, "geometrical_set", text=args["geometrical_set"] + ) + self.populate_tree(source_term, "geometry", text=args["geometry"]) elif "mesh" in args: - self.populate_tree(source_term, 'mesh', text=args['mesh']) + self.populate_tree(source_term, "mesh", text=args["mesh"]) else: - raise KeyError("You should provide either a geometrical set \ - or a mesh to define STs for.") + msg = "You should provide either a geometrical set \ + or a mesh to define STs for." + raise KeyError(msg) if "parameter" in args: if "component" in args: - self.populate_tree(source_term, 'component', text=args['component']) - self.populate_tree(source_term, 'parameter', text=args['parameter']) + self.populate_tree( + source_term, "component", text=args["component"] + ) + self.populate_tree(source_term, "parameter", text=args["parameter"]) elif "source_term_object" in args: if "component" in args: - self.populate_tree(source_term, 'component', text=args['component']) - self.populate_tree(source_term, 'source_term_object', text=args['source_term_object']) + self.populate_tree( + source_term, "component", text=args["component"] + ) + self.populate_tree( + source_term, + "source_term_object", + text=args["source_term_object"], + ) elif "u_0" in args: if "alpha" in args: - self.populate_tree(source_term, 'alpha', text=args['alpha']) - self.populate_tree(source_term, 'u_0', text=args['u_0']) + self.populate_tree(source_term, "alpha", text=args["alpha"]) + self.populate_tree(source_term, "u_0", text=args["u_0"]) else: - raise KeyError("Please provide the parameter for Dirichlet \ - or Neumann ST/source_term_object for Python STs") + msg = """Please provide the parameter for Dirichlet \ + or Neumann ST/source_term_object for Python STs""" + raise KeyError(msg) diff --git a/ogs6py/classes/properties.py b/ogs6py/classes/properties.py index 548bdf5..737cac9 100644 --- a/ogs6py/classes/properties.py +++ b/ogs6py/classes/properties.py @@ -1,32 +1,40 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2023, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ogstools.ogs6py.ogs import OGS + +from collections.abc import Generator from dataclasses import dataclass, field -from typing import List +from typing import Any + from lxml import etree as ET + @dataclass class Value: - medium : str - value : float + medium: str + value: float | None @dataclass class Property: - title : str - symbol : str - unit : str - value : List[Value] = field(default_factory=list) + title: str + symbol: str + unit: str + value: list[Value] = field(default_factory=list) - def _dict(self): - a = { + def _dict(self) -> dict[str, Any]: + a: dict[str, Any] = { "title": self.title, "symbol": self.symbol, "unit": self.unit, @@ -36,50 +44,142 @@ def _dict(self): return a - @dataclass class PropertySet: - property : List[Property] = field(default_factory=list) + property: list[Property] = field(default_factory=list) - def __len__(self): + def __len__(self) -> int: """Number of time steps""" return len(self.property) - def __iter__(self): + def __iter__(self) -> Generator: for v in self.property: yield v._dict() -property_dict = {"Solid": {"density": {"title": "Solid density", "symbol": "$\\rho_\\text{s}$", "unit": "kg m$^{-3}$"}, - "specific_heat_capacity": {"title": "Specific heat capacity", "symbol": "$c_\\text{s}$", "unit": "J kg$^{-1}$ K$^{-1}$"}, - "thermal_expansivity": {"title": "Thermal expansivity", "symbol": "$a_s$", "unit": "K$^{-1}$"}, - "youngs_modulus": {"title": "Young's modulus", "symbol": "$E$", "unit": "Pa"}, - "poissons_ratio": {"title": "Poisson's ratio", "symbol": "$\\nu$", "unit": "1"}, - - }, - "Medium": {"porosity": {"title": "Porosity", "symbol": "$\phi$", "unit": "1"}, - "biot_coefficient": {"title": "Biot-Willis coefficient", "symbol": "$\\alpha_\mathrm{B}$", "unit": "1"}, - "permeability": {"title": "Intrinsic permeability", "symbol": "$k$", "unit": "m$^2$"}, - "thermal_conductivity": {"title": "Thermal conductivity", "symbol": "$\lambda$", "unit": "W m$^{-1}$ K$^{-1}$"}, - "vgsat_residual_liquid_saturation": {"title": "Saturation: Van Genuchten, \\\ residual liquid saturation", "symbol": "$S^r_\\text{L}$", "unit": "1"}, - "vgsat_residual_gas_saturation": {"title": "Saturation: Van Genuchten, \\\ residual gas saturation ", "symbol": "$S^r_\\text{g}$", "unit": "1"}, - "vgsat_exponent": {"title": "Saturation: Van Genuchten, \\\ exponent", "symbol": "$m$", "unit": "1"}, - "vgsat_p_b": {"title": "Saturation: Van Genuchten, \\\ entry pressure", "symbol": "$p_b$", "unit": "Pa"}, - "vgrelperm_residual_liquid_saturation": {"title": "Relative permeability: Van Genuchten, \\\ residual liquid saturation ", "symbol": "$S^r_\\text{L}$", "unit": "1"}, - "vgrelperm_residual_gas_saturation": {"title": "Relative permeability: Van Genuchten, \\\ residual gas saturation ", "symbol": "$S^r_\\text{g}$", "unit": "1"}, - "vgrelperm_exponent": {"title": "Relative permeability: Van Genuchten, \\\ exponent", "symbol": "$m$", "unit": "1"}, - "vgrelperm_minimum_relative_permeability_liquid": {"title": "Relative permeability: Van Genuchten, \\\ minimmum relative permeability", "symbol": "$k^\\text{min}_r$", "unit": "1"}, - }, - "AqueousLiquid": {"viscosity": {"title": "Liquid phase viscosity", "symbol": "$\mu_\mathrm{LR}$", "unit": "Pa s"}, - "density": {"title": "Liquid phase density", "symbol": "$\\rho_\mathrm{LR}$", "unit": "kg m$^{-3}$"}, - "specific_heat_capacity": {"title": "Liquid specific heat capacity", "symbol": "$c_\\text{LR}$", "unit": "J kg$^{-1}$ K$^{-1}$"},}} -location_pointer = {"Solid": "phases/phase[type='Solid']/", - "Medium": "", - "AqueousLiquid": "phases/phase[type='AqueousLiquid']/"} - -def expand_tensors(obj, numofmedia, multidim_prop, root, location): + +property_dict = { + "Solid": { + "density": { + "title": "Solid density", + "symbol": "$\\rho_\\text{s}$", + "unit": "kg m$^{-3}$", + }, + "specific_heat_capacity": { + "title": "Specific heat capacity", + "symbol": "$c_\\text{s}$", + "unit": "J kg$^{-1}$ K$^{-1}$", + }, + "thermal_expansivity": { + "title": "Thermal expansivity", + "symbol": "$a_s$", + "unit": "K$^{-1}$", + }, + "youngs_modulus": { + "title": "Young's modulus", + "symbol": "$E$", + "unit": "Pa", + }, + "poissons_ratio": { + "title": "Poisson's ratio", + "symbol": "$\\nu$", + "unit": "1", + }, + }, + "Medium": { + "porosity": {"title": "Porosity", "symbol": r"$\phi$", "unit": "1"}, + "biot_coefficient": { + "title": "Biot-Willis coefficient", + "symbol": "$\\alpha_\\mathrm{B}$", + "unit": "1", + }, + "permeability": { + "title": "Intrinsic permeability", + "symbol": "$k$", + "unit": "m$^2$", + }, + "thermal_conductivity": { + "title": "Thermal conductivity", + "symbol": r"$\lambda$", + "unit": "W m$^{-1}$ K$^{-1}$", + }, + "vgsat_residual_liquid_saturation": { + "title": "Saturation: Van Genuchten, \\\\ residual liquid saturation", + "symbol": "$S^r_\\text{L}$", + "unit": "1", + }, + "vgsat_residual_gas_saturation": { + "title": "Saturation: Van Genuchten, \\\\ residual gas saturation ", + "symbol": "$S^r_\\text{g}$", + "unit": "1", + }, + "vgsat_exponent": { + "title": "Saturation: Van Genuchten, \\\\ exponent", + "symbol": "$m$", + "unit": "1", + }, + "vgsat_p_b": { + "title": "Saturation: Van Genuchten, \\\\ entry pressure", + "symbol": "$p_b$", + "unit": "Pa", + }, + "vgrelperm_residual_liquid_saturation": { + "title": "Relative permeability: Van Genuchten, \\\\ residual liquid saturation ", + "symbol": "$S^r_\\text{L}$", + "unit": "1", + }, + "vgrelperm_residual_gas_saturation": { + "title": "Relative permeability: Van Genuchten, \\\\ residual gas saturation ", + "symbol": "$S^r_\\text{g}$", + "unit": "1", + }, + "vgrelperm_exponent": { + "title": "Relative permeability: Van Genuchten, \\\\ exponent", + "symbol": "$m$", + "unit": "1", + }, + "vgrelperm_minimum_relative_permeability_liquid": { + "title": "Relative permeability: Van Genuchten, \\\\ minimmum relative permeability", + "symbol": "$k^\\text{min}_r$", + "unit": "1", + }, + }, + "AqueousLiquid": { + "viscosity": { + "title": "Liquid phase viscosity", + "symbol": r"$\mu_\mathrm{LR}$", + "unit": "Pa s", + }, + "density": { + "title": "Liquid phase density", + "symbol": "$\\rho_\\mathrm{LR}$", + "unit": "kg m$^{-3}$", + }, + "specific_heat_capacity": { + "title": "Liquid specific heat capacity", + "symbol": "$c_\\text{LR}$", + "unit": "J kg$^{-1}$ K$^{-1}$", + }, + }, +} +location_pointer = { + "Solid": "phases/phase[type='Solid']/", + "Medium": "", + "AqueousLiquid": "phases/phase[type='AqueousLiquid']/", +} + + +def expand_tensors( + obj: OGS, + numofmedia: int, + multidim_prop: dict[Any, Any], + root: ET.Element, + location: str, +) -> None: for medium_id in range(numofmedia): medium = obj._get_medium_pointer(root, medium_id) - const_props = medium.findall(f"./{location_pointer[location]}properties/property[type='Constant']/value") + const_props = medium.findall( + f"./{location_pointer[location]}properties/property[type='Constant']/value" + ) tobedeleted = [] for prop in const_props: proplist = prop.text.split(" ") @@ -97,21 +197,28 @@ def expand_tensors(obj, numofmedia, multidim_prop, root, location): q = ET.SubElement(properties_level, "property") for j, tag in enumerate(taglist): r = ET.SubElement(q, tag) - if not textlist[j] is None: + if textlist[j] is not None: r.text = str(textlist[j]) for element in tobedeleted: element.getparent().remove(element) -def expand_van_genuchten(obj, numofmedia, root, location): + +def expand_van_genuchten( + obj: OGS, numofmedia: int, root: ET.Element, location: str +) -> None: for medium_id in range(numofmedia): medium = obj._get_medium_pointer(root, medium_id) - sat_vg_props = medium.findall(f"./{location_pointer[location]}properties/property[type='SaturationVanGenuchten']") - relperm_vg_props = medium.findall(f"./{location_pointer[location]}properties/property[type='RelativePermeabilityVanGenuchten']") + sat_vg_props = medium.findall( + f"./{location_pointer[location]}properties/property[type='SaturationVanGenuchten']" + ) + relperm_vg_props = medium.findall( + f"./{location_pointer[location]}properties/property[type='RelativePermeabilityVanGenuchten']" + ) vg_properties = [sat_vg_props, relperm_vg_props] tobedeleted = [] for vg_property in vg_properties: for prop in vg_property: - proplist = [property for property in prop.getchildren()] + proplist = prop.getchildren() const_taglist = ["name", "type", "value"] prefix = "" for subprop in proplist: @@ -120,12 +227,16 @@ def expand_van_genuchten(obj, numofmedia, root, location): elif "RelativePermeabilityVanGenuchten" in subprop.text: prefix = "vgrelperm_" for subprop in proplist: - if not subprop.tag in ["name", "type"]: - const_textlist = [prefix+subprop.tag, "Constant", subprop.text] + if subprop.tag not in ["name", "type"]: + const_textlist = [ + prefix + subprop.tag, + "Constant", + subprop.text, + ] q = ET.SubElement(prop.getparent(), "property") for i, tag in enumerate(const_taglist): r = ET.SubElement(q, tag) - if not const_textlist[i] is None: + if const_textlist[i] is not None: r.text = str(const_textlist[i]) tobedeleted.append(prop) for element in tobedeleted: diff --git a/ogs6py/classes/python_script.py b/ogs6py/classes/python_script.py index 5a38080..a8dd493 100644 --- a/ogs6py/classes/python_script.py +++ b/ogs6py/classes/python_script.py @@ -1,25 +1,27 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class PythonScript(build_tree.BuildTree): """ Class managing python script in the project file """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() self.populate_tree(self.root, "python_script", overwrite=True) - - def set_pyscript(self, filename): + def set_pyscript(self, filename: str) -> None: """ Set a filename for a python script. @@ -27,4 +29,6 @@ def set_pyscript(self, filename): ---------- filename : `str` """ - self.populate_tree(self.root, "python_script", text=filename, overwrite=True) + self.populate_tree( + self.root, "python_script", text=filename, overwrite=True + ) diff --git a/ogs6py/classes/timeloop.py b/ogs6py/classes/timeloop.py index 716dbdf..e4a7d8d 100644 --- a/ogs6py/classes/timeloop.py +++ b/ogs6py/classes/timeloop.py @@ -1,27 +1,40 @@ -# -*- coding: utf-8 -*- """ -Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) +Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) Distributed under a Modified BSD License. See accompanying file LICENSE or http://www.opengeosys.org/project/license """ # pylint: disable=C0103, R0902, R0914, R0913 -from ogs6py.classes import build_tree +from typing import Any + +from lxml import etree as ET + +from ogstools.ogs6py import build_tree + class TimeLoop(build_tree.BuildTree): """ Class managing the time loop in the project file """ - def __init__(self, tree): + + def __init__(self, tree: ET.ElementTree) -> None: self.tree = tree self.root = self._get_root() - self.time_loop = self.populate_tree(self.root, 'time_loop', overwrite=True) - self.gpc = self.populate_tree(self.time_loop, "global_process_coupling", overwrite=True) - self.processes = self.populate_tree(self.time_loop, 'processes', overwrite=True) - self.output = self.populate_tree(self.time_loop, 'output', overwrite=True) + self.time_loop = self.populate_tree( + self.root, "time_loop", overwrite=True + ) + self.gpc = self.populate_tree( + self.time_loop, "global_process_coupling", overwrite=True + ) + self.processes = self.populate_tree( + self.time_loop, "processes", overwrite=True + ) + self.output = self.populate_tree( + self.time_loop, "output", overwrite=True + ) - def add_process(self, **args): + def add_process(self, **args: Any) -> None: """ Add a process section to timeloop @@ -39,44 +52,61 @@ def add_process(self, **args): """ self._convertargs(args) if "process" not in args: - raise KeyError("No process referenced") - process_name = args['process'] - process = self.populate_tree(self.processes, 'process', attr={'ref': process_name}) + msg = "No process referenced" + raise KeyError(msg) + process_name = args["process"] + process = self.populate_tree( + self.processes, "process", attr={"ref": process_name} + ) if "nonlinear_solver_name" not in args: - raise KeyError("Please specify a name (nonlinear_solver_name) \ - for the nonlinear solver.") - self.populate_tree(process, 'nonlinear_solver', text=args['nonlinear_solver_name']) + msg = """Please specify a name (nonlinear_solver_name) \ + for the nonlinear solver.""" + raise KeyError(msg) + self.populate_tree( + process, "nonlinear_solver", text=args["nonlinear_solver_name"] + ) if "convergence_type" not in args: - raise KeyError("No convergence criterion given. \ - Specify convergence_type.") - conv_crit = self.populate_tree(process, 'convergence_criterion') - self.populate_tree(conv_crit, 'type', text=args['convergence_type']) + msg = """No convergence criterion given. \ + Specify convergence_type.""" + raise KeyError(msg) + conv_crit = self.populate_tree(process, "convergence_criterion") + self.populate_tree(conv_crit, "type", text=args["convergence_type"]) if "norm_type" not in args: - raise KeyError("No norm_type given.") - self.populate_tree(conv_crit, 'norm_type', text=args['norm_type']) - if (args["convergence_type"] == "DeltaX") or (args["convergence_type"] == "Residual"): - if (("abstols" in args) or ("reltols" in args)): - raise KeyError("Plural tolerances only available for PerComponent conv. types") + msg = "No norm_type given." + raise KeyError(msg) + self.populate_tree(conv_crit, "norm_type", text=args["norm_type"]) + if (args["convergence_type"] == "DeltaX") or ( + args["convergence_type"] == "Residual" + ): + if ("abstols" in args) or ("reltols" in args): + msg = "Plural tolerances only available for PerComponent conv. types" + raise KeyError(msg) if "abstol" in args: - self.populate_tree(conv_crit, 'abstol', text=args['abstol']) + self.populate_tree(conv_crit, "abstol", text=args["abstol"]) if "reltol" in args: - self.populate_tree(conv_crit, 'reltol', text=args['reltol']) - elif (args["convergence_type"] == "PerComponentDeltaX") or (args["convergence_type"] == "PerComponentResidual"): - if (("abstol" in args) or ("reltol" in args)): - raise KeyError("Singular tolerances only available for scalar conv. types") + self.populate_tree(conv_crit, "reltol", text=args["reltol"]) + elif (args["convergence_type"] == "PerComponentDeltaX") or ( + args["convergence_type"] == "PerComponentResidual" + ): + if ("abstol" in args) or ("reltol" in args): + msg = ( + "Singular tolerances only available for scalar conv. types" + ) + raise KeyError(msg) if "abstols" in args: - self.populate_tree(conv_crit, 'abstols', text=args['abstols']) + self.populate_tree(conv_crit, "abstols", text=args["abstols"]) if "reltols" in args: - self.populate_tree(conv_crit, 'reltols', text=args['reltols']) + self.populate_tree(conv_crit, "reltols", text=args["reltols"]) else: - raise KeyError("No convergence_type given.") + msg = "No convergence_type given." + raise KeyError(msg) if "time_discretization" not in args: - raise KeyError("No time_discretization specified.") - td = self.populate_tree(process, 'time_discretization') - self.populate_tree(td, 'type', text=args['time_discretization']) + msg = "No time_discretization specified." + raise KeyError(msg) + td = self.populate_tree(process, "time_discretization") + self.populate_tree(td, "type", text=args["time_discretization"]) - - def set_stepping(self, **args): + def set_stepping(self, **args: Any) -> None: """ Sets the time stepping @@ -84,8 +114,7 @@ def set_stepping(self, **args): ---------- type : `str` process : `str` - process_count : `int` optional - for staggered coupling + process_count : `int` for staggered coupling t_initial : `int` or `str` initial_dt : `float` or `str` t_end : `int` or `str` @@ -104,7 +133,8 @@ def set_stepping(self, **args): """ self._convertargs(args) if "process" not in args: - raise KeyError("Process reference missing") + msg = "Process reference missing" + raise KeyError(msg) procs = self.processes.findall("./process") process = None procs_sub = [] @@ -113,64 +143,105 @@ def set_stepping(self, **args): procs_sub.append(proc) if "process_count" in args: try: - process = procs_sub[int(args['process_count'])] + process = procs_sub[int(args["process_count"])] except KeyError: - KeyError("Process count out of bounds.") + msg = "Process count out of bounds." + KeyError(msg) else: try: process = procs_sub[-1] except KeyError: - KeyError("Process reference not found.") + msg = "Process reference not found." + KeyError(msg) if "type" not in args: - raise KeyError("No type given.") - time_stepping = self.populate_tree(process, 'time_stepping') - self.populate_tree(time_stepping, 'type', text=args['type']) + msg = "No type given." + raise KeyError(msg) + time_stepping = self.populate_tree(process, "time_stepping") + self.populate_tree(time_stepping, "type", text=args["type"]) if args["type"] == "FixedTimeStepping": - self.populate_tree(time_stepping, 't_initial', text=args['t_initial']) - self.populate_tree(time_stepping, 't_end', text=args['t_end']) + self.populate_tree( + time_stepping, "t_initial", text=args["t_initial"] + ) + self.populate_tree(time_stepping, "t_end", text=args["t_end"]) if "repeat" in args and "delta_t" in args: - ts = self.populate_tree(time_stepping, 'timesteps') - if isinstance(args["repeat"], str) and isinstance(args["delta_t"], str): - pair = self.populate_tree(ts, 'pair') - self.populate_tree(pair, 'repeat', text=args['repeat']) - self.populate_tree(pair, 'delta_t', text=args['delta_t']) + ts = self.populate_tree(time_stepping, "timesteps") + if isinstance(args["repeat"], str) and isinstance( + args["delta_t"], str + ): + pair = self.populate_tree(ts, "pair") + self.populate_tree(pair, "repeat", text=args["repeat"]) + self.populate_tree(pair, "delta_t", text=args["delta_t"]) else: for i, entry in enumerate(args["repeat"]): - pair = self.populate_tree(ts, 'pair') - self.populate_tree(pair, 'repeat', text=entry) - self.populate_tree(pair, 'delta_t', text=args['delta_t'][i]) + pair = self.populate_tree(ts, "pair") + self.populate_tree(pair, "repeat", text=entry) + self.populate_tree( + pair, "delta_t", text=args["delta_t"][i] + ) else: - raise KeyError("No proper time stepping defined. \ - Please specify repeat and delta_t.") + msg = """No proper time stepping defined. \ + Please specify repeat and delta_t.""" + raise KeyError(msg) elif args["type"] == "SingleStep": pass elif args["type"] == "IterationNumberBasedTimeStepping": - self.populate_tree(time_stepping, 't_initial', text=args['t_initial']) - self.populate_tree(time_stepping, 't_end', text=args['t_end']) - self.populate_tree(time_stepping, 'initial_dt', text=args['initial_dt']) - self.populate_tree(time_stepping, 'minimum_dt', text=args['minimum_dt']) - self.populate_tree(time_stepping, 'maximum_dt', text=args['maximum_dt']) - if isinstance(args["number_iterations"], str) and isinstance(args["multiplier"], str): - self.populate_tree(time_stepping, 'number_iterations', text=args['number_iterations']) - self.populate_tree(time_stepping, 'multiplier', text=args['multiplier']) + self.populate_tree( + time_stepping, "t_initial", text=args["t_initial"] + ) + self.populate_tree(time_stepping, "t_end", text=args["t_end"]) + self.populate_tree( + time_stepping, "initial_dt", text=args["initial_dt"] + ) + self.populate_tree( + time_stepping, "minimum_dt", text=args["minimum_dt"] + ) + self.populate_tree( + time_stepping, "maximum_dt", text=args["maximum_dt"] + ) + if isinstance(args["number_iterations"], str) and isinstance( + args["multiplier"], str + ): + self.populate_tree( + time_stepping, + "number_iterations", + text=args["number_iterations"], + ) + self.populate_tree( + time_stepping, "multiplier", text=args["multiplier"] + ) else: - self.populate_tree(time_stepping, 'number_iterations', text=" ".join(str(x) for x in args['number_iterations'])) - self.populate_tree(time_stepping, 'multiplier', text=" ".join(str(x) for x in args['multiplier'])) + self.populate_tree( + time_stepping, + "number_iterations", + text=" ".join(str(x) for x in args["number_iterations"]), + ) + self.populate_tree( + time_stepping, + "multiplier", + text=" ".join(str(x) for x in args["multiplier"]), + ) elif args["type"] == "EvolutionaryPIDcontroller": - self.populate_tree(time_stepping, 't_initial', text=args['t_initial']) - self.populate_tree(time_stepping, 't_end', text=args['t_end']) - self.populate_tree(time_stepping, 'dt_guess', text=args['dt_guess']) - self.populate_tree(time_stepping, 'dt_min', text=args['dt_min']) - self.populate_tree(time_stepping, 'dt_max', text=args['dt_max']) - self.populate_tree(time_stepping, 'rel_dt_max', text=args['rel_dt_max']) - self.populate_tree(time_stepping, 'rel_dt_min', text=args['rel_dt_min']) - self.populate_tree(time_stepping, 'tol', text=args['tol']) + self.populate_tree( + time_stepping, "t_initial", text=args["t_initial"] + ) + self.populate_tree(time_stepping, "t_end", text=args["t_end"]) + self.populate_tree(time_stepping, "dt_guess", text=args["dt_guess"]) + self.populate_tree(time_stepping, "dt_min", text=args["dt_min"]) + self.populate_tree(time_stepping, "dt_max", text=args["dt_max"]) + self.populate_tree( + time_stepping, "rel_dt_max", text=args["rel_dt_max"] + ) + self.populate_tree( + time_stepping, "rel_dt_min", text=args["rel_dt_min"] + ) + self.populate_tree(time_stepping, "tol", text=args["tol"]) else: - raise KeyError("Specified time stepping scheme not valid.") + msg = "Specified time stepping scheme not valid." + raise KeyError(msg) - def add_output(self, **args): + def add_output(self, **args: Any) -> None: """ Add output section. @@ -189,83 +260,111 @@ def add_output(self, **args): fixed_output_times : `list` or `str` """ if "type" not in args: - raise KeyError("If you want to specify an output method, \ + msg = """If you want to specify an output method, \ you need to provide type, \ - prefix and a list of variables.") - self.populate_tree(self.output, 'type', text=args['type']) + prefix and a list of variables.""" + raise KeyError(msg) + self.populate_tree(self.output, "type", text=args["type"]) if "prefix" in args: - self.populate_tree(self.output, 'prefix', text=args['prefix']) + self.populate_tree(self.output, "prefix", text=args["prefix"]) if "suffix" in args: - self.populate_tree(self.output, 'suffix', text=args['suffix']) + self.populate_tree(self.output, "suffix", text=args["suffix"]) if "data_mode" in args: - self.populate_tree(self.output, 'data_mode', text=args['data_mode']) + self.populate_tree(self.output, "data_mode", text=args["data_mode"]) if "compress_output" in args: if isinstance(args["compress_output"], bool): if args["compress_output"] is True: - self.populate_tree(self.output, 'compress_output', text="true") + self.populate_tree( + self.output, "compress_output", text="true" + ) else: - self.populate_tree(self.output, 'compress_output', text="false") + self.populate_tree( + self.output, "compress_output", text="false" + ) else: - self.populate_tree(self.output, 'compress_output', text=args["compress_output"]) + self.populate_tree( + self.output, "compress_output", text=args["compress_output"] + ) if "output_iteration_results" in args: if isinstance(args["output_iteration_results"], bool): if args["output_iteration_results"] is True: - self.populate_tree(self.output, 'output_iteration_results', text="true") + self.populate_tree( + self.output, "output_iteration_results", text="true" + ) else: - self.populate_tree(self.output, 'output_iteration_results', text="false") + self.populate_tree( + self.output, "output_iteration_results", text="false" + ) else: - self.populate_tree(self.output, 'output_iteration_results', text=args["output_iteration_results"]) + self.populate_tree( + self.output, + "output_iteration_results", + text=args["output_iteration_results"], + ) if "meshes" in args: - meshes = self.populate_tree(self.output, 'meshes') + meshes = self.populate_tree(self.output, "meshes") if isinstance(args["meshes"], str): - self.populate_tree(meshes, 'mesh', text=args["meshes"]) + self.populate_tree(meshes, "mesh", text=args["meshes"]) else: for mesh in args["meshes"]: - self.populate_tree(meshes, 'mesh', text=mesh) + self.populate_tree(meshes, "mesh", text=mesh) # material_id attribute missing if "repeat" in args: - timesteps = self.populate_tree(self.output, 'timesteps') + timesteps = self.populate_tree(self.output, "timesteps") if "each_steps" not in args: - raise KeyError("each_steps is a required tag if repeat is given.") - if isinstance(args["repeat"], list) and isinstance(args["each_steps"], list): + msg = "each_steps is a required tag if repeat is given." + raise KeyError(msg) + if isinstance(args["repeat"], list) and isinstance( + args["each_steps"], list + ): for i, entry in enumerate(args["repeat"]): - pair = self.populate_tree(timesteps, 'pair') - self.populate_tree(pair, 'repeat', text=entry) - self.populate_tree(pair, 'each_steps', text=args['each_steps'][i]) + pair = self.populate_tree(timesteps, "pair") + self.populate_tree(pair, "repeat", text=entry) + self.populate_tree( + pair, "each_steps", text=args["each_steps"][i] + ) else: - pair = self.populate_tree(timesteps, 'pair') - self.populate_tree(pair, 'repeat', text=args["repeat"]) - self.populate_tree(pair, 'each_steps', text=args['each_steps']) - variables = self.populate_tree(self.output, 'variables') + pair = self.populate_tree(timesteps, "pair") + self.populate_tree(pair, "repeat", text=args["repeat"]) + self.populate_tree(pair, "each_steps", text=args["each_steps"]) + variables = self.populate_tree(self.output, "variables") if "variables" in args: if isinstance(args["variables"], list): for var in args["variables"]: - self.populate_tree(variables, 'variable', text=var) + self.populate_tree(variables, "variable", text=var) else: - raise KeyError("parameter variables needs to be a list") + msg = "parameter variables needs to be a list" + raise KeyError(msg) if "fixed_output_times" in args: if isinstance(args["fixed_output_times"], list): - self.populate_tree(self.output, 'fixed_output_times', text=" ".join(str(x) for x in args["fixed_output_times"])) + self.populate_tree( + self.output, + "fixed_output_times", + text=" ".join(str(x) for x in args["fixed_output_times"]), + ) else: - self.populate_tree(self.output, 'fixed_output_times', text=args["fixed_output_times"]) - + self.populate_tree( + self.output, + "fixed_output_times", + text=args["fixed_output_times"], + ) - def add_time_stepping_pair(self, **args): + def add_time_stepping_pair(self, **args: Any) -> None: """ add a time stepping pair Parameters ---------- process : `str` - process_count : `int` optional - for staggered coupling + process_count : `int` optional, for staggered coupling repeat : `int` or `str` or `list` delta_t : `int` or `str` or `list` """ self._convertargs(args) if "process" not in args: - raise KeyError("No process referenced") + msg = "No process referenced" + raise KeyError(msg) procs = self.processes.findall("./process") process = None procs_sub = [] @@ -274,32 +373,41 @@ def add_time_stepping_pair(self, **args): procs_sub.append(proc) if "process_count" in args: try: - process = procs_sub[int(args['process_count'])] + process = procs_sub[int(args["process_count"])] except KeyError: - KeyError("Process count out of bounds.") + msg = "Process count out of bounds." + KeyError(msg) else: try: process = procs_sub[-1] except KeyError: - KeyError("Process reference not found.") + msg = "Process reference not found." + KeyError(msg) + if process is None: + msg = "Could not find any associated process" + raise AttributeError(msg) ts = process.find("./time_stepping/timesteps") if ts is None: - raise RuntimeError("Cannot find time stepping section in the input file.") + msg = "Cannot find time stepping section in the input file." + raise RuntimeError(msg) if "repeat" in args and "delta_t" in args: - if isinstance(args["repeat"], str) and isinstance(args["delta_t"], str): - pair = self.populate_tree(ts, 'pair') - self.populate_tree(pair, 'repeat', text=args['repeat']) - self.populate_tree(pair, 'delta_t', text=args['delta_t']) + if isinstance(args["repeat"], str) and isinstance( + args["delta_t"], str + ): + pair = self.populate_tree(ts, "pair") + self.populate_tree(pair, "repeat", text=args["repeat"]) + self.populate_tree(pair, "delta_t", text=args["delta_t"]) else: for i, entry in enumerate(args["repeat"]): - pair = self.populate_tree(ts, 'pair') - self.populate_tree(pair, 'repeat', text=entry) - self.populate_tree(pair, 'delta_t', text=args['delta_t'][i]) + pair = self.populate_tree(ts, "pair") + self.populate_tree(pair, "repeat", text=entry) + self.populate_tree(pair, "delta_t", text=args["delta_t"][i]) else: - raise KeyError("You muss provide repeat and delta_t attributes to \ - define additional time stepping pairs.") + msg = """You muss provide repeat and delta_t attributes to \ + define additional time stepping pairs.""" + raise KeyError(msg) - def add_output_pair(self, **args): + def add_output_pair(self, **args: Any) -> None: """ add an output pair @@ -309,29 +417,33 @@ def add_output_pair(self, **args): each_steps : `int` or `str` or `list` """ self._convertargs(args) - timesteps = self.populate_tree(self.output, 'timesteps', overwrite=True) + timesteps = self.populate_tree(self.output, "timesteps", overwrite=True) if "repeat" in args and "each_steps" in args: - if isinstance(args["repeat"], list) and isinstance(args["each_steps"], list): + if isinstance(args["repeat"], list) and isinstance( + args["each_steps"], list + ): for i, entry in enumerate(args["repeat"]): - pair = self.populate_tree(timesteps, 'pair') - self.populate_tree(pair, 'repeat', text=entry) - self.populate_tree(pair, 'each_steps', text=args['each_steps'][i]) + pair = self.populate_tree(timesteps, "pair") + self.populate_tree(pair, "repeat", text=entry) + self.populate_tree( + pair, "each_steps", text=args["each_steps"][i] + ) else: - pair = self.populate_tree(timesteps, 'pair') - self.populate_tree(pair, 'repeat', text=args["repeat"]) - self.populate_tree(pair, 'each_steps', text=args['each_steps']) + pair = self.populate_tree(timesteps, "pair") + self.populate_tree(pair, "repeat", text=args["repeat"]) + self.populate_tree(pair, "each_steps", text=args["each_steps"]) else: - raise KeyError("You muss provide repeat and each_steps attributes \ - to define additional output pairs.") + msg = """You muss provide repeat and each_steps attributes \ + to define additional output pairs.""" + raise KeyError(msg) - def add_global_process_coupling(self, **args): + def add_global_process_coupling(self, **args: Any) -> None: """ Add a process section to timeloop Parameters ---------- - max_iter : `str` - optional, needs to be specified once + max_iter : `str` optional, needs to be specified once convergence_type : `str` abstol : `str` abstols : `str` @@ -343,38 +455,56 @@ def add_global_process_coupling(self, **args): """ self._convertargs(args) if "max_iter" in args: - self.populate_tree(self.gpc, "max_iter", text=args['max_iter'], overwrite=True) - convergence_criteria = self.populate_tree(self.gpc, "convergence_criteria", overwrite=True) + self.populate_tree( + self.gpc, "max_iter", text=args["max_iter"], overwrite=True + ) + convergence_criteria = self.populate_tree( + self.gpc, "convergence_criteria", overwrite=True + ) if "convergence_type" not in args: - raise KeyError("No convergence criterion given. \ - Specify convergence_type.") - conv_crit = self.populate_tree(convergence_criteria, 'convergence_criterion') - self.populate_tree(conv_crit, 'type', text=args['convergence_type']) + msg = """No convergence criterion given. \ + Specify convergence_type.""" + raise KeyError(msg) + conv_crit = self.populate_tree( + convergence_criteria, "convergence_criterion" + ) + self.populate_tree(conv_crit, "type", text=args["convergence_type"]) if "norm_type" not in args: - raise KeyError("No norm_type given.") - self.populate_tree(conv_crit, 'norm_type', text=args['norm_type']) - if (args["convergence_type"] == "DeltaX") or (args["convergence_type"] == "Residual"): - if (("abstols" in args) or ("reltols" in args)): - raise KeyError("Plural tolerances only available for PerComponent conv. types") + msg = "No norm_type given." + raise KeyError(msg) + self.populate_tree(conv_crit, "norm_type", text=args["norm_type"]) + if (args["convergence_type"] == "DeltaX") or ( + args["convergence_type"] == "Residual" + ): + if ("abstols" in args) or ("reltols" in args): + msg = "Plural tolerances only available for PerComponent conv. types" + raise KeyError(msg) if "abstol" in args: - self.populate_tree(conv_crit, 'abstol', text=args['abstol']) + self.populate_tree(conv_crit, "abstol", text=args["abstol"]) if "reltol" in args: - self.populate_tree(conv_crit, 'reltol', text=args['reltol']) - elif (args["convergence_type"] == "PerComponentDeltaX") or (args["convergence_type"] == "PerComponentResidual"): - if (("abstol" in args) or ("reltol" in args)): - raise KeyError("Singular tolerances only available for scalar conv. types") + self.populate_tree(conv_crit, "reltol", text=args["reltol"]) + elif (args["convergence_type"] == "PerComponentDeltaX") or ( + args["convergence_type"] == "PerComponentResidual" + ): + if ("abstol" in args) or ("reltol" in args): + msg = ( + "Singular tolerances only available for scalar conv. types" + ) + raise KeyError(msg) if "abstols" in args: - self.populate_tree(conv_crit, 'abstols', text=args['abstols']) + self.populate_tree(conv_crit, "abstols", text=args["abstols"]) if "reltols" in args: - self.populate_tree(conv_crit, 'reltols', text=args['reltols']) + self.populate_tree(conv_crit, "reltols", text=args["reltols"]) else: - raise KeyError("No convergence_type given.") + msg = "No convergence_type given." + raise KeyError(msg) if "local_coupling_processes" in args: if "local_coupling_processes_max_iter" not in args: - raise KeyError("local_coupling_processes_max_iter parameter is missing") + msg = "local_coupling_processes_max_iter parameter is missing" + raise KeyError(msg) lcp = self.populate_tree(self.gpc, "local_coupling_processes") - self.populate_tree(lcp, "max_iter", text=args['local_coupling_processes_max_iter']) + self.populate_tree( + lcp, "max_iter", text=args["local_coupling_processes_max_iter"] + ) for name in args["local_coupling_processes"]: self.populate_tree(lcp, "process_name", text=name) - - diff --git a/ogs6py/ogs.py b/ogs6py/ogs.py index 3f02788..38cd10e 100644 --- a/ogs6py/ogs.py +++ b/ogs6py/ogs.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- """ -ogs6py is a python-API for the OpenGeoSys finite element sofware. +ogs6py is a python-API for the OpenGeoSys finite element software. Its main functionalities include creating and altering OGS6 input files as well as executing OGS. Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) @@ -11,19 +10,46 @@ """ # pylint: disable=C0103, R0902, R0914, R0913 -import sys + +import copy import os +import shutil import subprocess +import sys import time -import copy -import shutil +from pathlib import Path +from typing import Any + import pandas as pd from lxml import etree as ET -from ogs6py.classes import (display, geo, mesh, python_script, processes, media, timeloop, - local_coordinate_system, parameters, curves, processvars, linsolvers, nonlinsolvers) + +from ogs6py.classes import ( + curves, + display, + geo, + linsolvers, + local_coordinate_system, + media, + mesh, + nonlinsolvers, + parameters, + processes, + processvars, + python_script, + timeloop, +) +from ogs6py.classes.properties import ( + Property, + PropertySet, + Value, + expand_tensors, + expand_van_genuchten, + location_pointer, + property_dict, +) import ogs6py.log_parser.log_parser as parser import ogs6py.log_parser.common_ogs_analyses as parse_fcts -from ogs6py.classes.properties import * + class OGS: """Class for an OGS6 model. @@ -38,42 +64,44 @@ class OGS: INPUT_FILE : `str`, optional Filename of the input project file XMLSTRING : `str`,optional + String containing the XML tree OMP_NUM_THREADS : `int`, optional Sets the environmentvariable before OGS execution to restrict number of OMP Threads VERBOSE : `bool`, optional Default: False + """ - def __init__(self, **args): + + def __init__(self, **args: Any) -> None: sys.setrecursionlimit(10000) - self.tag = [] - self.logfile = "out.log" + self.logfile: Path = Path("out.log") self.tree = None - self.include_elements = [] - self.include_files = [] - self.add_includes = [] - self.output_dir = "" - if "VERBOSE" in args: - self.verbose = args["VERBOSE"] - else: - self.verbose = False - if "OMP_NUM_THREADS" in args: - self.threads = args["OMP_NUM_THREADS"] - else: - self.threads = None + self.include_elements: list[ET.Element] = [] + self.include_files: list[Path] = [] + self.add_includes: list[dict[str, str]] = [] + self.output_dir: Path = Path() # default -> current dir + self.verbose: bool = args.get("VERBOSE", False) + self.threads: int = args.get("OMP_NUM_THREADS", None) + self.asm_threads: int = args.get("OGS_ASM_THREADS", self.threads) + self.inputfile: Path | None = None + self.folder: Path = Path() + if "PROJECT_FILE" in args: - self.prjfile = args['PROJECT_FILE'] + self.prjfile = Path(args["PROJECT_FILE"]) else: print("PROJECT_FILE for output not given. Calling it default.prj.") - self.prjfile = "default.prj" + self.prjfile = Path("default.prj") if "INPUT_FILE" in args: - if os.path.isfile(args['INPUT_FILE']) is True: - self.inputfile = args['INPUT_FILE'] - self.folder, _ = os.path.split(self.inputfile) + input_file = Path(args["INPUT_FILE"]) + if input_file.is_file(): + self.inputfile = input_file + self.folder = input_file.parent _ = self._get_root() if self.verbose is True: display.Display(self.tree) else: - raise RuntimeError(f"Input project file {args['INPUT_FILE']} not found.") + msg = f"Input project file {args['INPUT_FILE']} not found." + raise FileNotFoundError(msg) else: self.inputfile = None self.root = ET.Element("OpenGeoSysProject") @@ -83,7 +111,7 @@ def __init__(self, **args): tree_ = ET.fromstring(tree_string, parser=parse) self.tree = ET.ElementTree(tree_) if "XMLSTRING" in args: - root = ET.fromstring(args['XMLSTRING']) + root = ET.fromstring(args["XMLSTRING"]) self.tree = ET.ElementTree(root) self.geometry = geo.Geo(self.tree) self.mesh = mesh.Mesh(self.tree) @@ -91,47 +119,57 @@ def __init__(self, **args): self.python_script = python_script.PythonScript(self.tree) self.media = media.Media(self.tree) self.time_loop = timeloop.TimeLoop(self.tree) - self.local_coordinate_system = local_coordinate_system.LocalCoordinateSystem(self.tree) + self.local_coordinate_system = ( + local_coordinate_system.LocalCoordinateSystem(self.tree) + ) self.parameters = parameters.Parameters(self.tree) self.curves = curves.Curves(self.tree) self.process_variables = processvars.ProcessVars(self.tree) self.nonlinear_solvers = nonlinsolvers.NonLinSolvers(self.tree) self.linear_solvers = linsolvers.LinSolvers(self.tree) - def __replace_blocks_by_includes(self): + def __replace_blocks_by_includes(self) -> None: for i, file in enumerate(self.include_files): parent_element = self.include_elements[i].getparent() include_element = ET.SubElement(parent_element, "include") - if self.folder == "": - file_ = file - else: - file_ = file.replace(f"{self.folder}/","") - include_element.set("file", file_) + file_ = file if self.folder.cwd() else file.relative_to(self.folder) + include_element.set("file", str(file_)) parse = ET.XMLParser(remove_blank_text=True) - include_string = ET.tostring(self.include_elements[i], pretty_print=True) + include_string = ET.tostring( + self.include_elements[i], pretty_print=True + ) include_parse = ET.fromstring(include_string, parser=parse) include_tree = ET.ElementTree(include_parse) ET.indent(include_tree, space=" ") - include_tree.write(file, - encoding="ISO-8859-1", - xml_declaration=False, - pretty_print=True) + include_tree.write( + file, + encoding="ISO-8859-1", + xml_declaration=False, + pretty_print=True, + ) parent_element.remove(self.include_elements[i]) - def _get_root(self, remove_blank_text=False, remove_comments=False): - parser = ET.XMLParser(remove_blank_text=remove_blank_text, remove_comments=remove_comments, huge_tree=True) + def _get_root( + self, remove_blank_text: bool = False, remove_comments: bool = False + ) -> ET.Element: + parser = ET.XMLParser( + remove_blank_text=remove_blank_text, + remove_comments=remove_comments, + huge_tree=True, + ) if self.tree is None: if self.inputfile is not None: - self.tree = ET.parse(self.inputfile, parser) + self.tree = ET.parse(str(self.inputfile), parser) else: - raise RuntimeError("This should not happen.") - #self.build_tree() + msg = "This should not happen." + raise RuntimeError(msg) + # self.build_tree() root = self.tree.getroot() all_occurrences = root.findall(".//include") for occurrence in all_occurrences: - self.include_files.append(os.path.join(self.folder, occurrence.attrib["file"])) - for i, occurrence in enumerate(all_occurrences): - _tree = ET.parse(self.include_files[i], parser) + self.include_files.append(occurrence.attrib["file"]) + for i, _ in enumerate(all_occurrences): + _tree = ET.parse(str(self.folder / self.include_files[i]), parser) _root = _tree.getroot() parentelement = all_occurrences[i].getparent() children_before = parentelement.getchildren() @@ -143,10 +181,15 @@ def _get_root(self, remove_blank_text=False, remove_comments=False): self.include_elements.append(child) return root - def _remove_empty_elements(self): + def _remove_empty_elements(self) -> None: root = self._get_root() empty_text_list = ["./geometry", "./python_script"] - empty_el_list = ["./time_loop/global_process_coupling", "./curves", "./media"] + empty_el_list = [ + "./time_loop/global_process_coupling", + "./curves", + "./media", + "./local_coordinate_system", + ] for element in empty_text_list: entry = root.find(element) if entry is not None: @@ -155,44 +198,45 @@ def _remove_empty_elements(self): self.remove_element(".", tag=entry.tag, text=None) for element in empty_el_list: entry = root.find(element) - if entry is not None: - if len(entry.getchildren()) == 0: - entry.getparent().remove(entry) + if (entry is not None) and (len(entry.getchildren()) == 0): + entry.getparent().remove(entry) @classmethod - def _get_parameter_pointer(cls, root, name, xpath): + def _get_parameter_pointer( + cls, root: ET.Element, name: str, xpath: str + ) -> ET.Element: params = root.findall(xpath) parameterpointer = None for parameter in params: for paramproperty in parameter: - if paramproperty.tag == "name": - if paramproperty.text == name: - parameterpointer = parameter + if (paramproperty.tag == "name") and ( + paramproperty.text == name + ): + parameterpointer = parameter if parameterpointer is None: - print("Parameter/Property not found") - raise RuntimeError + msg = "Parameter/Property not found" + raise RuntimeError(msg) return parameterpointer @classmethod - def _get_medium_pointer(cls, root, mediumid): + def _get_medium_pointer(cls, root: ET.Element, mediumid: int) -> ET.Element: xpathmedia = "./media/medium" mediae = root.findall(xpathmedia) mediumpointer = None for medium in mediae: try: - if medium.attrib['id'] == str(mediumid): + if medium.attrib["id"] == str(mediumid): mediumpointer = medium except KeyError: - if len(mediae) == 1: - if str(mediumid) == "0": - mediumpointer = medium + if (len(mediae) == 1) and (str(mediumid) == "0"): + mediumpointer = medium if mediumpointer is None: - print("Medium not found") - raise RuntimeError + msg = "Medium not found" + raise RuntimeError(msg) return mediumpointer @classmethod - def _get_phase_pointer(cls, root, phase): + def _get_phase_pointer(cls, root: ET.Element, phase: str) -> ET.Element: phases = root.findall("./phases/phase") phasetypes = root.findall("./phases/phase/type") phasecounter = None @@ -201,12 +245,14 @@ def _get_phase_pointer(cls, root, phase): phasecounter = i phasepointer = phases[phasecounter] if phasepointer is None: - print("Phase not found") - raise RuntimeError + msg = "Phase not found" + raise RuntimeError(msg) return phasepointer @classmethod - def _get_component_pointer(cls, root, component): + def _get_component_pointer( + cls, root: ET.Element, component: str + ) -> ET.Element: components = root.findall("./components/component") componentnames = root.findall("./components/component/name") componentcounter = None @@ -215,21 +261,32 @@ def _get_component_pointer(cls, root, component): componentcounter = i componentpointer = components[componentcounter] if componentpointer is None: - print("Component not found") - raise RuntimeError + msg = "Component not found" + raise RuntimeError(msg) return componentpointer @classmethod - def _set_type_value(cls, parameterpointer, value, parametertype, valuetag=None): + def _set_type_value( + cls, + parameterpointer: ET.Element, + value: int, + parametertype: Any | None, + valuetag: str | None = None, + ) -> None: for paramproperty in parameterpointer: - if paramproperty.tag == valuetag: - if not value is None: - paramproperty.text = str(value) - elif paramproperty.tag == "type": - if not parametertype is None: - paramproperty.text = str(parametertype) - - def add_element(self, parent_xpath="./", tag=None, text=None, attrib_list=None, attrib_value_list=None): + if (paramproperty.tag == valuetag) and (value is not None): + paramproperty.text = str(value) + elif paramproperty.tag == "type" and parametertype is not None: + paramproperty.text = str(parametertype) + + def add_element( + self, + parent_xpath: str = "./", + tag: str | None = None, + text: str | None = None, + attrib_list: Any | None = None, + attrib_value_list: Any | None = None, + ) -> None: """General method to add an Entry An element is a single tag containing 'text', @@ -251,22 +308,24 @@ def add_element(self, parent_xpath="./", tag=None, text=None, attrib_list=None, root = self._get_root() parents = root.findall(parent_xpath) for parent in parents: - if not tag is None: + if tag is not None: q = ET.SubElement(parent, tag) - if not text is None: + if text is not None: q.text = str(text) - if not attrib_list is None: + if attrib_list is not None: if attrib_value_list is None: - print("attrib_value_list is not given for add_element") - raise RuntimeError + msg = "attrib_value_list is not given for add_element" + raise RuntimeError(msg) if len(attrib_list) != len(attrib_value_list): - print("The size of attrib_list is not the same as that of attrib_value_list") - raise RuntimeError + msg = "The size of attrib_list is not the same as that of attrib_value_list" + raise RuntimeError(msg) - for attrib, attrib_value in zip (attrib_list, attrib_value_list): + for attrib, attrib_value in zip( + attrib_list, attrib_value_list, strict=False + ): q.set(attrib, attrib_value) - def add_include(self, parent_xpath="./", file=""): + def add_include(self, parent_xpath: str = "./", file: str = "") -> None: """add include element Parameters @@ -276,17 +335,24 @@ def add_include(self, parent_xpath="./", file=""): file : `str` file name """ - self.add_includes.append({'parent_xpath': parent_xpath, 'file': file}) + self.add_includes.append({"parent_xpath": parent_xpath, "file": file}) - def _add_includes(self, root): + def _add_includes(self, root: ET.Element) -> None: for add_include in self.add_includes: - parent = root.findall(add_include['parent_xpath']) + parent = root.findall(add_include["parent_xpath"]) newelement = [] for i, entry in enumerate(parent): newelement.append(ET.SubElement(entry, "include")) - newelement[i].set("file", add_include['file']) - - def add_block(self, blocktag, block_attrib=None, parent_xpath="./", taglist=None, textlist=None): + newelement[i].set("file", add_include["file"]) + + def add_block( + self, + blocktag: str, + block_attrib: Any | None = None, + parent_xpath: str = "./", + taglist: list[str] | None = None, + textlist: list[str] | None = None, + ) -> None: """General method to add a Block A block consists of an enclosing tag containing a number of @@ -297,26 +363,31 @@ def add_block(self, blocktag, block_attrib=None, parent_xpath="./", taglist=None blocktag : `str` name of the enclosing tag block_attrib : 'dict', optional + attributes belonging to the blocktag parent_xpath : `str`, optional XPath of the parent tag taglist : `list` list of strings containing the keys textlist : `list` list of strings, ints or floats retaining the corresponding values + """ root = self._get_root() parents = root.findall(parent_xpath) for parent in parents: q = ET.SubElement(parent, blocktag) - if not block_attrib is None: + if block_attrib is not None: for key, val in block_attrib.items(): - q.set(key,val) - for i, tag in enumerate(taglist): - r = ET.SubElement(q, tag) - if not textlist[i] is None: - r.text = str(textlist[i]) - - def deactivate_property(self, name, mediumid=None, phase=None): + q.set(key, val) + if (taglist is not None) and (textlist is not None): + for i, tag in enumerate(taglist): + r = ET.SubElement(q, tag) + if textlist[i] is not None: + r.text = str(textlist[i]) + + def deactivate_property( + self, name: str, mediumid: int = 0, phase: str | None = None + ) -> None: """Replaces MPL properties by a comment Parameters @@ -324,20 +395,27 @@ def deactivate_property(self, name, mediumid=None, phase=None): mediumid : `int` id of the medium phase : `str` + name of the phase name : `str` property name """ root = self._get_root() mediumpointer = self._get_medium_pointer(root, mediumid) - phasepointer = self._get_phase_pointer(mediumpointer, phase) xpathparameter = "./properties/property" if phase is None: - parameterpointer = self._get_parameter_pointer(mediumpointer, name, xpathparameter) + parameterpointer = self._get_parameter_pointer( + mediumpointer, name, xpathparameter + ) else: - parameterpointer = self._get_parameter_pointer(phasepointer, name, xpathparameter) - parameterpointer.getparent().replace(parameterpointer, ET.Comment(ET.tostring(parameterpointer))) - - def deactivate_parameter(self, name): + phasepointer = self._get_phase_pointer(mediumpointer, phase) + parameterpointer = self._get_parameter_pointer( + phasepointer, name, xpathparameter + ) + parameterpointer.getparent().replace( + parameterpointer, ET.Comment(ET.tostring(parameterpointer)) + ) + + def deactivate_parameter(self, name: str) -> None: """Replaces parameters by a comment Parameters @@ -347,10 +425,16 @@ def deactivate_parameter(self, name): """ root = self._get_root() parameterpath = "./parameters/parameter" - parameterpointer = self._get_parameter_pointer(root, name, parameterpath) - parameterpointer.getparent().replace(parameterpointer, ET.Comment(ET.tostring(parameterpointer))) - - def remove_element(self, xpath, tag = None, text = None): + parameterpointer = self._get_parameter_pointer( + root, name, parameterpath + ) + parameterpointer.getparent().replace( + parameterpointer, ET.Comment(ET.tostring(parameterpointer)) + ) + + def remove_element( + self, xpath: str, tag: str | None = None, text: str | None = None + ) -> None: """Removes an element Parameters @@ -369,7 +453,9 @@ def remove_element(self, xpath, tag = None, text = None): if sub_element.tag == tag and sub_element.text == text: sub_element.getparent().remove(sub_element) - def replace_text(self, value, xpath=".", occurrence=-1): + def replace_text( + self, value: str | int, xpath: str = ".", occurrence: int = -1 + ) -> None: """General method for replacing text between opening and closing tags @@ -380,19 +466,22 @@ def replace_text(self, value, xpath=".", occurrence=-1): xpath : `str`, optional XPath of the tag occurrence : `int`, optional - Easy way to adress nonunique XPath addresses by their occurence + Easy way to address nonunique XPath addresses by their occurrence from the top of the XML file Default: -1 """ root = self._get_root() find_xpath = root.findall(xpath) for i, entry in enumerate(find_xpath): - if occurrence < 0: - entry.text = str(value) - elif i == occurrence: + if occurrence < 0 or i == occurrence: entry.text = str(value) - def replace_block_by_include(self, xpath="./", filename="include.xml", occurrence=0): + def replace_block_by_include( + self, + xpath: str = "./", + filename: str = "include.xml", + occurrence: int = 0, + ) -> None: """General method for replacing a block by an include @@ -401,20 +490,22 @@ def replace_block_by_include(self, xpath="./", filename="include.xml", occurrenc xpath : `str`, optional XPath of the tag filename : `str`, optional + name of the include file occurrence : `int`, optional Addresses nonunique XPath by their occurece - Default: 0 """ - print("Note: Includes are only written if write_input(keep_includes=True) is called.") + print( + "Note: Includes are only written if write_input(keep_includes=True) is called." + ) root = self._get_root() find_xpath = root.findall(xpath) for i, entry in enumerate(find_xpath): if i == occurrence: self.include_elements.append(entry) - self.include_files.append(os.path.join(self.folder, filename)) + self.include_files.append(self.prjfile.parent / filename) - def replace_mesh(self, oldmesh, newmesh): - """ Method to replace meshes + def replace_mesh(self, oldmesh: str, newmesh: str) -> None: + """Method to replace meshes Parameters ---------- @@ -423,24 +514,31 @@ def replace_mesh(self, oldmesh, newmesh): """ root = self._get_root() bulkmesh = root.find("./mesh") - try: + if bulkmesh is not None: if bulkmesh.text == oldmesh: bulkmesh.text = newmesh - except: - pass + else: + msg = "Bulk mesh name and oldmesh argument don't agree." + raise RuntimeError(msg) all_occurrences_meshsection = root.findall("./meshes/mesh") for occurrence in all_occurrences_meshsection: if occurrence.text == oldmesh: occurrence.text = newmesh all_occurrences = root.findall(".//mesh") for occurrence in all_occurrences: - if not occurrence in all_occurrences_meshsection: - oldmesh_stripped = os.path.split(oldmesh)[1].replace(".vtu","") - newmesh_stripped = os.path.split(newmesh)[1].replace(".vtu","") + if occurrence not in all_occurrences_meshsection: + oldmesh_stripped = os.path.split(oldmesh)[1].replace(".vtu", "") + newmesh_stripped = os.path.split(newmesh)[1].replace(".vtu", "") if occurrence.text == oldmesh_stripped: occurrence.text = newmesh_stripped - def replace_parameter(self, name=None, parametertype=None, taglist=None, textlist=None): + def replace_parameter( + self, + name: str = "", + parametertype: str = "", + taglist: list[str] | None = None, + textlist: list[str] | None = None, + ) -> None: """Replacing parametertypes and values Parameters @@ -455,20 +553,24 @@ def replace_parameter(self, name=None, parametertype=None, taglist=None, textlis values of parameter """ root = self._get_root() - parameterpath = "./parameters/parameter[name=\'"+name+"\']" + parameterpath = "./parameters/parameter[name='" + name + "']" parent = root.find(parameterpath) children = parent.getchildren() for child in children: - if not child.tag in ["name","type"]: + if child.tag not in ["name", "type"]: self.remove_element(f"{parameterpath}/{child.tag}") paramtype = root.find(f"{parameterpath}/type") paramtype.text = parametertype - for i, tag in enumerate(taglist): - if not tag in ["name","type"]: - self.add_element(parent_xpath=parameterpath, - tag=tag, text=textlist[i]) - - def replace_parameter_value(self, name=None, value=None, valuetag="value"): + if (taglist is not None) and (textlist is not None): + for i, tag in enumerate(taglist): + if tag not in ["name", "type"]: + self.add_element( + parent_xpath=parameterpath, tag=tag, text=textlist[i] + ) + + def replace_parameter_value( + self, name: str = "", value: int = 0, valuetag: str = "value" + ) -> None: """Replacing parameter values Parameters @@ -485,11 +587,21 @@ def replace_parameter_value(self, name=None, value=None, valuetag="value"): """ root = self._get_root() parameterpath = "./parameters/parameter" - parameterpointer = self._get_parameter_pointer(root, name, parameterpath) + parameterpointer = self._get_parameter_pointer( + root, name, parameterpath + ) self._set_type_value(parameterpointer, value, None, valuetag=valuetag) - def replace_phase_property_value(self, mediumid=None, phase="AqueousLiquid", component=None, name=None, value=None, - propertytype=None, valuetag="value"): + def replace_phase_property_value( + self, + mediumid: int = 0, + phase: str = "AqueousLiquid", + component: str | None = None, + name: str = "", + value: int = 0, + propertytype: str = "Constant", + valuetag: str = "value", + ) -> None: """Replaces properties in medium phases Parameters @@ -499,6 +611,7 @@ def replace_phase_property_value(self, mediumid=None, phase="AqueousLiquid", com phase : `str` name of the phase component : `str` + name of the component name : `str` property name value : `str`/any @@ -515,11 +628,21 @@ def replace_phase_property_value(self, mediumid=None, phase="AqueousLiquid", com if component is not None: phasepointer = self._get_component_pointer(phasepointer, component) xpathparameter = "./properties/property" - parameterpointer = self._get_parameter_pointer(phasepointer, name, xpathparameter) - self._set_type_value(parameterpointer, value, propertytype, valuetag=valuetag) - - def replace_medium_property_value(self, mediumid=None, name=None, value=None, propertytype=None, - valuetag="value"): + parameterpointer = self._get_parameter_pointer( + phasepointer, name, xpathparameter + ) + self._set_type_value( + parameterpointer, value, propertytype, valuetag=valuetag + ) + + def replace_medium_property_value( + self, + mediumid: int = 0, + name: str = "", + value: int = 0, + propertytype: str = "Constant", + valuetag: str = "value", + ) -> None: """Replaces properties in medium (not belonging to any phase) Parameters @@ -539,36 +662,45 @@ def replace_medium_property_value(self, mediumid=None, name=None, value=None, pr root = self._get_root() mediumpointer = self._get_medium_pointer(root, mediumid) xpathparameter = "./properties/property" - parameterpointer = self._get_parameter_pointer(mediumpointer, name, xpathparameter) - self._set_type_value(parameterpointer, value, propertytype, valuetag=valuetag) - - def set(self, **args): + parameterpointer = self._get_parameter_pointer( + mediumpointer, name, xpathparameter + ) + self._set_type_value( + parameterpointer, value, propertytype, valuetag=valuetag + ) + + def set(self, **args: str | int) -> None: """ Sets directly a uniquely defined property List of properties is given in the dictory below """ property_db = { - "t_initial": "./time_loop/processes/process/time_stepping/t_initial", - "t_end": "./time_loop/processes/process/time_stepping/t_end", - "output_prefix": "./time_loop/output/prefix", - "reltols": "./time_loop/processes/process/convergence_criterion/reltols", - "abstols": "./time_loop/processes/process/convergence_criterion/abstols", - "mass_lumping": "./processes/process/mass_lumping", - "eigen_solver": "./linear_solvers/linear_solver/eigen/solver_type", - "eigen_precon": "./linear_solvers/linear_solver/eigen/precon_type", - "eigen_max_iteration_step": "./linear_solvers/linear_solver/eigen/max_iteration_step", - "eigen_error_tolerance": "./linear_solvers/linear_solver/eigen/error_tolerance", - "eigen_scaling": "./linear_solvers/linear_solver/eigen/scaling", - "petsc_prefix": "./linear_solvers/linear_solver/petsc/prefix", - "petsc_parameters": "./linear_solvers/linear_solver/petsc/parameters", - "compensate_displacement": "./process_variables/process_variable[name='displacement']/compensate_non_equilibrium_initial_residuum", - "compensate_all": "./process_variables/process_variable/compensate_non_equilibrium_initial_residuum" - } + "t_initial": "./time_loop/processes/process/time_stepping/t_initial", + "t_end": "./time_loop/processes/process/time_stepping/t_end", + "output_prefix": "./time_loop/output/prefix", + "reltols": "./time_loop/processes/process/convergence_criterion/reltols", + "abstols": "./time_loop/processes/process/convergence_criterion/abstols", + "mass_lumping": "./processes/process/mass_lumping", + "eigen_solver": "./linear_solvers/linear_solver/eigen/solver_type", + "eigen_precon": "./linear_solvers/linear_solver/eigen/precon_type", + "eigen_max_iteration_step": "./linear_solvers/linear_solver/eigen/max_iteration_step", + "eigen_error_tolerance": "./linear_solvers/linear_solver/eigen/error_tolerance", + "eigen_scaling": "./linear_solvers/linear_solver/eigen/scaling", + "petsc_prefix": "./linear_solvers/linear_solver/petsc/prefix", + "petsc_parameters": "./linear_solvers/linear_solver/petsc/parameters", + "compensate_displacement": "./process_variables/process_variable[name='displacement']/compensate_non_equilibrium_initial_residuum", + "compensate_all": "./process_variables/process_variable/compensate_non_equilibrium_initial_residuum", + } for key, val in args.items(): self.replace_text(val, xpath=property_db[key]) - - def restart(self, restart_suffix="_restart", t_initial=None, t_end=None, zero_displacement=False): + def restart( + self, + restart_suffix: str = "_restart", + t_initial: float | None = None, + t_end: float | None = None, + zero_displacement: bool = False, + ) -> None: """Prepares the project file for a restart. Takes the last time step from the PVD file mentioned in the PRJ file. @@ -588,16 +720,17 @@ def restart(self, restart_suffix="_restart", t_initial=None, t_end=None, zero_di root_prj = self._get_root() filetype = root_prj.find("./time_loop/output/type").text - pvdfile = root_prj.find("./time_loop/output/prefix").text+".pvd" - pvdfile = os.path.join(self.output_dir, pvdfile) - if not filetype == "VTK": - raise RuntimeError("Output fil (*args: object) Please use VTK") - tree = ET.parse(pvdfile) - xpath="./Collection/DataSet" + pvdfile = root_prj.find("./time_loop/output/prefix").text + ".pvd" + pvdfile = self.output_dir / pvdfile + if filetype != "VTK": + msg = "Output file type unknown. Please use VTK." + raise RuntimeError(msg) + tree = ET.parse(pvdfile) + xpath = "./Collection/DataSet" root_pvd = tree.getroot() find_xpath = root_pvd.findall(xpath) - lastfile = find_xpath[-1].attrib['file'] - last_time = find_xpath[-1].attrib['timestep'] + lastfile = find_xpath[-1].attrib["file"] + last_time = find_xpath[-1].attrib["timestep"] try: bulk_mesh = root_prj.find("./mesh").text except AttributeError: @@ -606,32 +739,65 @@ def restart(self, restart_suffix="_restart", t_initial=None, t_end=None, zero_di except AttributeError: print("Can't find bulk mesh.") self.replace_mesh(bulk_mesh, lastfile) - root_prj.find("./time_loop/output/prefix").text = root_prj.find("./time_loop/output/prefix").text + restart_suffix - t_initials = root_prj.findall("./time_loop/processes/process/time_stepping/t_initial") - t_ends = root_prj.findall("./time_loop/processes/process/time_stepping/t_end") + root_prj.find("./time_loop/output/prefix").text = ( + root_prj.find("./time_loop/output/prefix").text + restart_suffix + ) + t_initials = root_prj.findall( + "./time_loop/processes/process/time_stepping/t_initial" + ) + t_ends = root_prj.findall( + "./time_loop/processes/process/time_stepping/t_end" + ) for i, t0 in enumerate(t_initials): if t_initial is None: t0.text = last_time else: t0.text = str(t_initial) - if not t_end is None: + if t_end is not None: t_ends[i].text = str(t_end) - process_vars = root_prj.findall("./process_variables/process_variable/name") - ic_names = root_prj.findall("./process_variables/process_variable/initial_condition") + process_vars = root_prj.findall( + "./process_variables/process_variable/name" + ) + ic_names = root_prj.findall( + "./process_variables/process_variable/initial_condition" + ) for i, process_var in enumerate(process_vars): if process_var.text == "displacement" and zero_displacement is True: - print("Please make sure that epsilon_ip is removed from the VTU file before you run OGS.") + print( + "Please make sure that epsilon_ip is removed from the VTU file before you run OGS." + ) zero = {"1": "0", "2": "0 0", "3": "0 0 0"} - cpnts = root_prj.find("./process_variables/process_variable[name='displacement']/components").text - self.replace_parameter(name=ic_names[i].text, parametertype="Constant", taglist=["values"], - textlist=[zero[cpnts]]) + cpnts = root_prj.find( + "./process_variables/process_variable[name='displacement']/components" + ).text + self.replace_parameter( + name=ic_names[i].text, + parametertype="Constant", + taglist=["values"], + textlist=[zero[cpnts]], + ) else: - self.replace_parameter(name=ic_names[i].text, parametertype="MeshNode", taglist=["mesh","field_name"], - textlist=[lastfile.split("/")[-1].replace(".vtu",""), process_var.text]) + self.replace_parameter( + name=ic_names[i].text, + parametertype="MeshNode", + taglist=["mesh", "field_name"], + textlist=[ + lastfile.split("/")[-1].replace(".vtu", ""), + process_var.text, + ], + ) self.remove_element("./processes/process/initial_stress") - - def run_model(self, logfile="out.log", path=None, args=None, container_path=None, wrapper=None, write_logs=True, write_prj_to_pvd=True): + def run_model( + self, + logfile: Path = Path("out.log"), + path: Path | None = None, + args: Any | None = None, + container_path: Path | str | None = None, + wrapper: Any | None = None, + write_logs: bool = True, + write_prj_to_pvd: bool = True, + ) -> None: """Command to run OGS. Runs OGS with the project file specified as PROJECT_FILE @@ -654,55 +820,75 @@ def run_model(self, logfile="out.log", path=None, args=None, container_path=None write_logs: `bolean`, optional set False to omit logging """ - - ogs_path = "" - if self.threads is None: - env_export = "" - else: - env_export = f"export OMP_NUM_THREADS={self.threads} && " - if not container_path is None: - container_path = os.path.expanduser(container_path) - if os.path.isfile(container_path) is False: - raise RuntimeError('The specific container-path is not a file. Please provide a path to the OGS container.') - if not container_path.endswith(".sif"): - raise RuntimeError('The specific file is not a Singularity container. Please provide a *.sif file containing OGS.') - if not path is None: - path = os.path.expanduser(path) - if os.path.isdir(path) is False: - if not container_path is None: - raise RuntimeError('The specified path is not a directory. Please provide a directory containing the Singularity executable.') - raise RuntimeError('The specified path is not a directory. Please provide a directory containing the OGS executable.') - ogs_path += path - if not logfile is None: - self.logfile = logfile - if not container_path is None: + ogs_path: Path = Path() + env_export = "" + if self.threads is not None: + env_export += f"export OMP_NUM_THREADS={self.threads} && " + if self.asm_threads is not None: + env_export += f"export OGS_ASM_THREADS={self.asm_threads} && " + if container_path is not None: + container_path = Path(container_path) + container_path = container_path.expanduser() + if not container_path.is_file(): + msg = "The specific container-path is not a file. Please provide a path to the OGS container." + raise RuntimeError(msg) + if str(container_path.suffix).lower() != ".sif": + msg = "The specific file is not a Singularity container. Please provide a *.sif file containing OGS." + raise RuntimeError(msg) + if path: + path = Path(path) + path = path.expanduser() + if not path.is_dir(): + if container_path is not None: + msg = "The specified path is not a directory. Please provide a directory containing the Singularity executable." + raise RuntimeError(msg) + msg = "The specified path is not a directory. Please provide a directory containing the OGS executable." + raise RuntimeError(msg) + ogs_path = ogs_path / path + if logfile is not None: + self.logfile = Path(logfile) + if container_path is not None: if sys.platform == "win32": - raise RuntimeError('Running OGS in a Singularity container is only possible in Linux. See https://sylabs.io/guides/3.0/user-guide/installation.html for Windows solutions.') - ogs_path = os.path.join(ogs_path, "singularity") - if shutil.which(ogs_path) is None: - raise RuntimeError('The Singularity executable was not found. See https://www.opengeosys.org/docs/userguide/basics/container/ for installation instructions.') + msg = "Running OGS in a Singularity container is only possible in Linux. See https://sylabs.io/guides/3.0/user-guide/installation.html for Windows solutions." + raise RuntimeError(msg) + ogs_path = ogs_path / "singularity" + if shutil.which(str(ogs_path)) is None: + msg = "The Singularity executable was not found. See https://www.opengeosys.org/docs/userguide/basics/container/ for installation instructions." + raise RuntimeError(msg) else: if sys.platform == "win32": - ogs_path = os.path.join(ogs_path, "ogs.exe") + ogs_path = ogs_path / "ogs.exe" else: - ogs_path = os.path.join(ogs_path, "ogs") - if shutil.which(ogs_path) is None: - raise RuntimeError('The OGS executable was not found. See https://www.opengeosys.org/docs/userguide/basics/introduction/ for installation instructions.') + ogs_path = ogs_path / "ogs" + if shutil.which(str(ogs_path)) is None: + msg = "The OGS executable was not found. See https://www.opengeosys.org/docs/userguide/basics/introduction/ for installation instructions." + raise RuntimeError(msg) cmd = env_export - if not wrapper is None: + if wrapper is not None: cmd += wrapper + " " cmd += f"{ogs_path} " - if not container_path is None: - if not wrapper is None: - cmd = env_export + "singularity exec " + f"{container_path} " + wrapper + " " + if container_path is not None: + if wrapper is not None: + cmd = ( + env_export + + "singularity exec " + + f"{container_path} " + + wrapper + + " " + ) else: - cmd = env_export + "singularity exec " + f"{container_path} " + "ogs " - if not args is None: + cmd = ( + env_export + + "singularity exec " + + f"{container_path} " + + "ogs " + ) + if args is not None: argslist = args.split(" ") output_dir_flag = False for entry in argslist: if output_dir_flag is True: - self.output_dir = entry + self.output_dir = Path(entry) output_dir_flag = False if "-o" in entry: output_dir_flag = True @@ -713,9 +899,22 @@ def run_model(self, logfile="out.log", path=None, args=None, container_path=None cmd += f"{self.prjfile}" startt = time.time() if sys.platform == "win32": - returncode = subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + returncode = subprocess.run( + cmd, + shell=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + check=False, + ) else: - returncode = subprocess.run(cmd, shell=True, executable="bash", stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) + returncode = subprocess.run( + cmd, + shell=True, + executable="bash", + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + check=False, + ) stopt = time.time() self.exec_time = stopt - startt if returncode.returncode == 0: @@ -723,48 +922,77 @@ def run_model(self, logfile="out.log", path=None, args=None, container_path=None print(f"Execution took {self.exec_time} s") if write_prj_to_pvd is True: self.inputfile = self.prjfile - self.tree = None - root = self._get_root(remove_blank_text=True, remove_comments=True) - prjstring = ET.tostring(root, pretty_print = True) - prjstring = str(prjstring).replace('\r', ' ').replace('\n', ' ').replace('--','') + # self.tree = None # TODO: check whether this line can be safely removed + root = self._get_root( + remove_blank_text=True, remove_comments=True + ) + prjstring = ET.tostring(root, pretty_print=True) + prjstring = ( + str(prjstring) + .replace("\r", " ") + .replace("\n", " ") + .replace("--", "") + ) + if self.tree is None: + msg = "self.tree is empty." + raise AttributeError(msg) fn_type = self.tree.find("./time_loop/output/type").text fn = None if fn_type == "VTK": - fn = self.tree.find("./time_loop/output/prefix").text + ".pvd" - fn = os.path.join(self.output_dir, fn) + fn = ( + self.tree.find("./time_loop/output/prefix").text + + ".pvd" + ) + fn = self.output_dir / fn elif fn_type == "XDMF": prefix = self.tree.find("./time_loop/output/prefix").text mesh = self.tree.find("./mesh") if mesh is None: mesh = self.tree.find("./meshes/mesh") - prefix = os.path.join(self.output_dir, prefix) - fn = prefix + "_" + mesh.text.split(".vtu")[0] + ".xdmf" - if not fn is None: + prefix = self.output_dir / prefix + if mesh is not None: + fn = ( + str(prefix) + + "_" + + mesh.text.split(".vtu")[0] + + ".xdmf" + ) + else: + msg = "No mesh found" + raise AttributeError(msg) + if fn is not None: tree_pvd = ET.parse(fn) root_pvd = tree_pvd.getroot() root_pvd.append(ET.Comment(prjstring)) - tree_pvd.write(fn, encoding="ISO-8859-1", xml_declaration=True, pretty_print=True) + tree_pvd.write( + fn, + encoding="ISO-8859-1", + xml_declaration=True, + pretty_print=True, + ) print("Project file written to output.") else: print(f"Error code: {returncode.returncode}") if write_logs is False: - raise RuntimeError('OGS execution was not successful. Please set write_logs to True to obtain more information.') - num_lines = sum(1 for line in open(self.logfile)) - with open(self.logfile) as file: + msg = "OGS execution was not successful. Please set write_logs to True to obtain more information." + raise RuntimeError(msg) + with self.logfile.open() as lf: + num_lines = len(lf.readlines()) + with self.logfile.open() as file: for i, line in enumerate(file): - if i > num_lines-10: + if i > num_lines - 10: print(line) - raise RuntimeError('OGS execution was not successful.') + msg = "OGS execution was not successful." + raise RuntimeError(msg) - - def write_input(self, keep_includes=False): + def write_input(self, keep_includes: bool = False) -> None: """Writes the projectfile to disk Parameters ---------- keep_includes : `boolean`, optional """ - if not self.tree is None: + if self.tree is not None: self._remove_empty_elements() if keep_includes is True: self.__replace_blocks_by_includes() @@ -777,14 +1005,20 @@ def write_input(self, keep_includes=False): ET.indent(self.tree, space=" ") if self.verbose is True: display.Display(self.tree) - self.tree.write(self.prjfile, - encoding="ISO-8859-1", - xml_declaration=True, - pretty_print=True) - return True - raise RuntimeError("No tree has been build.") - - def parse_out(self, logfile=None, filter=None, maximum_lines=None, reset_index=True): + self.tree.write( + self.prjfile, + encoding="ISO-8859-1", + xml_declaration=True, + pretty_print=True, + ) + else: + msg = "No tree has been build." + raise RuntimeError(msg) + + def parse_out(self, logfile: str | None = None, + filter: str | None = None, + maximum_lines: int | None = None, + reset_index: bool = True) -> pd.DataFrame: """Parses the logfile Parameters @@ -821,11 +1055,16 @@ def parse_out(self, logfile=None, filter=None, maximum_lines=None, reset_index=T return df.reset_index() return df - def property_dataframe(self, mediamapping=None): + def property_dataframe( + self, mediamapping: dict[int, str] | None = None + ) -> pd.DataFrame: newtree = copy.deepcopy(self.tree) + if (newtree is None) or (self.tree is None): + msg = "No tree existing." + raise AttributeError(msg) root = newtree.getroot() - property_list = [] - multidim_prop = {} + property_list: list[Property] = [] + multidim_prop: dict[int, dict] = {} numofmedia = len(self.tree.findall("./media/medium")) if mediamapping is None: mediamapping = {} @@ -835,71 +1074,131 @@ def property_dataframe(self, mediamapping=None): multidim_prop[i] = {} ## preprocessing # write elastic properties to MPL - for entry in newtree.findall("./processes/process/constitutive_relation"): - medium = self._get_medium_pointer(root, entry.attrib["id"]) + for entry in newtree.findall( + "./processes/process/constitutive_relation" + ): + medium = self._get_medium_pointer(root, entry.attrib.get("id", "0")) parent = medium.find("./phases/phase[type='Solid']/properties") taglist = ["name", "type", "parameter_name"] for subentry in entry: - if subentry.tag in ["youngs_modulus", "poissons_ratio","youngs_moduli", "poissons_ratios", "shear_moduli"]: + if subentry.tag in [ + "youngs_modulus", + "poissons_ratio", + "youngs_moduli", + "poissons_ratios", + "shear_moduli", + ]: textlist = [subentry.tag, "Parameter", subentry.text] q = ET.SubElement(parent, "property") for i, tag in enumerate(taglist): r = ET.SubElement(q, tag) - if not textlist[i] is None: + if textlist[i] is not None: r.text = str(textlist[i]) for location in location_pointer: # resolve parameters - parameter_names_add = newtree.findall(f"./media/medium/{location_pointer[location]}properties/property[type='Parameter']/parameter_name") + parameter_names_add = newtree.findall( + f"./media/medium/{location_pointer[location]}properties/property[type='Parameter']/parameter_name" + ) parameter_names = [name.text for name in parameter_names_add] for parameter_name in parameter_names: - param_type = newtree.find(f"./parameters/parameter[name='{parameter_name}']/type").text + param_type = newtree.find( + f"./parameters/parameter[name='{parameter_name}']/type" + ).text if param_type == "Constant": - param_value = newtree.findall(f"./parameters/parameter[name='{parameter_name}']/value") - param_value.append(newtree.find(f"./parameters/parameter[name='{parameter_name}']/values")) - property_type = newtree.findall(f"./media/medium/{location_pointer[location]}properties/property[parameter_name='{parameter_name}']/type") + param_value = newtree.findall( + f"./parameters/parameter[name='{parameter_name}']/value" + ) + param_value.append( + newtree.find( + f"./parameters/parameter[name='{parameter_name}']/values" + ) + ) + property_type = newtree.findall( + f"./media/medium/{location_pointer[location]}properties/property[parameter_name='{parameter_name}']/type" + ) for entry in property_type: entry.text = "Constant" - property_value = newtree.findall(f"./media/medium/{location_pointer[location]}properties/property[parameter_name='{parameter_name}']/parameter_name") + property_value = newtree.findall( + f"./media/medium/{location_pointer[location]}properties/property[parameter_name='{parameter_name}']/parameter_name" + ) for entry in property_value: entry.tag = "value" entry.text = param_value[0].text # expand tensors expand_tensors(self, numofmedia, multidim_prop, root, location) expand_van_genuchten(self, numofmedia, root, location) - property_names = [name.text for name in newtree.findall(f"./media/medium/{location_pointer[location]}properties/property/name")] + property_names = [ + name.text + for name in newtree.findall( + f"./media/medium/{location_pointer[location]}properties/property/name" + ) + ] property_names = list(dict.fromkeys(property_names)) - values = {} + values: dict[str, list] = {} for name in property_names: values[name] = [] - orig_name = ''.join(c for c in name if not c.isnumeric()) - number_suffix = ''.join(c for c in name if c.isnumeric()) + orig_name = "".join(c for c in name if not c.isnumeric()) + number_suffix = "".join(c for c in name if c.isnumeric()) if orig_name in property_dict[location]: for medium_id in range(numofmedia): if medium_id in mediamapping: medium = self._get_medium_pointer(root, medium_id) - proptytype = medium.find(f"./{location_pointer[location]}properties/property[name='{name}']/type") + proptytype = medium.find( + f"./{location_pointer[location]}properties/property[name='{name}']/type" + ) if proptytype is None: - values[name].append(Value(mediamapping[medium_id],None)) + values[name].append( + Value(mediamapping[medium_id], None) + ) else: - if "Constant" == proptytype.text: - value_entry = medium.find(f"./{location_pointer[location]}properties/property[name='{name}']/value").text + if proptytype.text == "Constant": + value_entry = medium.find( + f"./{location_pointer[location]}properties/property[name='{name}']/value" + ).text value_entry_list = value_entry.split(" ") if len(value_entry_list) == 1: - values[name].append(Value(mediamapping[medium_id],float(value_entry))) + values[name].append( + Value( + mediamapping[medium_id], + float(value_entry), + ) + ) else: - values[name].append(Value(mediamapping[medium_id],None)) - if not number_suffix == "": - new_symbol = property_dict[location][orig_name]["symbol"][:-1]+"_"+number_suffix +"$" + values[name].append( + Value(mediamapping[medium_id], None) + ) + if number_suffix != "": + new_symbol = ( + property_dict[location][orig_name]["symbol"][:-1] + + "_" + + number_suffix + + "$" + ) else: - new_symbol = property_dict[location][orig_name]["symbol"] - property_list.append(Property(property_dict[location][orig_name]["title"], new_symbol, property_dict[location][orig_name]["unit"], values[name])) + new_symbol = property_dict[location][orig_name][ + "symbol" + ] + property_list.append( + Property( + property_dict[location][orig_name]["title"], + new_symbol, + property_dict[location][orig_name]["unit"], + values[name], + ) + ) properties = PropertySet(property=property_list) return pd.DataFrame(properties) - def write_property_latextable(self, latexfile="property_dataframe.tex", mediamapping=None, float_format="{:.2e}"): - with open(latexfile, "w") as tf: - tf.write(self.property_dataframe(mediamapping).to_latex(index=False, - float_format=float_format.format)) - - + def write_property_latextable( + self, + latexfile: Path = Path("property_dataframe.tex"), + mediamapping: dict[int, str] | None = None, + float_format: str = "{:.2e}", + ) -> None: + with latexfile.open("w") as tf: + tf.write( + self.property_dataframe(mediamapping).to_latex( + index=False, float_format=float_format.format + ) + ) diff --git a/tests/test_ogs6py.py b/tests/test_ogs6py.py index b25ee07..5c73db3 100644 --- a/tests/test_ogs6py.py +++ b/tests/test_ogs6py.py @@ -632,7 +632,7 @@ def test_replace_property_in_include(self): while chunk := f.read(8192): file_hash.update(chunk) self.assertEqual(file_hash.hexdigest(), 'a5ca3722007055f9c1672d1ffc8994f8') - with open("tests/solid_inc.xml", "rb") as f: + with open("solid_inc.xml", "rb") as f: file_hash = hashlib.md5() while chunk := f.read(8192): file_hash.update(chunk)