Source code for configuration.backup.dict_to_xml_and_reverse

"""Contains functions to go from a dictionary representation of the crowd parameters to a XML representation and the reverse."""

# Copyright  2025  Institute of Light and Matter, CNRS UMR 5306, University Claude Bernard Lyon 1
# Contributors: Oscar DUFOUR, Maxime STAPELLE, Alexandre NICOLAS

# This software is a computer program designed to generate a realistic crowd from anthropometric data and
# simulate the mechanical interactions that occur within it and with obstacles.

# This software is governed by the CeCILL-B license under French law and abiding by the rules of distribution
# of free software.  You can  use, modify and/ or redistribute the software under the terms of the CeCILL-B
# license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".

# As a counterpart to the access to the source code and  rights to copy, modify and redistribute granted by
# the license, users are provided only with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited liability.

# In this respect, the user's attention is drawn to the risks associated with loading,  using,  modifying
# and/or developing or reproducing the software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also therefore means  that it is reserved
# for developers  and  experienced professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their requirements in conditions enabling
# the security of their systems and/or data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.

# The fact that you are presently reading this means that you have had knowledge of the CeCILL-B license and that
# you accept its terms.

import xml.etree.ElementTree as ET
from typing import Any
from xml.dom import minidom
from xml.dom.minidom import parseString

import numpy as np
from dicttoxml import dicttoxml

import configuration.utils.constants as cst
import configuration.utils.functions as fun
from configuration.utils.typing_custom import (
    DynamicCrowdDataType,
    GeometryDataType,
    InteractionsDataType,
    IntrinsicMaterialDataType,
    MaterialsDataType,
    PairMaterialsDataType,
    ShapeDataType,
    StaticCrowdDataType,
)


