Repository: idaholab/MontePy

Issue:
Montepy is_atomic_fraction does not work as expected
<!--
**Reminders**
1. This is not a place to debug your MCNP models
1. MCNP is export controlled and only its 6.2 and 6.3 user manuals are public. Don't include any information about MCNP which is not in those manuals.
1. Your model may be export controlled, or proprietary. Please change specific numbers (e.g., dimensions, material compositions, etc.) in your minimum working examples. -->

**Describe the bug**

When montepy.data_inputs.material.is_atomic_fraction is set to False and I add a new material component and then write the output file, the fraction is positive (thus set to atomic fraction).

**To Reproduce**

A short code snippet of what you have ran. 

``` python
zaid='1001.70c'
iso = montepy.data_inputs.isotope.Isotope(zaid)
mat = montepy.data_inputs.material_component.MaterialComponent(iso, 0.2)
# my MCNP_problem
print(pb.materials[99].is_atomic_fraction) --> True
# Then add a new material_component
pb.materials[99].material_components[iso]=mat
pb.write_to_file(output_path)
```
Then my output file has previous material components with negative fraction (i.e weight fraction) and the one I just added is positive. I find no way to write it with a minus sign before.


**MCNP input file snippet**

My M99 Material with the hydrogen i just added.

```
M99     12024.70c -0.4818 12025.70c -0.0610 12026.70c -0.0672 &
        8016.60C -0.39   $ MgO
1001.70c 0.2 
```

**Version**

 - MontePy 0.5.2
 - MCNP 6.1.0


**Additional context**

I know this should be fixed in MontePy 1.0 but I'd like to know if there is a quick fix I could do because I really need to be able to configure my Materials ~~cards~~. Thank you!


Discussion:

Patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 2e4bb9b4..d3a13b32 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -13,29 +13,35 @@ Please provide a summary of the change, referencing the issue it fixes, if appli
 - [ ] I have performed a self-review of my own code.
 - [ ] The code follows the standards outlined in the [development documentation](https://idaholab.github.io/MontePy/developing.html).
 - [ ] I have added tests that prove my fix is effective or that my feature works (if applicable).
-- [ ] I have made corresponding changes to the documentation, providing clear details on the added or modified functionality (if applicable).
 
 ---
 
-### Documentation Checklist
+<details open> 
+
+<summary><h3>Documentation Checklist</h3></summary>
 
 - [ ] I have documented all added classes and methods.
 - [ ] For infrastructure updates, I have updated the developer's guide.
 - [ ] For significant new features, I have added a section to the getting started guide.
 
+</details>
+
 ---
 
-### First-Time Contributor Checklist
+<details>
+<summary><h3>First-Time Contributor Checklist</h3></summary>
 
 - [ ] If this is your first contribution, add yourself to `pyproject.toml` if you wish to do so.
 
+</details>
+
 ---
 
 ### Additional Notes for Reviewers
 
 Ensure that:
 
-- The submitted code is consistent with the merge checklist outlined [here](https://www.montepy.org/developing.html#merge-checklist).
-- The tests pass locally before CI checks.
-- The PR covers all relevant aspects according to the development guidelines.
+- [ ] The submitted code is consistent with the merge checklist outlined [here](https://www.montepy.org/developing.html#merge-checklist).
+- [ ] The PR covers all relevant aspects according to the development guidelines.
+- [ ] 100% coverage of the patch is achieved, or justification for a variance is given.
 """
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index 27b2202a..64e195bf 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -5,6 +5,13 @@ MontePy Changelog
 0.5 releases
 ============
 
+#Next Version#
+--------------
+
+**Bug Fixes**
+
+* Fixed how material components work so new components can actually be added to a material and exported (:issue`597`).
+
 0.5.2
 --------------
 
diff --git a/montepy/data_inputs/material.py b/montepy/data_inputs/material.py
index b019ff36..f337fa0b 100644
--- a/montepy/data_inputs/material.py
+++ b/montepy/data_inputs/material.py
@@ -152,8 +152,13 @@ See <https://www.montepy.org/migrations/migrate0_1.html> for more information ""
 
     def _update_values(self):
         new_list = syntax_node.IsotopesNode("new isotope list")
-        for isotope, component in self._material_components.items():
+        for idx, (isotope, component) in enumerate(self._material_components.items()):
             isotope._tree.value = isotope.mcnp_str()
+            node = component._tree
+            node.is_negatable_float = True
+            node.is_negative = not self.is_atom_fraction
+            if idx < len(self._material_components) - 1 and not node.padding:
+                node.padding = syntax_node.PaddingNode(" ")
             new_list.append(("_", isotope._tree, component._tree))
         self._tree.nodes["data"] = new_list
 
diff --git a/pyproject.toml b/pyproject.toml
index 6bf2b1e0..6afab82b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,6 @@ test = [
 	"coverage[toml]>=6.3.2", 
 	"hypothesis",
 	"pytest", 
-	"hypothesis",
 	"pytest-profiling",
 	"montepy[build]",
 	"phmutest",


Test patch:
diff --git a/tests/test_material.py b/tests/test_material.py
index d9267c6a..144acbab 100644
--- a/tests/test_material.py
+++ b/tests/test_material.py
@@ -68,6 +68,38 @@ Pu-239   (80c) 0.1
             self.assertEqual(mat, answers[i])
 
 
+@pytest.mark.parametrize(
+    "isotope_str, atom_frac, fraction",
+    [("2004.80c", False, 0.1), ("1001.70c", True, 0.1)],
+)
+@pytest.mark.filterwarnings("ignore")
+def test_material_component_add(isotope_str, atom_frac, fraction):
+    frac_marker = "-" if not atom_frac else ""
+    in_str = f"M20 1001.80c {frac_marker}0.5 8016.80c {frac_marker}0.5"
+    input_card = Input([in_str], BlockType.DATA)
+    material = Material(input_card)
+    iso = Isotope(isotope_str)
+    comp = MaterialComponent(iso, fraction)
+    material.material_components[iso] = comp
+    verify_export(material)
+
+
+def verify_export(mat):
+    output = mat.format_for_mcnp_input((6, 3, 0))
+    print(output)
+    new_mat = Material(Input(output, BlockType.DATA))
+    assert mat.number == new_mat.number, "Material number not preserved."
+    assert len(mat.material_components) == len(new_mat.material_components)
+    assert mat.is_atom_fraction == new_mat.is_atom_fraction
+    for old_comp, new_comp in zip(
+        mat.material_components.values(), new_mat.material_components.values()
+    ):
+        assert str(old_comp.isotope) == str(
+            new_comp.isotope
+        ), "Material didn't preserve nuclides."
+        assert old_comp.fraction == pytest.approx(new_comp.fraction)
+
+
 def test_material_format_mcnp():
     in_strs = ["M20 1001.80c 0.5", "     8016.80c         0.5"]
     input_card = Input(in_strs, BlockType.DATA)