From f61a22eb37200c93812fb9b063e313b8dff6bcbf Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Mon, 20 Jul 2026 14:55:48 -0700 Subject: [PATCH 1/6] added_atom_class_with_test --- steer_materials/Atoms.py | 66 ++++++++++++++++++++++++++++++++++++++ steer_materials/Crystal.py | 0 test/test_atoms.py | 19 +++++++++++ 3 files changed, 85 insertions(+) create mode 100644 steer_materials/Atoms.py create mode 100644 steer_materials/Crystal.py create mode 100644 test/test_atoms.py diff --git a/steer_materials/Atoms.py b/steer_materials/Atoms.py new file mode 100644 index 0000000..6fa1423 --- /dev/null +++ b/steer_materials/Atoms.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: 2024-2026 Stanford University +# SPDX-License-Identifier: AGPL-3.0-or-later + +from steer_core.Constants.Units import * +from steer_core.Mixins.TypeChecker import ValidationMixin +from steer_core.Mixins.Serializer import SerializerMixin +from steer_core.Mixins.Dunder import DunderMixin +from steer_core.Mixins.Propagation import PropagationMixin + + +class Atom( + ValidationMixin, + SerializerMixin, + DunderMixin, + PropagationMixin +): + + def __init__( + self, + name: str, + symbol: str, + atomic_number: int, + atomic_mass: float + ): + + self.name = name + self.symbol = symbol + self.atomic_number = atomic_number + self.atomic_mass = atomic_mass + + @property + def name(self) -> str: + return self._name + + @property + def symbol(self) -> str: + return self._symbol + + @property + def atomic_number(self) -> int: + return self._atomic_number + + @property + def atomic_mass(self) -> float: + return self._atomic_mass * KG_TO_AU + + @name.setter + def name(self, value: str): + self.validate_type(value, str, "name") + self._name = value + + @symbol.setter + def symbol(self, value: str): + self.validate_type(value, str, "symbol") + self._symbol = value + + @atomic_number.setter + def atomic_number(self, value: int): + self.validate_type(value, int, "atomic_number") + self._atomic_number = value + + @atomic_mass.setter + def atomic_mass(self, value: float): + self.validate_type(value, float, "atomic_mass") + self._atomic_mass = value * AU_TO_KG + diff --git a/steer_materials/Crystal.py b/steer_materials/Crystal.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_atoms.py b/test/test_atoms.py new file mode 100644 index 0000000..811d435 --- /dev/null +++ b/test/test_atoms.py @@ -0,0 +1,19 @@ +import unittest +from steer_core.Constants.Units import AU_TO_KG + +from steer_materials.Atoms import Atom + + +class TestAtom(unittest.TestCase): + + def setUp(self): + self.atom = Atom(name="Plutonium", symbol="Pu", atomic_number=94, atomic_mass=244.0) + + def test_atom_properties(self): + self.assertEqual(self.atom.name, "Plutonium") + self.assertEqual(self.atom.symbol, "Pu") + self.assertEqual(self.atom.atomic_number, 94) + self.assertAlmostEqual(self.atom._atomic_mass, 244.0 * AU_TO_KG) + + + From ab9acc9044fb60aeed3e6bff072f0c847fc4325e Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Thu, 23 Jul 2026 10:01:05 -0700 Subject: [PATCH 2/6] Refactor Atom class to derive properties from symbol using periodic table constants Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- steer_materials/Atoms.py | 137 +++++++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 14 deletions(-) diff --git a/steer_materials/Atoms.py b/steer_materials/Atoms.py index 6fa1423..02f2485 100644 --- a/steer_materials/Atoms.py +++ b/steer_materials/Atoms.py @@ -7,6 +7,13 @@ from steer_core.Mixins.Dunder import DunderMixin from steer_core.Mixins.Propagation import PropagationMixin +from steer_core.Decorators.General import recalculate + +from steer_core.Constants.Periodic_table import ( + atomic_numbers_to_symbols, + atomic_numbers_to_names, + atomic_numbers_to_masses +) class Atom( ValidationMixin, @@ -17,50 +24,152 @@ class Atom( def __init__( self, - name: str, symbol: str, - atomic_number: int, - atomic_mass: float + atomic_mass: float = None, + charge: float = 0.0 ): - self.name = name + self._update_properties = False + self.symbol = symbol - self.atomic_number = atomic_number self.atomic_mass = atomic_mass + self.charge = charge + + self._calculate_all_properties() + self._update_properties = True + + def _calculate_all_properties(self): + """ + Calculate all properties of the atom based on the provided information. + """ + self._calculate_mass_from_symbol() + self._calculate_name_from_symbol() + self._calculate_atomic_number_from_symbol() + + def _calculate_mass_from_name(self): + """ + Calculate the atomic mass based on the atom's name. + """ + atomic_number = {v: k for k, v in atomic_numbers_to_names.items()}[self._name] + self._atomic_mass = atomic_numbers_to_masses[atomic_number] + + def _calculate_symbol_from_name(self): + """ + Calculate the atomic symbol based on the atom's name. + """ + atomic_number = {v: k for k, v in atomic_numbers_to_names.items()}[self._name] + self._symbol = atomic_numbers_to_symbols[atomic_number] + + def _calculate_atomic_number_from_name(self): + """ + Calculate the atomic number based on the atom's name. + """ + atomic_number = {v: k for k, v in atomic_numbers_to_names.items()}[self._name] + self._atomic_number = atomic_number + + def _calculate_mass_from_symbol(self): + """ + Calculate the atomic mass based on the atom's symbol. + """ + atomic_number = {v: k for k, v in atomic_numbers_to_symbols.items()}[self._symbol] + self._atomic_mass = atomic_numbers_to_masses[atomic_number] + + def _calculate_name_from_symbol(self): + """ + Calculate the atomic name based on the atom's symbol. + """ + atomic_number = {v: k for k, v in atomic_numbers_to_symbols.items()}[self._symbol] + self._name = atomic_numbers_to_names[atomic_number] + + def _calculate_atomic_number_from_symbol(self): + """ + Calculate the atomic number based on the atom's symbol. + """ + self._atomic_number = {v: k for k, v in atomic_numbers_to_symbols.items()}[self._symbol] + + def _calculate_mass_from_atomic_number(self): + """ + Calculate the atomic mass based on the atom's atomic number. + """ + self._atomic_mass = atomic_numbers_to_masses[self._atomic_number] + + def _calculate_name_from_atomic_number(self): + """ + Calculate the atomic name based on the atom's atomic number. + """ + self._name = atomic_numbers_to_names[self._atomic_number] + + def _calculate_symbol_from_atomic_number(self): + """ + Calculate the atomic symbol based on the atom's atomic number. + """ + self._symbol = atomic_numbers_to_symbols[self._atomic_number] + + def _calculate_name_from_mass(self): + """ + Calculate the atomic name based on the atom's atomic mass. + """ + self._name = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] + + def _calculate_symbol_from_mass(self): + """ + Calculate the atomic symbol based on the atom's atomic mass. + """ + self._symbol = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] + + def _calculate_atomic_number_from_mass(self): + """ + Calculate the atomic number based on the atom's atomic mass. + """ + self._atomic_number = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] @property def name(self) -> str: return self._name - + @property def symbol(self) -> str: return self._symbol - + @property def atomic_number(self) -> int: return self._atomic_number - + @property def atomic_mass(self) -> float: - return self._atomic_mass * KG_TO_AU + return self._atomic_mass @name.setter + @recalculate("mass_from_name") + @recalculate("symbol_from_name") + @recalculate("atomic_number_from_name") def name(self, value: str): - self.validate_type(value, str, "name") + self.validate_string(value, "name") self._name = value @symbol.setter + @recalculate("atomic_number_from_symbol") + @recalculate("mass_from_symbol") + @recalculate("name_from_symbol") def symbol(self, value: str): - self.validate_type(value, str, "symbol") + self.validate_string(value, "symbol") self._symbol = value @atomic_number.setter + @recalculate("symbol_from_atomic_number") + @recalculate("mass_from_atomic_number") + @recalculate("name_from_atomic_number") def atomic_number(self, value: int): - self.validate_type(value, int, "atomic_number") + self.validate_positive_int(value, "atomic_number") self._atomic_number = value @atomic_mass.setter + @recalculate("name_from_mass") + @recalculate("symbol_from_mass") + @recalculate("atomic_number_from_mass") def atomic_mass(self, value: float): - self.validate_type(value, float, "atomic_mass") - self._atomic_mass = value * AU_TO_KG + self.validate_positive_float(value, "atomic_mass") + self._atomic_mass = value + + From 6376688bf5e541d7e30cb20401d3ec426a96f33c Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Wed, 29 Jul 2026 11:16:28 -0700 Subject: [PATCH 3/6] added auto update to atom class --- steer_materials/Atoms.py | 19 +++++++++++-------- test/test_atoms.py | 27 ++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/steer_materials/Atoms.py b/steer_materials/Atoms.py index 02f2485..9806d7e 100644 --- a/steer_materials/Atoms.py +++ b/steer_materials/Atoms.py @@ -15,6 +15,8 @@ atomic_numbers_to_masses ) +from steer_core.Constants.Units import AU_TO_KG, KG_TO_AU + class Atom( ValidationMixin, SerializerMixin, @@ -51,7 +53,7 @@ def _calculate_mass_from_name(self): Calculate the atomic mass based on the atom's name. """ atomic_number = {v: k for k, v in atomic_numbers_to_names.items()}[self._name] - self._atomic_mass = atomic_numbers_to_masses[atomic_number] + self._atomic_mass = atomic_numbers_to_masses[atomic_number] * AU_TO_KG def _calculate_symbol_from_name(self): """ @@ -72,7 +74,7 @@ def _calculate_mass_from_symbol(self): Calculate the atomic mass based on the atom's symbol. """ atomic_number = {v: k for k, v in atomic_numbers_to_symbols.items()}[self._symbol] - self._atomic_mass = atomic_numbers_to_masses[atomic_number] + self._atomic_mass = atomic_numbers_to_masses[atomic_number] * AU_TO_KG def _calculate_name_from_symbol(self): """ @@ -91,7 +93,7 @@ def _calculate_mass_from_atomic_number(self): """ Calculate the atomic mass based on the atom's atomic number. """ - self._atomic_mass = atomic_numbers_to_masses[self._atomic_number] + self._atomic_mass = atomic_numbers_to_masses[self._atomic_number] * AU_TO_KG def _calculate_name_from_atomic_number(self): """ @@ -109,19 +111,20 @@ def _calculate_name_from_mass(self): """ Calculate the atomic name based on the atom's atomic mass. """ - self._name = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] + self._name = {v: k for k, v in atomic_numbers_to_masses.items()}[self.atomic_mass] def _calculate_symbol_from_mass(self): """ Calculate the atomic symbol based on the atom's atomic mass. """ - self._symbol = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] + atomic_number = {v: k for k, v in atomic_numbers_to_masses.items()}[self.atomic_mass] + self._symbol = atomic_numbers_to_symbols[atomic_number] def _calculate_atomic_number_from_mass(self): """ Calculate the atomic number based on the atom's atomic mass. """ - self._atomic_number = {v: k for k, v in atomic_numbers_to_masses.items()}[self._atomic_mass] + self._atomic_number = {v: k for k, v in atomic_numbers_to_masses.items()}[self.atomic_mass] @property def name(self) -> str: @@ -137,7 +140,7 @@ def atomic_number(self) -> int: @property def atomic_mass(self) -> float: - return self._atomic_mass + return round(self._atomic_mass * KG_TO_AU, 3) @name.setter @recalculate("mass_from_name") @@ -169,7 +172,7 @@ def atomic_number(self, value: int): @recalculate("atomic_number_from_mass") def atomic_mass(self, value: float): self.validate_positive_float(value, "atomic_mass") - self._atomic_mass = value + self._atomic_mass = value * AU_TO_KG diff --git a/test/test_atoms.py b/test/test_atoms.py index 811d435..6b1c7c0 100644 --- a/test/test_atoms.py +++ b/test/test_atoms.py @@ -7,7 +7,7 @@ class TestAtom(unittest.TestCase): def setUp(self): - self.atom = Atom(name="Plutonium", symbol="Pu", atomic_number=94, atomic_mass=244.0) + self.atom = Atom(symbol="Pu", atomic_mass=244.0) def test_atom_properties(self): self.assertEqual(self.atom.name, "Plutonium") @@ -15,5 +15,26 @@ def test_atom_properties(self): self.assertEqual(self.atom.atomic_number, 94) self.assertAlmostEqual(self.atom._atomic_mass, 244.0 * AU_TO_KG) - - + def test_atomic_mass_conversion(self): + # Test that the atomic mass is correctly converted to kg + expected_mass_kg = 244.0 * AU_TO_KG + self.assertAlmostEqual(self.atom._atomic_mass, expected_mass_kg) + + def test_name_setter(self): + self.atom.name = "Uranium" + self.assertEqual(self.atom.symbol, "U") + self.assertEqual(self.atom.atomic_number, 92) + self.assertAlmostEqual(self.atom._atomic_mass, 238.02891 * AU_TO_KG) + + def test_mass_setter(self): + self.atom.atomic_mass = 238.02891 + self.assertAlmostEqual(self.atom._atomic_mass, 238.02891 * AU_TO_KG) + self.assertEqual(self.atom.symbol, "U") + self.assertEqual(self.atom.atomic_number, 92) + + def test_symbol_setter(self): + self.atom.symbol = "U" + self.assertEqual(self.atom.name, "Uranium") + self.assertEqual(self.atom.atomic_number, 92) + self.assertAlmostEqual(self.atom._atomic_mass, 238.02891 * AU_TO_KG) + From 077a8828779e5e89bfb47121a3da38f700c00e7a Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Mon, 10 Aug 2026 08:28:34 -0700 Subject: [PATCH 4/6] Update to steer-core 0.2.20, bump to 0.1.46 --- pyproject.toml | 2 +- steer_materials/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f498789..bd3f1a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ ] dependencies = [ - "steer-core==0.2.19", + "steer-core==0.2.20", ] [project.optional-dependencies] diff --git a/steer_materials/__init__.py b/steer_materials/__init__.py index 2c61630..1547a44 100644 --- a/steer_materials/__init__.py +++ b/steer_materials/__init__.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2024-2026 Stanford University # SPDX-License-Identifier: AGPL-3.0-or-later -__version__ = "0.1.45" +__version__ = "0.1.46" From 1bcbf80a25ad1ccae9a1f7b5277f12da1365ae8e Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Mon, 17 Aug 2026 09:55:18 -0700 Subject: [PATCH 5/6] chore(deps): relax the steer-core pin, bump to 0.1.47 `steer-core==0.2.20` was an exact pin, which made the steer-core 0.2.21 deserialization security patch uninstallable without rebuilding this package. Widened to `>=0.2.21,<0.3`: a deploy's reproducibility comes from its vendored wheel set, not from pins between the sibling packages. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 5 ++++- steer_materials/__init__.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bd3f1a8..57bc218 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,10 @@ classifiers = [ ] dependencies = [ - "steer-core==0.2.20", + # Compatible-release range, not an exact pin: an exact pin means a steer-core + # security patch cannot be installed without rebuilding this package too. + # A deploy's reproducibility comes from its vendored wheel set, not from here. + "steer-core>=0.2.21,<0.3", ] [project.optional-dependencies] diff --git a/steer_materials/__init__.py b/steer_materials/__init__.py index 1547a44..14a8fb1 100644 --- a/steer_materials/__init__.py +++ b/steer_materials/__init__.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2024-2026 Stanford University # SPDX-License-Identifier: AGPL-3.0-or-later -__version__ = "0.1.46" +__version__ = "0.1.47" From 72ad9c08d2a79c87eca2ed35bf232bf3840a8517 Mon Sep 17 00:00:00 2001 From: Nicholas Siemons Date: Tue, 18 Aug 2026 11:47:08 -0700 Subject: [PATCH 6/6] vbump_req_bump --- pyproject.toml | 2 +- steer_materials/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 57bc218..860ac46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ # Compatible-release range, not an exact pin: an exact pin means a steer-core # security patch cannot be installed without rebuilding this package too. # A deploy's reproducibility comes from its vendored wheel set, not from here. - "steer-core>=0.2.21,<0.3", + "steer-core>=0.2.22,<0.3", ] [project.optional-dependencies] diff --git a/steer_materials/__init__.py b/steer_materials/__init__.py index 14a8fb1..eabc785 100644 --- a/steer_materials/__init__.py +++ b/steer_materials/__init__.py @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2024-2026 Stanford University # SPDX-License-Identifier: AGPL-3.0-or-later -__version__ = "0.1.47" +__version__ = "0.1.48"