[docs] def save_light_agents_params_dict_to_xml(crowd_data_dict: StaticCrowdDataType) -> str: """ Generate a pretty-printed XML string of agent parameters from a dictionary. Parameters ---------- crowd_data_dict : StaticCrowdDataType A dictionary containing static agent data for all agents in the crowd. Returns ------- str A string representation of the dictionary. """ # Convert dictionary to XML string without type attributes xml_data = dicttoxml(crowd_data_dict, attr_type=False, root=False) # Parse the XML string into a DOM object dom = parseString(xml_data) # Pretty-print the XML with indentation and remove empty lines pretty_xml = dom.toprettyxml(indent=" ") data = "\n".join([line for line in pretty_xml.split("\n") if line.strip()]) return data
[docs] def static_dict_to_xml(crowd_dict: StaticCrowdDataType) -> bytes: """ Convert a static crowd dictionary to a prettified XML representation. Parameters ---------- crowd_dict : StaticCrowdDataType Dictionary with agent data. Returns ------- bytes UTF-8 encoded, pretty-printed XML representation of all agents' static parameters. """ # Create the root element <Agents> root = ET.Element("Agents") # Iterate through each agent in the dictionary for agent_data in crowd_dict["Agents"].values(): # Create an <Agent> element with attributes agent = ET.SubElement( root, "Agent", { "Type": agent_data["Type"], "Id": f"{agent_data['Id']}", "Mass": f"{agent_data['Mass']:.2f}", "Height": f"{agent_data['Height']:.2f}", "MomentOfInertia": f"{agent_data['MomentOfInertia']:.2f}", "FloorDamping": f"{agent_data['FloorDamping']:.2f}", "AngularDamping": f"{agent_data['AngularDamping']:.2f}", }, ) # Iterate through each shape in the agent's shapes for shape_data in agent_data["Shapes"].values(): # Create a <Shape> element with attributes ET.SubElement( agent, "Shape", { "Type": shape_data["Type"], "Radius": f"{shape_data['Radius']:.3f}", "MaterialId": f"{shape_data['MaterialId']}", "Position": f"{shape_data['Position'][0]:.3f},{shape_data['Position'][1]:.3f}", }, ) rough_string = ET.tostring(root, encoding="utf-8") reparsed = minidom.parseString(rough_string).toprettyxml(indent=" ", encoding="utf-8") return reparsed
[docs] def dynamic_dict_to_xml(dynamical_parameters_crowd: DynamicCrowdDataType) -> bytes: """ Convert a dictionary of agents' dynamic parameters to a prettified XML representation. Parameters ---------- dynamical_parameters_crowd : DynamicCrowdDataType Dictionary with agent data. Returns ------- bytes UTF-8 encoded, pretty-printed XML representation of all agents' dynamic parameters. """ # Create the root element root = ET.Element("Agents") # Iterate through agents in the dictionary for agent_data in dynamical_parameters_crowd["Agents"].values(): # Create an Agent element agent_element = ET.SubElement(root, "Agent", Id=f"{agent_data['Id']}") # Create Kinematics element kinematics_data = agent_data["Kinematics"] ET.SubElement( agent_element, "Kinematics", Position=f"{kinematics_data['Position'][0]:.3f},{kinematics_data['Position'][1]:.3f}", Velocity=f"{kinematics_data['Velocity'][0]:.2f},{kinematics_data['Velocity'][1]:.2f}", Theta=f"{kinematics_data['Theta']:.2f}", Omega=f"{kinematics_data['Omega']:.2f}", ) # Create Dynamics element dynamics_data = agent_data["Dynamics"] ET.SubElement( agent_element, "Dynamics", Fp=f"{dynamics_data['Fp'][0]:.2f},{dynamics_data['Fp'][1]:.2f}", Mp=f"{dynamics_data['Mp']:.2f}", ) # Convert the tree to a string xml_str = ET.tostring(root, encoding="utf-8") # Prettify the XML string pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ", encoding="utf-8") return pretty_xml_str
[docs] def geometry_dict_to_xml(boundaries_dict: GeometryDataType) -> bytes: """ Convert a dictionary of geometry data to a prettified XML representation. Parameters ---------- boundaries_dict : GeometryDataType Dictionary with boundary data. Returns ------- bytes UTF-8 encoded, pretty-printed XML representation of the boundaries. """ # Create the root element root = ET.Element("Geometry") # Add Dimensions element dimensions = boundaries_dict["Geometry"]["Dimensions"] ET.SubElement(root, "Dimensions", Lx=f"{dimensions['Lx']:.3f}", Ly=f"{dimensions['Ly']:.3f}") # Iterate over all walls in the dictionary for wall_data in boundaries_dict["Geometry"]["Wall"].values(): # Add Wall element wall_element = ET.SubElement(root, "Wall", Id=f"{wall_data['Id']}", MaterialId=f"{wall_data['MaterialId']}") # Add Corners element for corner_data in wall_data["Corners"].values(): ET.SubElement( wall_element, "Corner", Coordinates=f"{corner_data['Coordinates'][0]:.3f},{corner_data['Coordinates'][1]:.3f}" ) # Convert the tree to a string xml_str = ET.tostring(root, encoding="utf-8") # Prettify the XML string pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ", encoding="utf-8") return pretty_xml_str
[docs] def materials_dict_to_xml(material_dict: MaterialsDataType) -> bytes: """ Convert a dictionary of material properties to a prettified XML representation. Parameters ---------- material_dict : MaterialsDataType Dictionary with material data. Returns ------- bytes UTF-8 encoded, pretty-printed XML representation of the materials. """ # Create the root element root = ET.Element("Materials") # Add Intrinsic materials intrinsic_element = ET.SubElement(root, "Intrinsic") for material_data in material_dict["Materials"]["Intrinsic"].values(): ET.SubElement( intrinsic_element, "Material", Id=f"{material_data['Id']}", YoungModulus=f"{material_data['YoungModulus']:.2e}", ShearModulus=f"{material_data['ShearModulus']:.2e}", ) # Add Binary contacts binary_element = ET.SubElement(root, "Binary") for contact_data in material_dict["Materials"]["Binary"].values(): ET.SubElement( binary_element, "Contact", Id1=f"{contact_data['Id1']}", Id2=f"{contact_data['Id2']}", GammaNormal=f"{contact_data['GammaNormal']:.2e}", GammaTangential=f"{contact_data['GammaTangential']:.2e}", KineticFriction=f"{contact_data['KineticFriction']:.2f}", ) # Convert the tree to a string xml_str = ET.tostring(root, encoding="utf-8") # Prettify the XML string pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ", encoding="utf-8") return pretty_xml_str
[docs] def interactions_dict_to_xml(data: InteractionsDataType) -> bytes: """ Convert a dictionary of interactions data to a prettified XML representation. Parameters ---------- data : InteractionsDataType Dictionary with interactions data. Returns ------- bytes UTF-8 encoded, pretty-printed XML representation of all agents and their interactions. """ # Create the root element root = ET.Element("Interactions") # Iterate through agents in the dictionary for agent_data in data["Interactions"].values(): agent_element = ET.SubElement(root, "Agent", Id=f"{agent_data['Id']}") # Iterate through neighboring agents if "NeighbouringAgents" in agent_data: for neighbor_data in agent_data["NeighbouringAgents"].values(): neighbor_element = ET.SubElement(agent_element, "Agent", Id=f"{neighbor_data['Id']}") # Iterate through interactions for interaction_data in neighbor_data["Interactions"].values(): ET.SubElement( neighbor_element, "Interaction", ParentShape=f"{interaction_data['ParentShape']}", ChildShape=f"{interaction_data['ChildShape']}", TangentialRelativeDisplacement=f"{interaction_data['TangentialRelativeDisplacement'][0]:.2f},{interaction_data['TangentialRelativeDisplacement'][1]:.2f}", Fn=f"{interaction_data['Fn'][0]:.2f},{interaction_data['Fn'][1]:.2f}", Ft=f"{interaction_data['Ft'][0]:.2f},{interaction_data['Ft'][1]:.2f}", ) # Convert the tree to a string xml_str = ET.tostring(root, encoding="utf-8") # Prettify the XML string pretty_xml_str = minidom.parseString(xml_str).toprettyxml(indent=" ", encoding="utf-8") return pretty_xml_str
[docs] def static_xml_to_dict(xml_file: str) -> StaticCrowdDataType: """ Convert an XML string representing agents' static data into a dictionary. Parameters ---------- xml_file : str XML data as a string. Returns ------- StaticCrowdDataType A dictionary representation of the XML data. Raises ------ ValueError If the XML structure or attribute types are incorrect. """ try: root = ET.fromstring(xml_file) except ET.ParseError as e: raise ValueError(f"Malformed XML: {e}") from e crowd_dict: StaticCrowdDataType = {"Agents": {}} for agent_idx, agent in enumerate(root.findall("Agent")): # Validate required agent attributes try: agent_data: dict[str, Any] = { "Type": agent.attrib["Type"], "Id": int(agent.attrib["Id"]), "Mass": float(agent.attrib["Mass"]), "Height": float(agent.attrib["Height"]), "MomentOfInertia": float(agent.attrib["MomentOfInertia"]), "FloorDamping": float(agent.attrib["FloorDamping"]), "AngularDamping": float(agent.attrib["AngularDamping"]), } except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Agent> at position {agent_idx}.") from e except ValueError as e: raise ValueError(f"Type error in <Agent> at position {agent_idx}: {e}") from e # Process <Shapes> if present shapes_dict: ShapeDataType = {} for shape_idx, shape in enumerate(agent.findall("Shape")): # Validate required shape attributes try: shape_data = { "Type": shape.attrib["Type"], "Radius": float(shape.attrib["Radius"]), "MaterialId": str(shape.attrib["MaterialId"]), "Position": fun.from_string_to_tuple(shape.attrib["Position"]), } except KeyError as e: raise ValueError( f"Missing '{e.args[0]}' attribute in <Shape> at position {shape_idx} under <Agent> with Id={agent_data['Id']}." ) from e except ValueError as e: raise ValueError(f"Type error in <Shape> at position {shape_idx} under <Agent> with Id={agent_data['Id']}: {e}") from e # Assign a unique name to each shape (e.g., disk0, disk1, ...) shape_name = f"disk{shape_idx}" shapes_dict[shape_name] = shape_data agent_data["Shapes"] = shapes_dict # Assign a unique name to each agent (e.g., Agent0, Agent1, ...) agent_name = f"Agent{agent_idx}" crowd_dict["Agents"][agent_name] = agent_data return crowd_dict
[docs] def dynamic_xml_to_dict(xml_data: str) -> DynamicCrowdDataType: """ Convert an XML string representing agents' dynamic parameters into a dictionary. Parameters ---------- xml_data : str A string containing XML data. Returns ------- DynamicCrowdDataType A dictionary representation of the XML data. Raises ------ ValueError If the XML structure or attribute types are incorrect. """ try: root = ET.fromstring(xml_data) except ET.ParseError as e: raise ValueError(f"Malformed XML: {e}") from e agents: DynamicCrowdDataType = {} for agent_idx, agent in enumerate(root.findall("Agent")): # Validate agent Id try: agent_id = int(agent.attrib["Id"]) except KeyError as e: raise ValueError(f"Missing 'Id' attribute in <Agent> at position {agent_idx}.") from e except ValueError as e: raise ValueError(f"Invalid 'Id' value in <Agent> at position {agent_idx}: {e}") from e # Extract and validate kinematics kinematics = agent.find("Kinematics") if kinematics is None: raise ValueError(f"Missing <Kinematics> section for <Agent> with Id={agent_id}.") try: position_str = kinematics.attrib["Position"] velocity_str = kinematics.attrib["Velocity"] theta_str = kinematics.attrib["Theta"] omega_str = kinematics.attrib["Omega"] except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Kinematics> for <Agent> with Id={agent_id}.") from e try: kinematics_dict = { "Position": fun.from_string_to_tuple(position_str), "Velocity": fun.from_string_to_tuple(velocity_str), "Theta": float(theta_str), "Omega": float(omega_str), } except ValueError as e: raise ValueError(f"Type error in <Kinematics> for <Agent> with Id={agent_id}: {e}") from e # Extract and validate dynamics parameters dynamics = agent.find("Dynamics") fp_str_default = ( f"{float(np.round(cst.DECISIONAL_TRANSLATIONAL_FORCE_X, 2))},{float(np.round(cst.DECISIONAL_TRANSLATIONAL_FORCE_Y, 2))}" ) mp_str_default = f"{float(np.round(cst.DECISIONAL_TORQUE, 2))}" if dynamics is not None: # Get the 'Fp' and 'Mp' attributes, or use defaults if not present fp_str = dynamics.attrib.get("Fp", fp_str_default) mp_str = dynamics.attrib.get("Mp", mp_str_default) else: # Use default values if 'Dynamics' element is missing fp_str = fp_str_default mp_str = mp_str_default dynamics_dict = { "Fp": fun.from_string_to_tuple(fp_str), "Mp": float(mp_str), } # Combine into agent dictionary agents[f"Agent{agent_id}"] = { "Id": agent_id, "Kinematics": kinematics_dict, "Dynamics": dynamics_dict, } # Construct the final dictionary dynamical_parameters_crowd = {"Agents": agents} return dynamical_parameters_crowd
[docs] def geometry_xml_to_dict(xml_data: str) -> GeometryDataType: """ Convert an XML string representing geometric data into a dictionary. Parameters ---------- xml_data : str A string containing XML data. Returns ------- GeometryDataType A dictionary representation of the XML data. Raises ------ ValueError If the XML structure or attribute types are incorrect. """ try: root = ET.fromstring(xml_data) except ET.ParseError as e: raise ValueError(f"Malformed XML: {e}") from e # --- Extract and validate dimensions --- dimensions = root.find("Dimensions") if dimensions is None: raise ValueError("Missing required <Dimensions> section in XML.") try: dimensions_dict = {"Lx": float(dimensions.attrib["Lx"]), "Ly": float(dimensions.attrib["Ly"])} except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Dimensions>.") from e except ValueError as e: raise ValueError(f"Type error in <Dimensions>: {e}") from e # --- Extract and validate walls and corners --- walls: dict[str, Any] = {} for wall_idx, wall in enumerate(root.findall("Wall")): # Validate required wall attributes try: wall_id = int(wall.attrib["Id"]) id_material = str(wall.attrib["MaterialId"]) except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Wall> at position {wall_idx}.") from e except ValueError as e: raise ValueError(f"Type error in <Wall> at position {wall_idx}: {e}") from e # Validate corners section corners: dict[str, dict[str, tuple[float, float]]] = {} for i, corner in enumerate(wall.findall("Corner")): try: coords = fun.from_string_to_tuple(corner.attrib["Coordinates"]) except KeyError as e: raise ValueError(f"Missing 'Coordinates' attribute in <Corner> at position {i} for <Wall> with Id={wall_id}.") from e except ValueError as e: raise ValueError(f"Type error in 'Coordinates' of <Corner> at position {i} for <Wall> with Id={wall_id}: {e}") from e corners[f"Corner{i}"] = {"Coordinates": coords} walls[f"Wall{wall_id}"] = { "Id": wall_id, "MaterialId": id_material, "Corners": corners, } # --- Construct the final dictionary --- boundaries_dict = {"Geometry": {"Dimensions": dimensions_dict, "Wall": walls}} return boundaries_dict
[docs] def materials_xml_to_dict(xml_data: str) -> MaterialsDataType: """ Convert an XML string representing material properties into a dictionary. Parameters ---------- xml_data : str A string containing XML data. Returns ------- MaterialsDataType A dictionary representation of the XML data. Raises ------ ValueError If the XML structure or attribute types are incorrect. """ try: root = ET.fromstring(xml_data) except ET.ParseError as e: raise ValueError(f"Malformed XML: {e}") from e # --- Validate and extract Intrinsic materials --- intrinsic_element = root.find("Intrinsic") if intrinsic_element is None: raise ValueError("Missing required <Intrinsic> section in XML.") intrinsic_materials: IntrinsicMaterialDataType = [] for idx, material in enumerate(intrinsic_element): try: instrinsic_material_dict = { "Id": str(material.attrib["Id"]), "YoungModulus": float(material.attrib["YoungModulus"]), "ShearModulus": float(material.attrib["ShearModulus"]), } except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Material> at position {idx}.") from e except ValueError as e: raise ValueError(f"Type error in <Material> at position {idx}: {e}") from e intrinsic_materials.append(instrinsic_material_dict) # --- Validate and extract Binary contacts --- binary_element = root.find("Binary") if binary_element is None: raise ValueError("Missing required <Binary> section in XML.") binary_contacts: PairMaterialsDataType = [] for idx, contact in enumerate(binary_element): try: contact_dict = { "Id1": str(contact.attrib["Id1"]), "Id2": str(contact.attrib["Id2"]), "GammaNormal": float(contact.attrib["GammaNormal"]), "GammaTangential": float(contact.attrib["GammaTangential"]), "KineticFriction": float(contact.attrib["KineticFriction"]), } except KeyError as e: raise ValueError(f"Missing '{e.args[0]}' attribute in <Contact> at position {idx}.") from e except ValueError as e: raise ValueError(f"Type error in <Contact> at position {idx}: {e}") from e binary_contacts.append(contact_dict) # --- Assemble the dictionary --- material_dict: MaterialsDataType = { "Materials": { "Intrinsic": {f"Material{id_material}": material for id_material, material in enumerate(intrinsic_materials)}, "Binary": {f"Contact{i}": contact for i, contact in enumerate(binary_contacts)}, } } return material_dict
[docs] def interactions_xml_to_dict(xml_data: str) -> InteractionsDataType: """ Convert an XML string describing interactions between agents and with boundaries into a dictionary. Parameters ---------- xml_data : str A string containing XML data. Returns ------- InteractionsDataType A dictionary representation of the XML data. Raises ------ ValueError If the XML structure or attribute types are incorrect. """ try: root = ET.fromstring(xml_data) except ET.ParseError as e: raise ValueError(f"Malformed XML: {e}") from e interactions_dict: InteractionsDataType = {"Interactions": {}} # Iterate through all Agent elements in the XML for agent_idx, agent in enumerate(root.findall("Agent")): # Validate required attribute if "Id" not in agent.attrib: raise ValueError(f"Missing 'Id' attribute in <Agent> at position {agent_idx}.") try: agent_id = int(agent.attrib["Id"]) except ValueError as e: raise ValueError( f"Invalid 'Id' value in <Agent> at position {agent_idx}: '{agent.attrib['Id']}' is not an integer." ) from e agent_key = f"Agent{agent_id}" interactions_dict["Interactions"][agent_key] = {"Id": agent_id, "NeighbouringAgents": {}} # Iterate through neighboring agents for neighbor_idx, neighbor_agent in enumerate(agent.findall("Agent")): if "Id" not in neighbor_agent.attrib: raise ValueError(f"Missing 'Id' attribute in <Agent> (neighbor) at position {neighbor_idx} under Agent {agent_id}.") try: neighbor_id = int(neighbor_agent.attrib["Id"]) except ValueError as e: raise ValueError( f"Invalid 'Id' value in <Agent> (neighbor) at position {neighbor_idx} under Agent {agent_id}: " "'{neighbor_agent.attrib['Id']}' is not an integer." ) from e neighbor_key = f"Agent{neighbor_id}" interactions_dict["Interactions"][agent_key]["NeighbouringAgents"][neighbor_key] = {"Id": neighbor_id, "Interactions": {}} # Iterate through interactions for interaction_idx, interaction in enumerate(neighbor_agent.findall("Interaction")): required_attrs = ["ParentShape", "ChildShape", "TangentialRelativeDisplacement", "Fn", "Ft"] for attr in required_attrs: if attr not in interaction.attrib: raise ValueError( f"Missing '{attr}' attribute in <Interaction> at position {interaction_idx} " f"under Neighbor Agent {neighbor_id} of Agent {agent_id}." ) try: parent_shape_id = int(interaction.attrib["ParentShape"]) child_shape_id = int(interaction.attrib["ChildShape"]) ft = fun.from_string_to_tuple(interaction.attrib["Ft"]) fn = fun.from_string_to_tuple(interaction.attrib["Fn"]) tangential_rel_displacement = fun.from_string_to_tuple(interaction.attrib["TangentialRelativeDisplacement"]) except ValueError as e: raise ValueError( f"Type error in <Interaction> at position {interaction_idx} " f"under Neighbor Agent {neighbor_id} of Agent {agent_id}: {e}" ) from e interaction_key = f"Interaction_{parent_shape_id}_{child_shape_id}" interactions_dict["Interactions"][agent_key]["NeighbouringAgents"][neighbor_key]["Interactions"][interaction_key] = { "ParentShape": parent_shape_id, "ChildShape": child_shape_id, "TangentialRelativeDisplacement": tangential_rel_displacement, "Fn": fn, "Ft": ft, } return interactions_dict