Skip to content

Discuss new InputFile interface for 4C YAML #304

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

Open
isteinbrecher opened this issue Apr 3, 2025 · 8 comments
Open

Discuss new InputFile interface for 4C YAML #304

isteinbrecher opened this issue Apr 3, 2025 · 8 comments
Assignees

Comments

@isteinbrecher
Copy link
Collaborator

With #135 we will switch to YAML. This will mostly result in a complete rework of the current InputFile class. Let's discuss the interface of the new InputFile (or whatever name it will have) class here, as this will be one of the main points that will change for users.

Currently the interface looks something like this:

input_file = InputFile()

material = MaterialReissner()
input_file.add(material)

create_beam_mesh_line(input_file, beam_type, material, p1, p2)

mesh = Mesh()
create_beam_mesh_line(mesh, beam_type, material, p3, p4)

input_file.add(mesh)

input_file.add("""
--------------------------------------------------------------STRUCTURAL DYNAMIC
DYNAMICTYPE                           Statics
TIMESTEP                              0.05
NUMSTEP                               20
""")

input_file.write_input_file(path)

I think this general approach still makes sense, but I am open to any changes that can improve the code design behind everything and/or the usability.

Some thoughts about a possible new InputFile:

  • I think we only need the following class variables in the input file:
    class InputFile:
        def __init__(self, ...):
            self.yaml = {} # either a pure dictionary or a FourCIPP object
            self.mesh = Mesh()
            self.nox_xml = None
            self._nox_xml_file = None
    
  • If the InputFile does not derive from Mesh any more, we would have to change the first create_beam_mesh_line call in the example to:
    - create_beam_mesh_line(input_file, beam_type, material, p1, p2)
    + create_beam_mesh_line(input_file.mesh, beam_type, material, p1, p2)
    I am very much in favour of this change.
  • What do we want to do about the .add methods? We at least need the options to add parameters and meshes directly to the InputFile:
    input_file.add(mesh)
    input_file.add({
        "STRUCTURAL DYNAMIC": {
            "DYNAMICTYPE": "Statics",
            "TIMESTEP": 0.05,
            "NUMSTEP": 20
        }
    }
    Do we want to also be able to add other objects, e.g., boundary conditions, functions, ...? I would be fine to pipe all additions of non-dictionaries to input_file.mesh.add, but my opinion on this is not strong.
@isteinbrecher isteinbrecher self-assigned this Apr 3, 2025
@davidrudlstorfer
Copy link
Collaborator

Thanks for the nice overview!

One thing I'd like to discuss is if we should treat the input file like a "discretization" (or some better word for it) again.

What do you think if we can simply add the materials, bc's etc to the mesh and in the end the input file is only able to "translate" a mesh into the respective input file.

We could then use it like this:

mesh = InputFile("path_to_existing_input_file.4C.yml")

mesh.add(material)
mesh.add(boundarycondition)
mesh.add(whatever)

input_file = InputFile(mesh)
input_file.validate()
input_file.write("path_to_new_input_file.4C.yml")
input_file.write("path_to_vtu_file.vtu")
input_file.write("path_to_new_abaqus_file")

(And if you directly want to add a dictionary - you could add it with some sort of custom action

input_file.add_custom_4C_section({"STRUCTURAL_DYNAMIC": ...})

)

@isteinbrecher
Copy link
Collaborator Author

I am not sure what exactly you mean with "treat the input file like a "discretization". The Mesh object can be considered as a discretisation. In my understanding the InputFile combines the information of the discretisation with the parameters not directly related to the mesh.

I don't think it is desirable to create a base InputFile for all solvers, e.g., Abaqus, 4C, as there is not a lot of common functionality required for the individual cases (if there is, I would put that into free functions like for example get_coupled_nodes_to_master_map).

In your case would the InputFile derive from some class and what would the class variables be like?

Is there a reason you want to to this

input_file.add_custom_4C_section({"STRUCTURAL_DYNAMIC": ...})

instead of

input_file.add({"STRUCTURAL_DYNAMIC": ...})

@isteinbrecher
Copy link
Collaborator Author

isteinbrecher commented Apr 3, 2025

Would it make sense to simply consider the example from the description have a look how we want it to look in the future?

Alternative option: We could leave everything as it current is, and we replace simply replace one class variable in the input file:

    class InputFile(_Mesh):
        """An item that represents a complete 4C input file."""

        def __init__(self, *, description=None, dat_file=None, cubit=None):
            """Initialize the input file.
            """

            super().__init__()

            self.description = description
            self.dat_header = []

            # In case we import a 4C dat file with plain string data, we store them in these lists,
            # they do not interfere with other operations.
            self.dat_nodes = []
            self.dat_elements = []
            self.dat_elements_fluid = []
            self.dat_geometry_sets = _GeometrySetContainer()
            self.dat_boundary_conditions = _BoundaryConditionContainer()

            # Contents of NOX xml file.
            self.nox_xml = None
            self._nox_xml_file = None

            # Dictionaries for sections other than mesh sections.
-           self.sections = dict()
+           self.sections = ... # dict or some sort of yaml class

            # Flag if dat file was loaded.
            self._dat_file_loaded = False

            # Load existing dat files. If both of the following if statements are
            # true, an error will be thrown.
            if dat_file is not None:
                self.read_dat(dat_file)
            if cubit is not None:
                self._read_dat_lines(cubit.get_dat_lines())

and then we try to reproduce the current functionality, only with YAML under the hood instead of the self written classes. Mayor functional refactoring of InputFile could be done afterwards (or on the fly). Maybe then we will also see some requirement that we currently don't think of.

@davidrudlstorfer
Copy link
Collaborator

I am not sure what exactly you mean with "treat the input file like a "discretization". The Mesh object can be considered as a discretisation. In my understanding the InputFile combines the information of the discretisation with the parameters not directly related to the mesh.

Yes that sounds better - I initially thought that we could add all the information to the Mesh object. Giving it some thought this information should definetely be in the InputFile and not the Mesh.

I don't think it is desirable to create a base InputFile for all solvers, e.g., Abaqus, 4C, as there is not a lot of common functionality required for the individual cases (if there is, I would put that into free functions like for example get_coupled_nodes_to_master_map).

Also sounds good

In your case would the InputFile derive from some class and what would the class variables be like?

I think the InputFile should not derive from any class - and the class variables you proposed sound very good.

Is there a reason you want to to this

input_file.add_custom_4C_section({"STRUCTURAL_DYNAMIC": ...})

instead of

input_file.add({"STRUCTURAL_DYNAMIC": ...})

Here I also would agree with your proposal - a simple add method will be easier from a user perspective. Initially I thought we could split it up to better separate the implementation details but that could also be done similar to our testing framework where we have one general function which then calls all the separate functions.

Alternative option: We could leave everything as it current is, and we replace simply replace one class variable in the input file:
...
and then we try to reproduce the current functionality, only with YAML under the hood instead of the self written classes. Mayor functional refactoring of InputFile could be done afterwards (or on the fly). Maybe then we will also see some requirement that we currently don't think of.

I would not go with that approach - in the long run this will take much more time and if we start from scratch right now we have a better overview and structure. The code of the original implementation is not lost - we still have a functioning main and git history if we decide on switching back.

Thanks for the good input - let's discuss the remaining stuff in the meeting:)

@davidrudlstorfer
Copy link
Collaborator

@isteinbrecher this afternoon I looked into this conversion and I have a very general question (we can document it here and discuss it in our next meeting)

  • why do we even have a mesh within the input file class?

After looking into the entire logic I would propose the following simplification which would really slim down the code and also simplify the interaction with it from a user perspective

  1. Create a class/function which converts an existing input file into a MeshPy Mesh() and also returns the remaining header as a simple dict, i.e.,
header, mesh = convert_4C_input_file_to_meshpy_mesh("example.4C.yaml")

Then one can easily do everything with the mesh and the header and once the work is done convert everything into a FourC Input File again in point 2

  1. Create the new InputFile() which is basically the same as the fourcipp FourCInput. This way we completely get rid of the weird connection between the InputFile and the Mesh
input_file = InputFile()
input_file.add(header) # add the header we previously exported from our existing file
input_file.add(mesh) # which internally converts to the input dict (similar to get_dict_to_dump)
input_file.add({"IO": {"dummy": "test"}}

I think we can simply omit the option to convert our InputFile to a MeshPy Mesh - because with fourcipp we can interpret all of the 4C input. Just convert everything that is doable with MeshPy to a MeshPy mesh - and the rest stays within the dict/header.

With this approach we could really simplify the logic of the input file. This is closely related to #321. With this change we would need an approach to compare two meshes in MeshPy for testing - but we should really look into that.

@isteinbrecher
Copy link
Collaborator Author

Thanks for your thoughts on this!

  • why do we even have a mesh within the input file class?

Historically, we used to read .dat files and populate the self.nodes, self.elements, etc., lists in the InputFile regardless of whether we did a full import or not. Even when we were just reading in plain strings without converting them into MeshPy objects, we effectively stored BaseMeshItem instances (which wrapped the raw strings) in the mesh item lists of the InputFile.

In that sense, InputFile was tightly coupled to the Mesh data structure. But with the recent refactoring, that coupling has been removed (which is a positive change that I actually didn't envision while doing the refactoring). We now have a much clearer distinction between actual MeshPy objects and plain dictionaries that happen to contain nodes or elements, but aren’t part of a "true" mesh structure.

Let's split them up!

header, mesh = convert_4C_input_file_to_meshpy_mesh("example.4C.yaml")

input_file = InputFile()
input_file.add(header) # add the header we previously exported from our existing file
input_file.add(mesh) # which internally converts to the input dict (similar to get_dict_to_dump)
input_file.add({"IO": {"dummy": "test"}}

I really like the general idea of that! One thing we should discuss, how much logic do we want to put into the proposed InputFile? I think it makes sense to have the add method that can also check for overwritten options, and some other utility functionality - we can talk about that today.

I think we can simply omit the option to convert our InputFile to a MeshPy Mesh - because with fourcipp we can interpret all of the 4C input. Just convert everything that is doable with MeshPy to a MeshPy mesh - and the rest stays within the dict/header.

I don't quite get that comment.

With this approach we could really simplify the logic of the input file. This is closely related to #321. With this change we would need an approach to compare two meshes in MeshPy for testing - but we should really look into that.

I agree. This is also closely related to modularisation #250. I have some ideas how we can do this in testing, let's discuss this today.

@isteinbrecher
Copy link
Collaborator Author

isteinbrecher commented Apr 16, 2025

A short recap of the discussion:

  • InputFile contains dictionaries describing simulations in more or less standard python types. InputFile should not derive from or contain a true Mesh. InputFile can contain meshes dumped to a dict - but they are treated like any other parameter and don’t have any functionality.
  • Mesh objects can be dumped to an InputFile
  • There will be a way to either read an existing mesh as pure parameters, or convert the mesh entries to a true MeshPy Mesh
  • Dumping to files will be exclusively done by fourcipp
  • In test_meshpy.py we want to test functionality related to meshpy, not related to 4C. Therefore, no 4C inputs shall be used there (not possible eight away but will improve with modularization). Comparison of the meshes will be done by converting them to a „4C dictionary“, but that will take place under the hood, the developers can simply use assert_results_equal(mesh1, mesh2)

@davidrudlstorfer please correct/add anything that I might have missed.

If anyone else has comments though, feel free to share them.

@davidrudlstorfer
Copy link
Collaborator

@isteinbrecher sounds good and I have nothing to add:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants