Repository: Fusion4Energy/F4Enix

Issue:
[BUG] - Wrong material card generation from mixture
**Describe the bug**
Header of submaterial in generate_material() not printed correctly when the mixture is generated by two materials previously defined by the user. A None is printed at the beginning of the line, without a C in front, which is interpreted by mcnp as a card. Moreover, the header is not printed correctly, a \n character is missing

![Image](https://github.com/user-attachments/assets/183dcaae-1dfa-4136-b033-f19676fe6309)



**To Reproduce**
```from f4enix.input.MCNPinput import Input
from f4enix.input.materials import Material
from f4enix.input.libmanager import LibManager
libman = LibManager()
inp = Input.from_input("issue_inp.txt")
zaids = [('1001', -100), ('B-0', -200), ('C-12', -50)]
newmat = Material.from_zaids(zaids, libman, '31c', 'B4CH2\n', 302)
inp.materials.append(newmat)
zaids = [('1001', 2), ('O-0', 1)]
newmat = Material.from_zaids(zaids, libman, '31c', 'Water\n', 400)
inp.materials.append(newmat)
newmat = inp.materials.generate_material(['M302', 'M400'],
                                           [0.2, 0.8],
                                           '31c', libman,
                                           fractiontype='atom',
                                           mat_name = "M500")
print(newmat.to_text())
```

**Expected behavior**
Correct material card generation

[issue_inp.txt](https://github.com/user-attachments/files/19336841/issue_inp.txt)

**Desktop (please complete the following information):**
 - OS: Windows
 - F4Enix version: 13.1
 - Python version: 3.10.16

Discussion:
@dodu94: I reviewed this better, it is not a bug. When you are creating a material, in addition to the material ID, you can also specify the material name. If you do  not specify it, it defaults to material 1 or something similar. If you specify the material names, you should observe the expected behaviour 

Solution patch:
diff --git a/docs/source/examples/output/jupyters/mcnp_out.ipynb b/docs/source/examples/output/jupyters/mcnp_out.ipynb
index 2abaac7..45295c5 100644
--- a/docs/source/examples/output/jupyters/mcnp_out.ipynb
+++ b/docs/source/examples/output/jupyters/mcnp_out.ipynb
@@ -23,7 +23,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 2,
    "metadata": {},
    "outputs": [
     {
@@ -32,7 +32,7 @@
        "'1.30E+06'"
       ]
      },
-     "execution_count": 1,
+     "execution_count": 2,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -50,7 +50,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 3,
    "metadata": {},
    "outputs": [
     {
@@ -59,7 +59,7 @@
        "'6.2'"
       ]
      },
-     "execution_count": 2,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -71,7 +71,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -337,7 +337,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -374,7 +374,7 @@
        "      <th>Other TFC bins</th>\n",
        "    </tr>\n",
        "    <tr>\n",
-       "      <th>Cell</th>\n",
+       "      <th>Tally</th>\n",
        "      <th></th>\n",
        "      <th></th>\n",
        "      <th></th>\n",
@@ -716,86 +716,86 @@
        "</div>"
       ],
       "text/plain": [
-       "     mean behaviour rel error value rel error decrease  \\\n",
-       "Cell                                                     \n",
-       "4               yes             yes                yes   \n",
-       "6               yes             yes                yes   \n",
-       "14              yes             yes                yes   \n",
-       "16              yes             yes                yes   \n",
-       "24              yes             yes                yes   \n",
-       "26              yes             yes                yes   \n",
-       "34              yes             yes                yes   \n",
-       "44              yes             yes                yes   \n",
-       "54              yes             yes                yes   \n",
-       "64              yes             yes                yes   \n",
-       "74              yes             yes                yes   \n",
-       "84              yes             yes                yes   \n",
-       "94              yes             yes                yes   \n",
-       "104             yes             yes                yes   \n",
-       "114             yes             yes                yes   \n",
-       "124             yes             yes                yes   \n",
-       "134             yes             yes                yes   \n",
-       "144             yes             yes                yes   \n",
-       "154             yes             yes                yes   \n",
-       "164             yes             yes                yes   \n",
-       "174             yes             yes                yes   \n",
-       "204             yes             yes                yes   \n",
-       "214             yes             yes                yes   \n",
+       "      mean behaviour rel error value rel error decrease  \\\n",
+       "Tally                                                     \n",
+       "4                yes             yes                yes   \n",
+       "6                yes             yes                yes   \n",
+       "14               yes             yes                yes   \n",
+       "16               yes             yes                yes   \n",
+       "24               yes             yes                yes   \n",
+       "26               yes             yes                yes   \n",
+       "34               yes             yes                yes   \n",
+       "44               yes             yes                yes   \n",
+       "54               yes             yes                yes   \n",
+       "64               yes             yes                yes   \n",
+       "74               yes             yes                yes   \n",
+       "84               yes             yes                yes   \n",
+       "94               yes             yes                yes   \n",
+       "104              yes             yes                yes   \n",
+       "114              yes             yes                yes   \n",
+       "124              yes             yes                yes   \n",
+       "134              yes             yes                yes   \n",
+       "144              yes             yes                yes   \n",
+       "154              yes             yes                yes   \n",
+       "164              yes             yes                yes   \n",
+       "174              yes             yes                yes   \n",
+       "204              yes             yes                yes   \n",
+       "214              yes             yes                yes   \n",
        "\n",
-       "     rel error decrease rate VoV value VoV decrease VoV decrease rate  \\\n",
-       "Cell                                                                    \n",
-       "4                        yes       yes          yes               yes   \n",
-       "6                        yes       yes          yes               yes   \n",
-       "14                       yes       yes          yes               yes   \n",
-       "16                       yes       yes          yes               yes   \n",
-       "24                       yes       yes          yes               yes   \n",
-       "26                       yes       yes          yes               yes   \n",
-       "34                       yes       yes          yes               yes   \n",
-       "44                       yes       yes          yes               yes   \n",
-       "54                       yes       yes          yes               yes   \n",
-       "64                       yes       yes          yes               yes   \n",
-       "74                       yes       yes          yes               yes   \n",
-       "84                       yes       yes          yes               yes   \n",
-       "94                       yes       yes          yes               yes   \n",
-       "104                      yes       yes          yes               yes   \n",
-       "114                      yes       yes          yes               yes   \n",
-       "124                      yes       yes          yes               yes   \n",
-       "134                      yes       yes          yes               yes   \n",
-       "144                      yes       yes          yes               yes   \n",
-       "154                      yes       yes          yes               yes   \n",
-       "164                      yes       yes          yes               yes   \n",
-       "174                      yes       yes          yes               yes   \n",
-       "204                      yes       yes          yes               yes   \n",
-       "214                      yes       yes          yes               yes   \n",
+       "      rel error decrease rate VoV value VoV decrease VoV decrease rate  \\\n",
+       "Tally                                                                    \n",
+       "4                         yes       yes          yes               yes   \n",
+       "6                         yes       yes          yes               yes   \n",
+       "14                        yes       yes          yes               yes   \n",
+       "16                        yes       yes          yes               yes   \n",
+       "24                        yes       yes          yes               yes   \n",
+       "26                        yes       yes          yes               yes   \n",
+       "34                        yes       yes          yes               yes   \n",
+       "44                        yes       yes          yes               yes   \n",
+       "54                        yes       yes          yes               yes   \n",
+       "64                        yes       yes          yes               yes   \n",
+       "74                        yes       yes          yes               yes   \n",
+       "84                        yes       yes          yes               yes   \n",
+       "94                        yes       yes          yes               yes   \n",
+       "104                       yes       yes          yes               yes   \n",
+       "114                       yes       yes          yes               yes   \n",
+       "124                       yes       yes          yes               yes   \n",
+       "134                       yes       yes          yes               yes   \n",
+       "144                       yes       yes          yes               yes   \n",
+       "154                       yes       yes          yes               yes   \n",
+       "164                       yes       yes          yes               yes   \n",
+       "174                       yes       yes          yes               yes   \n",
+       "204                       yes       yes          yes               yes   \n",
+       "214                       yes       yes          yes               yes   \n",
        "\n",
-       "     FoM value FoM behaviour PDF slope Other TFC bins  \n",
-       "Cell                                                   \n",
-       "4          yes           yes       yes         Passed  \n",
-       "6          yes           yes       yes         Passed  \n",
-       "14         yes           yes       yes         Passed  \n",
-       "16         yes           yes       yes         Passed  \n",
-       "24         yes           yes       yes         Passed  \n",
-       "26         yes           yes       yes         Passed  \n",
-       "34         yes           yes       yes         Passed  \n",
-       "44         yes           yes       yes         Passed  \n",
-       "54         yes           yes       yes         Passed  \n",
-       "64         yes           yes       yes         Passed  \n",
-       "74         yes            no       yes         Missed  \n",
-       "84         yes            no       yes         Missed  \n",
-       "94         yes            no       yes         Missed  \n",
-       "104        yes           yes       yes         Passed  \n",
-       "114        yes           yes       yes         Passed  \n",
-       "124        yes           yes       yes         Passed  \n",
-       "134        yes           yes       yes         Passed  \n",
-       "144        yes           yes       yes         Passed  \n",
-       "154        yes           yes       yes         Passed  \n",
-       "164        yes           yes       yes         Passed  \n",
-       "174        yes           yes       yes         Passed  \n",
-       "204        yes           yes       yes         Passed  \n",
-       "214        yes           yes       yes         Passed  "
+       "      FoM value FoM behaviour PDF slope Other TFC bins  \n",
+       "Tally                                                   \n",
+       "4           yes           yes       yes         Passed  \n",
+       "6           yes           yes       yes         Passed  \n",
+       "14          yes           yes       yes         Passed  \n",
+       "16          yes           yes       yes         Passed  \n",
+       "24          yes           yes       yes         Passed  \n",
+       "26          yes           yes       yes         Passed  \n",
+       "34          yes           yes       yes         Passed  \n",
+       "44          yes           yes       yes         Passed  \n",
+       "54          yes           yes       yes         Passed  \n",
+       "64          yes           yes       yes         Passed  \n",
+       "74          yes            no       yes         Missed  \n",
+       "84          yes            no       yes         Missed  \n",
+       "94          yes            no       yes         Missed  \n",
+       "104         yes           yes       yes         Passed  \n",
+       "114         yes           yes       yes         Passed  \n",
+       "124         yes           yes       yes         Passed  \n",
+       "134         yes           yes       yes         Passed  \n",
+       "144         yes           yes       yes         Passed  \n",
+       "154         yes           yes       yes         Passed  \n",
+       "164         yes           yes       yes         Passed  \n",
+       "174         yes           yes       yes         Passed  \n",
+       "204         yes           yes       yes         Passed  \n",
+       "214         yes           yes       yes         Passed  "
       ]
      },
