diff --git a/xml/convertRichardsFlowToMPL.py b/xml/convertRichardsFlowToMPL.py new file mode 100755 index 0000000000000000000000000000000000000000..188ca756c4cba9d98cbd27e63a1c030e14bb5090 --- /dev/null +++ b/xml/convertRichardsFlowToMPL.py @@ -0,0 +1,488 @@ +#!/usr/bin/env python + +from lxml import etree as ET +import re +import os +import sys + + +def listOfProjectFiles(path='.'): + result = [] + for root, dirs, files in os.walk(path): + for file in files: + if file.endswith(".prj"): + result.append(os.path.join(root, file)) + return result + + +def writeResult(filename, tree): + tree.write(open(filename, 'wb'), encoding=tree.docinfo.encoding, pretty_print=True, xml_declaration=True) +# res = "" +# with open(filename, 'r') as f: +# data = f.read() +# ### this regex query doubles the line indentation, for an indent of 4 +# ### [:-1] removes the last character bc lxml writes newline at the end +# data2 = re.sub(r"( {2,})<", r"\1\1<", data)[:-1] +# ### put empty elements on the same level +# res = re.sub(r"( +)<([\w?!/]+)>\s+</\2>", r"\1<\2>\n\1</\2>", data2) +# with open(filename, 'w') as f: +# print("""<?xml version="1.0" encoding="ISO-8859-1"?>""", file=f) +# print(res, file=f) + + +# Find Properties Element of phase with sought type +def findPropertiesOfPhase(medium, phase, porous_medium_id): + if phase == "Medium": + return medium.find("./properties") + phase_tree = medium.find("./phases/phase/[type='" + phase + "']") + if phase_tree is None: + return None + return phase_tree.find("./properties") + +def getMedia(root): + ###### Add Media Group if non existent + if (root.find("./media") == None): + print("constructing new media/medium id=\"0\"") + media = ET.XML("<media><medium id=\"0\"><phases>" + \ + "<phase><type>Gas</type><properties></properties></phase>" + \ + "<phase><type>AqueousLiquid</type><properties></properties></phase>" + \ + "<phase><type>Solid</type><properties></properties></phase>" + \ + "</phases><properties></properties></medium></media>") + + root.insert(3, media) + return root.find("./media") + +def getMedium(root, porous_medium_id): + media = getMedia(root) + # check if the porous medium definition with 'porous_medium_id' already exist + path="./medium[@id=\"" + str(porous_medium_id) + "\"]" + medium=media.xpath(path) + if (len(medium) == 0): + print("constructing new medium tag with id " + str(porous_medium_id)) + medium = ET.XML("<medium id=\"" + str(porous_medium_id) + "\"><phases>" + \ + "<phase><type>Gas</type><properties></properties></phase>" + \ + "<phase><type>AqueousLiquid</type><properties></properties></phase>" + \ + "<phase><type>Solid</type><properties></properties></phase>" + \ + "</phases><properties></properties></medium>") + media.insert(porous_medium_id+1, medium) + return media.xpath("./medium[@id=\"" + str(porous_medium_id) + "\"]")[0] + +def getPhaseProperties(root, phase, porous_medium_id): + medium = getMedium(root, porous_medium_id) + return findPropertiesOfPhase(medium, phase, porous_medium_id) + +def parameterUsesLocalCoordinateSystem(parameter): + use_coord_system = parameter.find("./use_local_coordinate_system") + if use_coord_system is None: + return False + return use_coord_system.text == "true" + +def readConstantValue(tree): + if parameterUsesLocalCoordinateSystem(tree): + return None # Keep parameters with local coord systems as they are. + + type = tree.find("./type").text + if type == "Constant": + if (value := tree.find("./value")) is not None: + return value.text + if (values := tree.find("./values")) is not None: + return values.text + return None + +def readConstantPorosityParameter(tree): + type = tree.find("./type").text + if type == "Constant": + if (porosity_parameter := tree.find("./porosity_parameter")) is not None: + return porosity_parameter.text + + return None + +def readConstantPermeabilityTensorEntries(tree): + type = tree.find("./type").text + if type == "Constant": + if (parameter := tree.find("./permeability_tensor_entries")) is not None: + return parameter.text + + return None + +def readDupuitPermeabilityTensorParameter(tree): + type = tree.find("./type").text + if type == "Dupuit": + if (parameter := tree.find("./permeability_tensor_entries")) is not None: + return parameter.text + + return None + +def addReferenceTemperature(root, porous_medium_id): + props = getPhaseProperties(root, 'Medium', porous_medium_id) + props.append(ET.XML("<property><name>reference_temperature</name><type>" + \ + "Constant</type><value>293.15</value>" + \ + "</property>")) + +def mplAppendConstant(mpl_properties, name, value): + mpl_properties.append(ET.XML("<property><name>" + name + "</name><type>" + \ + "Constant</type><value>" + value + "</value>" + \ + "</property>")) + +def mplAppendParameter(mpl_properties, tag_name, parameter_name): + mpl_properties.append(ET.XML("<property><name>" + tag_name + "</name><type>" + \ + "Parameter</type><parameter_name>" + parameter_name + \ + "</parameter_name></property>")) + +def mplAppendDupuitParameter(mpl_properties, tag_name, parameter_name): + mpl_properties.append(ET.XML("<property><name>" + tag_name + "</name><type>" + \ + "Dupuit</type><parameter_name>" + parameter_name + \ + "</parameter_name></property>")) + +def getVanGenuchtenSaturationValues(material_property): + SLR = material_property.find("./sr").text + SGR = "{0:.15g}".format(1 - float(material_property.find("./smax").text)) + exponent = material_property.find("./m").text + p0 = material_property.find("./pd").text + print("vanGenuchtenSaturation: IGNORING pc_max.") + return (SLR, SGR, exponent, p0) + +def getVanGenuchtenRelativePermeabilityValues(material_property): + SLR = material_property.find("./sr").text + SGR = "{0:.15g}".format(1 - float(material_property.find("./smax").text)) + exponent = material_property.find("./m").text + k_rel_min = material_property.find("./krel_min").text + return (SLR, SGR, exponent, k_rel_min) + +def mplAppendVanGenuchtenSaturation(mpl_properties, tag_name, SLR, SGR, exponent, p0): + assert tag_name == "saturation" + mpl_properties.append(ET.XML( + "<property>" + \ + "<name>" + tag_name + "</name>" + \ + "<type>SaturationVanGenuchten</type>" + \ + "<residual_liquid_saturation>" + SLR + "</residual_liquid_saturation>" + \ + "<residual_gas_saturation>" + SGR + "</residual_gas_saturation>" + \ + "<exponent>" + exponent + "</exponent>" + \ + "<entry_pressure>" + p0 + "</entry_pressure>" + \ + "</property>" \ + )) + +def mplAppendVanGenuchtenRelativePermeability(mpl_properties, tag_name, SLR, SGR, exponent, k_rel_min): + assert tag_name == "relative_permeability" + mpl_properties.append(ET.XML( + "<property>" + \ + "<name>" + tag_name + "</name>" + \ + "<type>RelativePermeabilityVanGenuchten</type>" + \ + "<residual_liquid_saturation>" + SLR + "</residual_liquid_saturation>" + \ + "<residual_gas_saturation>" + SGR + "</residual_gas_saturation>" + \ + "<exponent>" + exponent + "</exponent>" + \ + "<minimum_relative_permeability_liquid>" + k_rel_min + "</minimum_relative_permeability_liquid>" + \ + "</property>" \ + )) + +def removeXmlSubtree(tree): + tree.getparent().remove(tree) + +def replaceParameterWithMplProperty(root, paramName, phaseType, propName, porous_medium_id): + props = getPhaseProperties(root, phaseType, porous_medium_id) + assert props is not None + + #print("Process parameter", paramName) + + pName = root.find("./processes/process/" + paramName) + if pName == None: + print("Parameter", paramName, "not found") + return + + param = root.find("./parameters/parameter/[name='" + pName.text.strip() + "']") + assert param is not None, "Referenced parameter " + pName.text + " not found in parameters/." + pType = param.find("./type").text + + ###### Exchange old params for new MPL-style input + ###### if not a Constant parameter, old parameter stays and gets referenced + + if v := readConstantValue(param): + mplAppendConstant(props, propName, v) + removeXmlSubtree(param) + removeXmlSubtree(pName) + return + + props.append(ET.XML("<property><name>" + propName + "</name><type>" + \ + "Parameter</type><parameter_name>" + pName.text + \ + "</parameter_name></property>")) + pName.getparent().remove(pName) + + +def isCurveType(tree): + type = tree.find("./type") + if type is None: + return False + return type.text == "Curve" + +def isVanGenuchtenType(tree): + type = tree.find("./type") + return type is not None and \ + (type.text == "vanGenuchten" or type.text == "WettingPhaseVanGenuchten") + +def addMPLMaterialProperty(root, property_type, property_name, phase, + mpl_property_name, porous_medium_id): + props = getPhaseProperties(root, phase, porous_medium_id) + assert props is not None + + #print("Material property", property_type + "/" + property_name) + material_property = root.find("./processes/process/material_property/" + + property_type + "/" + property_name) + if material_property == None: + print("Material property", property_type + "/" + property_name, + "not found") + return + + if v := readConstantValue(material_property): + # add it to mpl, remove from material_property, and we are done. + mplAppendConstant(props, property_name, v) + removeXmlSubtree(material_property) + return + + if p := readConstantPorosityParameter(material_property): + # Find parameter + param = root.find("./parameters/parameter/[name='" + p + "']") + if v := readConstantValue(param): + mplAppendConstant(props, property_name, v) + removeXmlSubtree(material_property) + return + + if p := readConstantPermeabilityTensorEntries(material_property): + # Find parameter + param = root.find("./parameters/parameter/[name='" + p + "']") + if parameterUsesLocalCoordinateSystem(param): + # Keep parameters with local coord systems as they are. + mplAppendParameter(props, property_name, p) + if v := readConstantValue(param): + mplAppendConstant(props, property_name, v) + return + + if p := readDupuitPermeabilityTensorParameter(material_property): + # Find parameter + param = root.find("./parameters/parameter/[name='" + p + "']") + # Keep parameters for Dupuit permeability as they are. + mplAppendDupuitParameter(props, property_name, p) + return + + if (property_name == "relative_permeability" \ + or property_name == "capillary_pressure") \ + and isCurveType(material_property): + print("Replace curve parameter", property_name, "with constant", mpl_property_name) + mplAppendConstant(props, mpl_property_name, "1") # assume fully saturated + removeXmlSubtree(material_property) + return + + if property_name == "capillary_pressure" and isVanGenuchtenType(material_property): + values = getVanGenuchtenSaturationValues(material_property) + mplAppendVanGenuchtenSaturation(props, mpl_property_name, *values) + removeXmlSubtree(material_property) + return + + if property_name == "relative_permeability" and isVanGenuchtenType(material_property): + values = getVanGenuchtenRelativePermeabilityValues(material_property) + mplAppendVanGenuchtenRelativePermeability(props, mpl_property_name, *values) + removeXmlSubtree(material_property) + +def removeMaterialProperty(root, property_type, property_name, phase, + mpl_property_name, porous_medium_id): + props = getPhaseProperties(root, phase, porous_medium_id) + assert props is not None + + #print("Material property", property_type + "/" + property_name) + material_property = root.find("./processes/process/material_property/" + + property_type + "/" + property_name) + if material_property == None: + print("Material property", property_type + "/" + property_name, + "not found") + return + + if v := readConstantValue(material_property): + # add it to mpl, remove from material_property, and we are done. + removeXmlSubtree(material_property) + return + + if p := readConstantPorosityParameter(material_property): + # Find parameter + param = root.find("./parameters/parameter/[name='" + p + "']") + if v := readConstantValue(param): + removeXmlSubtree(material_property) + removeXmlSubtree(param) + return + + if p := readConstantPermeabilityTensorEntries(material_property): + # Find parameter + param = root.find("./parameters/parameter/[name='" + p + "']") + if parameterUsesLocalCoordinateSystem(param): + # Keep parameters with local coord systems as they are. + removeXmlSubtree(material_property) + if v := readConstantValue(param): + removeXmlSubtree(material_property) + removeXmlSubtree(param) + return + + if p := readDupuitPermeabilityTensorParameter(material_property): + removeXmlSubtree(material_property) + return + + if (property_name == "relative_permeability" \ + or property_name == "capillary_pressure") \ + and isCurveType(material_property): + removeXmlSubtree(material_property) + return + + if property_name == "capillary_pressure" and isVanGenuchtenType(material_property): + removeXmlSubtree(material_property) + return + + if property_name == "relative_permeability" and isVanGenuchtenType(material_property): + values = getVanGenuchtenRelativePermeabilityValues(material_property) + removeXmlSubtree(material_property) + +def addFluidPhaseMPLProperty(root, property_type, property_name, phase, + mpl_property_name, porous_medium_id): + props = getPhaseProperties(root, phase, porous_medium_id) + assert props is not None + + #print("Material property", property_type + "/" + property_name) + material_property = root.find("./processes/process/material_property/" + + property_type + "/" + property_name) + if material_property == None: + print("Material property", property_type + "/" + property_name, + "not found") + return + + if v := readConstantValue(material_property): + # add it to mpl. + mplAppendConstant(props, property_name, v) + return + +def removeFluidProperty(root, property_type, property_name, phase, + mpl_property_name, porous_medium_id): + props = getPhaseProperties(root, phase, porous_medium_id) + assert props is not None + + #print("Material property", property_type + "/" + property_name) + material_property = root.find("./processes/process/material_property/" + + property_type + "/" + property_name) + if material_property == None: + return + + if v := readConstantValue(material_property): + # remove property from the old xml structures + removeXmlSubtree(material_property) + return + +def recursevelyEmpty(tree): + #print("Recursively empty checking:", '<' + tree.tag + '>', "with text", tree.text) + if tree is None: + print("Recursively empty found got an non-existing tree.") + print("RETURN False") + return False # non-exisiting tree is not empty, s.t. it will not be deleted + if tree.text is not None: + print("Recursively empty found this non-empty part:", '<' + tree.tag + '>', "with text", tree.text) + print("RETURN False") + return False + all_recursively_empty = all(recursevelyEmpty(c) for c in tree.iterchildren()) + print("RETURN", all_recursively_empty) + return all_recursively_empty + +def cleanupEmptyMplPhases(tree, porous_medium_id): + for phase_name in ["Solid", "Gas", "AqueousLiquid"]: + properties = getPhaseProperties(tree, phase_name, porous_medium_id) + if recursevelyEmpty(properties): + # remove the phase if there are no properties + print("Remove phase w/o properties:", phase_name) + phase = properties.getparent() + removeXmlSubtree(phase) + +def removeRecursivelyEmptyTree(tree): + if tree is None: + return + assert recursevelyEmpty(tree), "tree <"+ tree.tag+ "> is not empty!" + removeXmlSubtree(tree) + +def countPorousMediumTags(tree): + return len(tree.xpath('//porous_medium/porous_medium')) + +def processFile(filename): + parser = ET.XMLParser(remove_blank_text=True) + tree = ET.parse(filename, parser) + + # (parameter name, phase, property) + process_parameter_table = { + # Solid + ("solid_bulk_modulus", "Solid", "bulk_modulus"), + ("reference_solid_density", "Solid", "reference_density"), + ("solid_density", "Solid", "density"), + ("biot_coefficient", "Solid", "biot_coefficient"), + ("linear_thermal_expansion_coefficient", "Solid", + "thermal_expansivity"), + # Medium + ("thermal_conductivity", "Medium", "thermal_conductivity"), + ("specific_heat_capacity", "Medium", "specific_heat_capacity"), + ("specific_heat_capacity", "Medium", "specific_heat_capacity"), + ("temperature", "Medium", "reference_temperature"), + ("hydraulic_conductivity", "Medium", "diffusion"), + # Liquid + ("fluid_bulk_modulus", "AqueousLiquid", "bulk_modulus"), + } + + liquid_property_table = { + ("fluid", "viscosity", "AqueousLiquid", "viscosity"), + ("fluid", "density", "AqueousLiquid", "density") + } + + material_property_table = { + ("porous_medium/porous_medium", "storage", "Solid", "storage"), + ("porous_medium/porous_medium", "permeability", "Medium", "permeability"), + ("porous_medium/porous_medium", "porosity", "Medium", "porosity"), + ("porous_medium/porous_medium", "capillary_pressure", "Medium", "saturation"), + ("porous_medium/porous_medium", "relative_permeability", "Medium", "relative_permeability") + } + + # count porous_medium tags + number_porous_medium_tags = countPorousMediumTags(tree.getroot()) + print('number of porous_medium tags: ' + str(number_porous_medium_tags)) + + for porous_medium_id in range(0, number_porous_medium_tags): + for entry in process_parameter_table: + replaceParameterWithMplProperty(tree.getroot(), *entry, porous_medium_id) + + for entry in material_property_table: + addMPLMaterialProperty(tree.getroot(), *entry, porous_medium_id) + + for entry in liquid_property_table: + addFluidPhaseMPLProperty(tree.getroot(), *entry, porous_medium_id) + + for porous_medium_id in range(0, number_porous_medium_tags): + #for entry in liquid_property_table: + # removeFluidProperty(tree.getroot(), *entry, porous_medium_id) + + #for entry in material_property_table: + # removeMaterialProperty(tree.getroot(), *entry, porous_medium_id) + + cleanupEmptyMplPhases(tree, porous_medium_id) + try: + print("trying to remove old material_property tree:") + #print(ET.tostring(tree.find("./processes/process/material_property"), + # pretty_print=True)) + removeRecursivelyEmptyTree(tree.find("./processes/process/material_property")) + except AssertionError: + print("The old material property subtree could not be removed.") + print("Project file will be invalid.") + + if number_porous_medium_tags == 0: + for entry in process_parameter_table: + replaceParameterWithMplProperty(tree.getroot(), *entry, 0) + addReferenceTemperature(tree.getroot(), 0) + cleanupEmptyMplPhases(tree, 0) + + writeResult(filename + ".new", tree) + + +if os.path.isfile(sys.argv[1]): + processFile(sys.argv[1]) + +if os.path.isdir(sys.argv[1]): + filenamelist = listOfProjectFiles(sys.argv[1]) + for filename in filenamelist: + processFile(filename)