From 34101bc10d43527d531379f6300cc203981013fe Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 23 Jan 2026 17:01:29 -0500 Subject: [PATCH 1/9] pre-commit Black --- src/diffpy/clusterrender/structuredf.py | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/diffpy/clusterrender/structuredf.py diff --git a/src/diffpy/clusterrender/structuredf.py b/src/diffpy/clusterrender/structuredf.py new file mode 100644 index 0000000..e279454 --- /dev/null +++ b/src/diffpy/clusterrender/structuredf.py @@ -0,0 +1,39 @@ +"""This module defines class StructureDF. + +A local structure or cluster of atoms is represented in a DataFrame +format. +""" + +import pandas as pd +from pymatgen.core import Structure + +# ------------------------- + + +class StructureDF(pd.DataFrame): + """Define a structure or cluster of atoms in a pandas DataFrame + format. Each row corresponds to an atom, and columns represent + atomic properties: species, xyz coordinates, and (optionally) + coordination shells, specifying the central atom (0) and its + neighboring atoms (1, 2, ...). + + Parameters + ---------- + structure : pymatgen.core.Structure, optional + A Structure object from pymatgen.core to initialize the DataFrame. + filename : str, optional + Path to a file to read the structure (or cluster) from. + Accepted file formats include cif and xyz. + """ + + def __init__(self, structure=None, site_index=None, filename=None): + # load from Structure object if provided + if isinstance(structure, Structure): + self._from_structure(structure, site_index) + return + # load from file if a filename is provided (without a Structure) + elif filename is not None: + self._from_file(filename, site_index) + return + else: + raise ValueError("Either structure or filename must be provided.") From d5499f71f09b4e779d6841c209d7ef62cff99749 Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 23 Jan 2026 17:15:20 -0500 Subject: [PATCH 2/9] add news --- news/tn-structuredf.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/tn-structuredf.rst diff --git a/news/tn-structuredf.rst b/news/tn-structuredf.rst new file mode 100644 index 0000000..2b61e0c --- /dev/null +++ b/news/tn-structuredf.rst @@ -0,0 +1,23 @@ +**Added:** + +* create StructureDF class (DataFrame) to store a cluster of atoms + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From afc1b9ca26c084c17cedc34859f99f34b345e32a Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 23 Jan 2026 17:57:43 -0500 Subject: [PATCH 3/9] add DataFrame initialization option --- src/diffpy/clusterrender/structuredf.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/diffpy/clusterrender/structuredf.py b/src/diffpy/clusterrender/structuredf.py index e279454..fbffe92 100644 --- a/src/diffpy/clusterrender/structuredf.py +++ b/src/diffpy/clusterrender/structuredf.py @@ -19,21 +19,31 @@ class StructureDF(pd.DataFrame): Parameters ---------- + *args, **kwargs + Arguments passed to the pandas DataFrame constructor to initialize + the DataFrame. + These are ignored if `structure` or `filename` is provided. structure : pymatgen.core.Structure, optional A Structure object from pymatgen.core to initialize the DataFrame. filename : str, optional Path to a file to read the structure (or cluster) from. Accepted file formats include cif and xyz. + site_index : int, optional + Index of the central atom in the structure. """ - def __init__(self, structure=None, site_index=None, filename=None): + def __init__( + self, *args, structure=None, site_index=None, filename=None, **kwargs + ): # load from Structure object if provided if isinstance(structure, Structure): - self._from_structure(structure, site_index) + self.from_structure(structure, site_index) return # load from file if a filename is provided (without a Structure) elif filename is not None: - self._from_file(filename, site_index) + self.from_file(filename, site_index) return + # or try initializing with generic DataFrame arguments else: - raise ValueError("Either structure or filename must be provided.") + super().__init__(*args, **kwargs) + return From a491d0bc89056f0c94ad34849ef644d3d95f0e55 Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 23 Jan 2026 17:59:43 -0500 Subject: [PATCH 4/9] add property constructor --- src/diffpy/clusterrender/structuredf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/diffpy/clusterrender/structuredf.py b/src/diffpy/clusterrender/structuredf.py index fbffe92..42b1570 100644 --- a/src/diffpy/clusterrender/structuredf.py +++ b/src/diffpy/clusterrender/structuredf.py @@ -32,6 +32,10 @@ class StructureDF(pd.DataFrame): Index of the central atom in the structure. """ + @property + def _constructor(self): + return StructureDF + def __init__( self, *args, structure=None, site_index=None, filename=None, **kwargs ): From ba49fb59f133cab09ae920846988cba3334042ce Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Mon, 26 Jan 2026 18:03:44 -0500 Subject: [PATCH 5/9] edit Docstring --- src/diffpy/clusterrender/structuredf.py | 41 +++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/diffpy/clusterrender/structuredf.py b/src/diffpy/clusterrender/structuredf.py index 42b1570..5d3d6b9 100644 --- a/src/diffpy/clusterrender/structuredf.py +++ b/src/diffpy/clusterrender/structuredf.py @@ -17,19 +17,17 @@ class StructureDF(pd.DataFrame): coordination shells, specifying the central atom (0) and its neighboring atoms (1, 2, ...). - Parameters + Methods + ------- + from_structure(structure, site_index=None) + Load structure data from a pymatgen Structure object. + from_file(filename, site_index=None) + Load structure data from a file. + + Attributes ---------- - *args, **kwargs - Arguments passed to the pandas DataFrame constructor to initialize - the DataFrame. - These are ignored if `structure` or `filename` is provided. - structure : pymatgen.core.Structure, optional - A Structure object from pymatgen.core to initialize the DataFrame. - filename : str, optional - Path to a file to read the structure (or cluster) from. - Accepted file formats include cif and xyz. - site_index : int, optional - Index of the central atom in the structure. + _constructor : property + Ensures that DataFrame operations return StructureDF objects. """ @property @@ -39,6 +37,25 @@ def _constructor(self): def __init__( self, *args, structure=None, site_index=None, filename=None, **kwargs ): + """Initialize StructureDF from a Structure object, a file, or + generic DataFrame arguments. + + Parameters + ---------- + structure : pymatgen.core.Structure, optional + The pymatgen Structure object to load the structure from. + site_index : int, optional + The index of atom in the structure to be treated as the + central atom. + filename : str, optional + The path to the structure file. + Accepted formats include CIF and XYZ. + *args, **kwargs + The arguments to initialize a pandas DataFrame with a + generic pandas constructor. + Accepted arguments are described in + https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html. + """ # load from Structure object if provided if isinstance(structure, Structure): self.from_structure(structure, site_index) From 9325a9f89a1b49c546692236f1a3495463c71ae6 Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Thu, 29 Jan 2026 16:24:20 -0500 Subject: [PATCH 6/9] edit docstring --- .../clusterrender/structuredataframe.py | 50 +++++++++++++ src/diffpy/clusterrender/structuredf.py | 70 ------------------- 2 files changed, 50 insertions(+), 70 deletions(-) create mode 100644 src/diffpy/clusterrender/structuredataframe.py delete mode 100644 src/diffpy/clusterrender/structuredf.py diff --git a/src/diffpy/clusterrender/structuredataframe.py b/src/diffpy/clusterrender/structuredataframe.py new file mode 100644 index 0000000..66d1258 --- /dev/null +++ b/src/diffpy/clusterrender/structuredataframe.py @@ -0,0 +1,50 @@ +"""This module defines class StructureDF. + +A local structure or cluster of atoms is represented in a DataFrame +format. +""" + +import pandas as pd + +# ------------------------- + + +class StructureDataFrame(pd.DataFrame): + """Define a structure or cluster of atoms in a pandas DataFrame + format. Each row corresponds to an atom, and columns represent + atomic properties: species, xyz coordinates, and (optionally) + coordination shells, specifying the central atom (0) and its + neighboring atoms (1, 2, ...). + + Methods + ------- + parse_data(structure_input, site_index=0) + Parse structure data from a structure, a file, a dictionary, + or a DataFrame into StructureDataFrame. + + Attributes + ---------- + _constructor : property + Ensures that DataFrame operations return StructureDataFrame objects. + """ + + @property + def _constructor(self): + return StructureDataFrame + + def __init__(self, structure_input, site_index=0): + """Initialize StructureDataFrame from a Structure object, a + file, or generic DataFrame arguments. + + Parameters + ---------- + structure_input : pymatgen.core.Structure, pathlib.Path, str, + dict, or pd.DataFrame + The input structure or cluster of atoms to be visualized. + site_index : int, optional + The index of atom in the structure to be treated as the + central atom. + Default is 0. + """ + # parse and load structure_input + self._parse_data(structure_input, site_index) diff --git a/src/diffpy/clusterrender/structuredf.py b/src/diffpy/clusterrender/structuredf.py deleted file mode 100644 index 5d3d6b9..0000000 --- a/src/diffpy/clusterrender/structuredf.py +++ /dev/null @@ -1,70 +0,0 @@ -"""This module defines class StructureDF. - -A local structure or cluster of atoms is represented in a DataFrame -format. -""" - -import pandas as pd -from pymatgen.core import Structure - -# ------------------------- - - -class StructureDF(pd.DataFrame): - """Define a structure or cluster of atoms in a pandas DataFrame - format. Each row corresponds to an atom, and columns represent - atomic properties: species, xyz coordinates, and (optionally) - coordination shells, specifying the central atom (0) and its - neighboring atoms (1, 2, ...). - - Methods - ------- - from_structure(structure, site_index=None) - Load structure data from a pymatgen Structure object. - from_file(filename, site_index=None) - Load structure data from a file. - - Attributes - ---------- - _constructor : property - Ensures that DataFrame operations return StructureDF objects. - """ - - @property - def _constructor(self): - return StructureDF - - def __init__( - self, *args, structure=None, site_index=None, filename=None, **kwargs - ): - """Initialize StructureDF from a Structure object, a file, or - generic DataFrame arguments. - - Parameters - ---------- - structure : pymatgen.core.Structure, optional - The pymatgen Structure object to load the structure from. - site_index : int, optional - The index of atom in the structure to be treated as the - central atom. - filename : str, optional - The path to the structure file. - Accepted formats include CIF and XYZ. - *args, **kwargs - The arguments to initialize a pandas DataFrame with a - generic pandas constructor. - Accepted arguments are described in - https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html. - """ - # load from Structure object if provided - if isinstance(structure, Structure): - self.from_structure(structure, site_index) - return - # load from file if a filename is provided (without a Structure) - elif filename is not None: - self.from_file(filename, site_index) - return - # or try initializing with generic DataFrame arguments - else: - super().__init__(*args, **kwargs) - return From 78c25fb50fc9145afdfd107647014f9254bae3b3 Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 30 Jan 2026 14:49:57 -0500 Subject: [PATCH 7/9] change name and edit docstring --- ...ucturedataframe.py => clusterdataframe.py} | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) rename src/diffpy/clusterrender/{structuredataframe.py => clusterdataframe.py} (62%) diff --git a/src/diffpy/clusterrender/structuredataframe.py b/src/diffpy/clusterrender/clusterdataframe.py similarity index 62% rename from src/diffpy/clusterrender/structuredataframe.py rename to src/diffpy/clusterrender/clusterdataframe.py index 66d1258..dd7457c 100644 --- a/src/diffpy/clusterrender/structuredataframe.py +++ b/src/diffpy/clusterrender/clusterdataframe.py @@ -1,4 +1,4 @@ -"""This module defines class StructureDF. +"""This module defines class ClusterDataFrame. A local structure or cluster of atoms is represented in a DataFrame format. @@ -9,32 +9,33 @@ # ------------------------- -class StructureDataFrame(pd.DataFrame): - """Define a structure or cluster of atoms in a pandas DataFrame - format. Each row corresponds to an atom, and columns represent +class ClusterDataFrame(pd.DataFrame): + """Define a cluster of atoms in a pandas DataFrame format. + + Each row corresponds to an atom, and columns represent atomic properties: species, xyz coordinates, and (optionally) coordination shells, specifying the central atom (0) and its neighboring atoms (1, 2, ...). Methods ------- - parse_data(structure_input, site_index=0) + parse_structure(structure_input, site_index=0) Parse structure data from a structure, a file, a dictionary, - or a DataFrame into StructureDataFrame. + or a DataFrame into ClusterDataFrame. Attributes ---------- _constructor : property - Ensures that DataFrame operations return StructureDataFrame objects. + Ensures that DataFrame operations return ClusterDataFrame objects. """ @property def _constructor(self): - return StructureDataFrame + return ClusterDataFrame def __init__(self, structure_input, site_index=0): - """Initialize StructureDataFrame from a Structure object, a - file, or generic DataFrame arguments. + """Initialize ClusterDataFrame from a Structure object, a file, + or generic DataFrame arguments. Parameters ---------- @@ -47,4 +48,4 @@ def __init__(self, structure_input, site_index=0): Default is 0. """ # parse and load structure_input - self._parse_data(structure_input, site_index) + self._parse_structure(structure_input, site_index) From 975e28d9c77d9edc51b0d3a79833b8d9770eabab Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Fri, 30 Jan 2026 14:50:19 -0500 Subject: [PATCH 8/9] change class name in news --- news/tn-structuredf.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/tn-structuredf.rst b/news/tn-structuredf.rst index 2b61e0c..0996e84 100644 --- a/news/tn-structuredf.rst +++ b/news/tn-structuredf.rst @@ -1,6 +1,6 @@ **Added:** -* create StructureDF class (DataFrame) to store a cluster of atoms +* create ClusterDataFrame class (DataFrame) to store a cluster of atoms **Changed:** From d9c2ede1838796c57fc155a52ed1ba5c46177588 Mon Sep 17 00:00:00 2001 From: tinatn29 Date: Mon, 9 Feb 2026 17:13:20 -0500 Subject: [PATCH 9/9] start writing test for class constructor --- tests/test_clusterdataframe.py | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_clusterdataframe.py diff --git a/tests/test_clusterdataframe.py b/tests/test_clusterdataframe.py new file mode 100644 index 0000000..c6fc648 --- /dev/null +++ b/tests/test_clusterdataframe.py @@ -0,0 +1,64 @@ +import pandas as pd +import pytest + +from diffpy.clusterrender.clusterdataframe import ClusterDataFrame + +""" +Tests for the ClusterDataFrame class. +""" +# set up test data +dict_input = { + "species": ["C", "O", "O"], + "x": [0.0, 1.0, -1.0], + "y": [0.0, 0.0, 0.0], + "z": [0.0, 0.0, 0.0], +} +df_input = pd.DataFrame(dict_input) + +# basic outputs (specifying center but not coordination shells) +output_C0 = pd.DataFrame( + { + "species": ["C", "O", "O"], + "x": [0.0, 1.0, -1.0], + "y": [0.0, 0.0, 0.0], + "z": [0.0, 0.0, 0.0], + "shell": [0, None, None], + } +) +output_O1 = pd.DataFrame( + { + "species": ["O", "C", "O"], + "x": [0.0, -1.0, -2.0], + "y": [0.0, 0.0, 0.0], + "z": [0.0, 0.0, 0.0], + "shell": [0, None, None], + } +) + +test_data = [ + # (input, expected_output) or + # (input, test_index, expected_output) + # basic inputs: read from dict or DataFrame + # without any changes + (dict_input, df_input), + (df_input, df_input), + # with site_index specified + (dict_input, 0, df_input), + (dict_input, 1, output_O1), +] + + +@pytest.mark.parametrize("input_test_data", test_data) +def test_clusterdataframe(input_test_data): + """Test ClusterDataFrame initialization and parsing.""" + if len(input_test_data) == 2: + input_structure, expected_output = input_test_data + cdf = ClusterDataFrame(input_structure) + else: + input_structure, site_index, expected_output = input_test_data + cdf = ClusterDataFrame(input_structure, site_index=site_index) + + # check if the output matches the expected DataFrame + pd.testing.assert_frame_equal( + cdf.reset_index(drop=True), expected_output.reset_index(drop=True) + )