-     "execution_count": 4,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -807,7 +807,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -920,7 +920,7 @@
        "passed?                         yes       yes            no       yes  "
       ]
      },
-     "execution_count": 5,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -932,7 +932,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
@@ -952,7 +952,7 @@
        "dtype: int64"
       ]
      },
-     "execution_count": 6,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -962,6 +962,33 @@
     "outp.get_warnings()[:10]  # show only 10"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "['no m card for material no. 1',\n",
+       " 'surface        5 of tally       22 not found.',\n",
+       " 'FILES card is missing.',\n",
+       " 'photon material        1 is not defined.',\n",
+       " '1 tally volumes or areas were not input nor calculated.']"
+      ]
+     },
+     "execution_count": 8,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# get all fatal errors encountered in the simulation\n",
+    "# Parse the output file\n",
+    "outp = Output(\"outp_fatal\")\n",
+    "outp.get_fatal_errors()"
+   ]
+  },
   {
    "attachments": {},
    "cell_type": "markdown",
@@ -972,7 +999,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [
     {
@@ -981,7 +1008,7 @@
        "677"
       ]
      },
-     "execution_count": 7,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -994,7 +1021,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [
     {
@@ -1003,7 +1030,7 @@
        "0.06763236763236763"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -1015,7 +1042,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [],
    "source": [
diff --git a/src/f4enix/input/materials.py b/src/f4enix/input/materials.py
index b6d1be8..178ad3d 100644
--- a/src/f4enix/input/materials.py
+++ b/src/f4enix/input/materials.py
@@ -1719,9 +1719,7 @@ class MatCardsList(Sequence):
         for materialname, percentage in zip(materials, percentages):
             materialname = materialname.upper()
             percentage_str = str(round(float(percentage) * 100, 2)) + "%"
-            main_header = "{}C Material: {} Percentage: {} ({})\n".format(
-                main_header, materialname, percentage_str, fractiontype
-            )
+            main_header = f"{main_header}C Material: {materialname} Percentage: {percentage_str} ({fractiontype})\n"
             material = copy.deepcopy(self[materialname])
             # Ensure materials have the requested fraction type
             material.switch_fraction(fractiontype, libmanager)
@@ -1736,17 +1734,19 @@ class MatCardsList(Sequence):
                     norm_factor = -norm_factor
                 submat.scale_fractions(norm_factor)
                 # Add info to the header in order to back-trace the generation
-                submat.header = "C {}, submaterial {}\n{}".format(
-                    materialname, j + 1, submat.header
-                )
+                if submat.header is None:
+                    comment = "C no submat header"
+                else:
+                    comment = submat.header
+                submat.header = f"C {materialname}, submaterial {j + 1}\n{comment}"
                 # Drop additional keys if present
                 submat.additional_keys = []
                 current_submaterials.append(submat)
 
             # Change the header of the first submaterial to include the mat. 1
-            new_sub_header = (material.header + current_submaterials[0].header).strip(
-                "\n"
-            )
+            new_sub_header = (
+                str(material.header).strip("\n") + "\n" + current_submaterials[0].header
+            ).strip("\n")
             current_submaterials[0].header = new_sub_header
             submaterials.extend(current_submaterials)
 
diff --git a/src/f4enix/output/MCNPoutput.py b/src/f4enix/output/MCNPoutput.py
index fcf71f5..dc3095d 100644
--- a/src/f4enix/output/MCNPoutput.py
+++ b/src/f4enix/output/MCNPoutput.py
@@ -39,6 +39,7 @@ TOTAL_LP = "particles got lost"
 # -- Patterns --
 PAT_NPS_LINE = re.compile(r" source")
 PAT_WARNING = re.compile(r"warning\.")
+PAT_FATAL = re.compile(r"[\s\t]+fatal error")
 
 STAT_CHECKS_COLUMNS = [
     "TFC bin behaviour",
@@ -712,3 +713,19 @@ class Output:
 
         df = pd.DataFrame(warnings, columns=["Warning"])
         return df.groupby("Warning").size()
+
+    def get_fatal_errors(self) -> list[str]:
+        """Get the fatal errors from the output file.
+
+        Returns
+        -------
+        list[str]
+            list of fatal errors
+        """
+        fatal_errors = []
+        for line in self.lines:
+            if PAT_FATAL.match(line):
+                error = line.split("fatal error.")[-1].strip()
+                fatal_errors.append(error)
+
+        return fatal_errors


Test patch:
diff --git a/tests/MCNPoutput_test.py b/tests/MCNPoutput_test.py
index a637db3..03ec6b7 100644
--- a/tests/MCNPoutput_test.py
+++ b/tests/MCNPoutput_test.py
@@ -1,12 +1,13 @@
-from importlib.resources import files, as_file
-import pandas as pd
 import os
+from importlib.resources import as_file, files
 
-from f4enix.output.MCNPoutput import Output
+import pandas as pd
+import pytest
+
+import tests.resources.output as resources
 from f4enix.input.MCNPinput import Input
+from f4enix.output.MCNPoutput import Output
 from f4enix.output.mctal import Mctal
-import tests.resources.output as resources
-import pytest
 
 RESOURCES = files(resources)
 
@@ -139,3 +140,10 @@ class TestOutput:
         df_2 = outp.assign_tally_description(df, mctal.tallies)
         assert len(df_2) == 11
         assert df_2["Neutron heating F6 [6]"] == "Passed"
+
+    def test_get_fatal_errors(self):
+        with as_file(RESOURCES.joinpath("outp_fatal")) as file:
+            outp = Output(file)
+        errors = outp.get_fatal_errors()
+        assert len(errors) == 5
+        assert errors[-1] == "1 tally volumes or areas were not input nor calculated."
diff --git a/tests/materials_test.py b/tests/materials_test.py
index 4e294ae..20c88c9 100644
--- a/tests/materials_test.py
+++ b/tests/materials_test.py
@@ -540,6 +540,43 @@ class TestMatCardList:
         )
         assert matcard["m1"].submaterials[0].zaidList[1].fraction > 0
 
+    def test_generate_material_from_created_materials(self):
+        materials = MatCardsList([])
+        zaids = [("1001", -100), ("B-0", -200), ("C-12", -50)]
+        newmat = Material.from_zaids(zaids, LIBMAN, "31c", "B4CH2", 302)
+        materials.append(newmat)
+        zaids = [("1001", 2), ("O-0", 1)]
+        newmat = Material.from_zaids(zaids, LIBMAN, "31c", "Water", 400)
+        materials.append(newmat)
+        newmat = materials.generate_material(
+            ["M302", "M400"],
+            [0.2, 0.8],
+            "31c",
+            LIBMAN,
+            fractiontype="atom",
+            mat_name="M500",
+        )
+        assert (
+            newmat.to_text()
+            == """C Material: M302 Percentage: 20.0% (atom)
+C Material: M400 Percentage: 80.0% (atom)
+M500
+C B4CH2
+C M302, submaterial 1
+C no submat header
+       1001.31c        1.628143E-1     $ H-1    WEIGHT(%) 28.571 AB(%) 100.0
+       5010.31c        5.963525E-3     $ B-10   WEIGHT(%) 57.143 AB(%) 19.65
+       5011.31c        2.438520E-2     $ B-11   WEIGHT(%) 57.143 AB(%) 80.35
+       6012.31c        6.837012E-3     $ C-12   WEIGHT(%) 14.286 AB(%) 100.0
+C Water
+C M400, submaterial 1
+C no submat header
+       1001.31c        5.333336E-1     $ H-1    WEIGHT(%) 11.189 AB(%) 100.0
+       8016.31c        2.660188E-1     $ O-16   WEIGHT(%) 88.811 AB(%) 99.757
+       8017.31c        1.022667E-4     $ O-17   WEIGHT(%) 88.811 AB(%) 0.03835
+       8018.31c        5.453336E-4     $ O-18   WEIGHT(%) 88.811 AB(%) 0.2045"""
+        )
+
 
 def compare_without_dollar_comments(text_A: str, text_B: str):
     # strip all dollars