Skip to content

Fix writing of NOX xml #360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ dev = [
"pytest",
"pytest-cov",
"testbook",
"vistools"
"vistools",
"xmltodict"
]

[project.urls]
Expand Down
4 changes: 2 additions & 2 deletions src/meshpy/four_c/header_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def set_header_static(
)

# Set the contents of the NOX xml file.
nox_xml = f"""
nox_xml_contents = f"""
<ParameterList name="Status Test">
<!-- Outer Status Test: This test is an OR combination of the structural convergence and the maximum number of iterations -->
<ParameterList name="Outer Status Test">
Expand Down Expand Up @@ -448,7 +448,7 @@ def set_header_static(
)

# Set the xml content in the input file.
input_file.nox_xml = nox_xml
input_file.nox_xml_contents = nox_xml_contents


def set_binning_strategy_section(
Expand Down
32 changes: 13 additions & 19 deletions src/meshpy/four_c/input_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
import sys as _sys
from datetime import datetime as _datetime
from pathlib import Path as _Path
from typing import Any as _Any
from typing import Dict as _Dict
from typing import List as _List
from typing import Optional as _Optional

from fourcipp.fourc_input import FourCInput as _FourCInput

Expand Down Expand Up @@ -142,9 +142,9 @@ def add(self, object_to_add, **kwargs):

def dump(
self,
input_file_path: _Path,
input_file_path: str | _Path,
*,
nox_xml_file: bool | str = False,
nox_xml_file: str | None = None,
add_header_default: bool = True,
add_header_information: bool = True,
add_footer_application_script: bool = True,
Expand All @@ -157,10 +157,9 @@ def dump(
input_file_path:
Path to the input file that should be created.
nox_xml_file:
If this is a string, the xml file will be created with this
name. If this is False, no xml file will be created. If this
is True, the xml file will be created with the name of the
input file with the extension ".xml".
If this is a string, the NOX xml file will be created with this
name. If this is None, the NOX xml file will be created with the
name of the input file with the extension ".nox.xml".
add_header_default:
Prepend the default MeshPy header comment to the input file.
add_header_information:
Expand All @@ -176,22 +175,17 @@ def dump(
Validate if the created input file is compatible with 4C with FourCIPP.
"""

# Make sure the given input file is a Path instance.
input_file_path = _Path(input_file_path)

if self.nox_xml_contents:
if nox_xml_file is False:
xml_file_name = "NOT_DEFINED"
elif nox_xml_file is True:
xml_file_name = _os.path.splitext(input_file_path)[0] + ".xml"
elif isinstance(nox_xml_file, str):
xml_file_name = nox_xml_file
else:
raise TypeError("nox_xml_file must be a string or a boolean value.")
if nox_xml_file is None:
nox_xml_file = input_file_path.name.split(".")[0] + ".nox.xml"

self.sections["STRUCT NOX/Status Test"] = {"XML File": xml_file_name}
self["STRUCT NOX/Status Test"] = {"XML File": nox_xml_file}

# Write the xml file to the disc.
with open(
_os.path.join(_os.path.dirname(input_file_path), xml_file_name), "w"
) as xml_file:
with open(input_file_path.parent / nox_xml_file, "w") as xml_file:
xml_file.write(self.nox_xml_contents)

# Add information header to the input file
Expand Down
9 changes: 7 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
import os
import shutil
import subprocess
from difflib import unified_diff
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Union

import numpy as np
import pytest
import vtk
import xmltodict
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from fourcipp.utils.dict_utils import compare_nested_dicts_or_lists
Expand Down Expand Up @@ -335,6 +335,10 @@ def get_raw_data(
elif isinstance(obj, Path) and obj.suffix == ".json":
return json.loads(get_string(obj))

elif isinstance(obj, Path) and obj.suffix == ".xml":
with open(obj, "r", encoding="utf-8") as f:
return xmltodict.parse(f.read())

else:
raise TypeError(f"The comparison for {type(obj)} is not yet implemented.")

Expand Down Expand Up @@ -381,12 +385,13 @@ def _assert_results_equal(
elif reference.suffix in [".vtk", ".vtu"]:
compare_vtk_files(reference, result, rtol, atol)
return
elif reference.suffix in [".yaml"]:
elif reference.suffix in [".yaml", ".xml"]:
reference_data = get_raw_data(reference)
result_data = get_raw_data(result)
compare_nested_dicts_or_lists_with_custom_compare(
reference_data, result_data, rtol, atol
)
return
else:
raise NotImplementedError(
f"Comparison is not yet implemented for {reference.suffix} files."
Expand Down
45 changes: 45 additions & 0 deletions tests/reference-files/test_header_functions_nox_xml.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<ParameterList name="Status Test">
<!-- Outer Status Test: This test is an OR combination of the structural convergence and the maximum number of iterations -->
<ParameterList name="Outer Status Test">
<Parameter name="Test Type" type="string" value="Combo"/>
<Parameter name="Combo Type" type="string" value="OR"/>
<!-- Structural convergence is an AND combination of the residuum and step update -->
<ParameterList name="Test 0">
<Parameter name="Test Type" type="string" value="Combo"/>
<Parameter name="Combo Type" type="string" value="AND"/>
<!-- BEGIN: Combo AND - Test 0: "NormF" -->
<ParameterList name="Test 0">
<Parameter name="Test Type" type="string" value="NormF"/>
<!-- NormF - Quantity 0: Check the right-hand-side norm of the structural quantities -->
<ParameterList name="Quantity 0">
<Parameter name="Quantity Type" type="string" value="Structure"/>
<Parameter name="Tolerance Type" type="string" value="Absolute"/>
<Parameter name="Tolerance" type="double" value="1e-05"/>
<Parameter name="Norm Type" type="string" value="Two Norm"/>
<Parameter name="Scale Type" type="string" value="Scaled"/>
</ParameterList>
</ParameterList>
<!-- END: Combo AND - Test 0: "NormF" -->
<!-- BEGIN: Combo AND - Test 1: "NormWRMS" -->
<ParameterList name="Test 1">
<Parameter name="Test Type" type="string" value="NormUpdate"/>
<!-- NormWRMS - Quantity 0: Check the increment of the structural displacements -->
<ParameterList name="Quantity 0">
<Parameter name="Quantity Type" type="string" value="Structure"/>
<Parameter name="Tolerance Type" type="string" value="Absolute"/>
<Parameter name="Tolerance" type="double" value="0.0001"/>
<Parameter name="Norm Type" type="string" value="Two Norm"/>
<Parameter name="Scale Type" type="string" value="Scaled"/>
</ParameterList>
</ParameterList>
<!-- END: Combo AND - Test 1: "NormWRMS" -->
</ParameterList>
<!-- END: Combo 0 - Test 0: "Combo" -->
<!-- BEGIN: Combo OR - Test 1: "MaxIters" -->
<ParameterList name="Test 1">
<Parameter name="Test Type" type="string" value="MaxIters"/>
<Parameter name="Maximum Iterations" type="int" value="20"/>
</ParameterList>
<!--END: "MaxIters" -->
</ParameterList>
</ParameterList>
33 changes: 33 additions & 0 deletions tests/test_header_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,36 @@ def test_header_functions_beam_interaction(

# Compare the output.
assert_results_equal(get_corresponding_reference_file_path(), input_file)


@pytest.mark.parametrize(
("nox_xml_file_kwarg", "xml_relative_path"),
[(None, "xml_test.nox.xml"), ("custom_name.xml", "custom_name.xml")],
)
def test_header_functions_nox_xml(
get_corresponding_reference_file_path,
assert_results_equal,
nox_xml_file_kwarg,
xml_relative_path,
tmp_path,
):
"""Test that the NOX xml is exported correctly."""

input_file = InputFile()
set_header_static(
input_file, total_time=1.0, n_steps=1, tol_increment=1e-4, tol_residuum=1e-5
)
input_file.dump(
tmp_path / "xml_test.4C.yaml",
nox_xml_file=nox_xml_file_kwarg,
add_footer_application_script=False,
)

# Check the xml path in the input file
assert input_file["STRUCT NOX/Status Test"]["XML File"] == xml_relative_path

# Check the created xml
assert_results_equal(
get_corresponding_reference_file_path(extension="xml"),
tmp_path / xml_relative_path,
)
Loading