Skip to content

Commit bde39e0

Browse files
pm-blancojngrad
andauthored
Hide internal methods for df management from the API and make them stateless (#145)
* create a private class for methods to manage the df * move more methods to the new private class * remove unnesary out variable * move add_value_to_df to storage * make clean_ids_in_df_row private in storage * make add_bond and related methods privated and migrate them to storage * shorten calls to df_management * make private the last internal utility functions * update changelog and add missing changes * fix issues in calls in tests * fix broken code in test * fix wrong argument name * fix PR number in changelog * Update CHANGELOG.md Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> * Update pyMBE/storage/df_management.py Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> * Update pyMBE/storage/df_management.py Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> * Update pyMBE/storage/df_management.py Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> * Update pyMBE/storage/df_management.py Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de> --------- Co-authored-by: Pablo <pablb.ntnu.no> Co-authored-by: Jean-Noël Grad <jgrad@icp.uni-stuttgart.de>
1 parent 9b22435 commit bde39e0

11 files changed

Lines changed: 922 additions & 755 deletions

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Changed
11+
- Methods that interact directly with the pyMBE dataframe are now private and stored in a dedicated module in `storage/df_management`. These methods also have been refactored to be stateless methods, i.e. making it impossible for them to change behavior during the pyMBE object lifetime or for the user to change the pyMBE dataframe unless explicitely calling them. This includes the methods: `add_bond_in_df`, `add_value_to_df`, `assign_molecule_id`, `check_if_df_cell_has_a_value`, `check_if_name_is_defined_in_df`, `check_if_multiple_pmb_types_for_name`, `clean_df_row`, `clean_ids_in_df_row`, `copy_df_entry`, `create_variable_with_units`, `convert_columns_to_original_format`, `convert_str_to_bond_object`, `delete_entries_in_df`, `find_bond_key`, `setup_df`. (#145)
12+
- `define_particle_entry_in_df` is now a private method in pyMBE, as it is a convenience method for internal use. (#145)
13+
- The custom `NumpyEncoder` is now a private class in the private module `storage/df_management` because it is only internally used in pyMBE for serialization/deserialization. (#145)
14+
815
## [1.0.0] - 2025-10-08
916

1017
### Changed

pyMBE/pyMBE.py

Lines changed: 362 additions & 706 deletions
Large diffs are not rendered by default.

pyMBE/storage/df_management.py

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.

testsuite/bond_tests.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import json
2525
import io
2626
import logging
27-
27+
import pyMBE.storage.df_management as df_management
2828

2929
# Create an in-memory log stream
3030
log_stream = io.StringIO()
@@ -38,7 +38,7 @@
3838
class Test(ut.TestCase):
3939

4040
def setUp(self):
41-
pmb.setup_df()
41+
pmb.df = df_management._DFManagement._setup_df()
4242

4343
def check_bond_setup(self, bond_object, input_parameters, bond_type):
4444
"""
@@ -91,7 +91,7 @@ def test_bond_harmonic(self):
9191
# check bond deserialization
9292
bond_params = bond_object.get_params()
9393
bond_params["bond_id"] = bond_object._bond_id
94-
deserialized = pmb.convert_str_to_bond_object(
94+
deserialized = df_management._DFManagement._convert_str_to_bond_object(
9595
f'{bond_object.__class__.__name__}({json.dumps(bond_params)})')
9696
self.check_bond_setup(bond_object=deserialized,
9797
input_parameters=bond,
@@ -130,7 +130,7 @@ def test_bond_fene(self):
130130
# check bond deserialization
131131
bond_params = bond_object.get_params()
132132
bond_params["bond_id"] = bond_object._bond_id
133-
deserialized = pmb.convert_str_to_bond_object(
133+
deserialized = df_management._DFManagement._convert_str_to_bond_object(
134134
f'{bond_object.__class__.__name__}({json.dumps(bond_params)})')
135135
self.check_bond_setup(bond_object=deserialized,
136136
input_parameters=bond,
@@ -278,22 +278,22 @@ def test_bond_framework(self):
278278

279279
# check deserialization exceptions
280280
with self.assertRaises(ValueError):
281-
pmb.convert_str_to_bond_object('Not_A_Bond()')
281+
df_management._DFManagement._convert_str_to_bond_object('Not_A_Bond()')
282282
with self.assertRaises(json.decoder.JSONDecodeError):
283-
pmb.convert_str_to_bond_object('HarmonicBond({invalid_json})')
283+
df_management._DFManagement._convert_str_to_bond_object('HarmonicBond({invalid_json})')
284284
with self.assertRaises(NotImplementedError):
285-
pmb.convert_str_to_bond_object('QuarticBond({"r_0": 1., "k": 1.})')
285+
df_management._DFManagement._convert_str_to_bond_object('QuarticBond({"r_0": 1., "k": 1.})')
286286

287287
# check bond keys
288-
self.assertEqual(pmb.find_bond_key('A', 'A'), 'A-A')
289-
self.assertEqual(pmb.find_bond_key('B', 'B'), 'B-B')
290-
self.assertEqual(pmb.find_bond_key('A', 'A', use_default_bond=True), 'A-A')
291-
self.assertEqual(pmb.find_bond_key('Z', 'Z', use_default_bond=True), 'default')
292-
self.assertIsNone(pmb.find_bond_key('A', 'B'))
293-
self.assertIsNone(pmb.find_bond_key('B', 'A'))
294-
self.assertIsNone(pmb.find_bond_key('Z', 'Z'))
295-
self.assertEqual(pmb.find_bond_key('A', 'B', use_default_bond=True), 'default')
296-
288+
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'A'), 'A-A')
289+
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'B', particle_name2 = 'B'), 'B-B')
290+
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'A', use_default_bond=True), 'A-A')
291+
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'Z', particle_name2 = 'Z', use_default_bond=True), 'default')
292+
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'B'))
293+
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'B', particle_name2 = 'A'))
294+
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'Z', particle_name2 = 'Z'))
295+
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'B', use_default_bond=True), 'default')
296+
297297
self.assertIsNone(pmb.search_bond('A', 'B', hard_check=False))
298298
log_contents = log_stream.getvalue()
299299
self.assertIn("Bond not defined between particles A and B", log_contents)
@@ -305,12 +305,16 @@ def test_bond_framework(self):
305305
pmb.search_bond('A', 'B' , hard_check=True)
306306

307307
# check invalid bond index
308-
pmb.add_value_to_df(key=('particle_id',''), new_value=10,
309-
index=np.where(pmb.df['name']=='A')[0][0])
310-
pmb.add_value_to_df(key=('particle_id',''), new_value=20,
311-
index=np.where(pmb.df['name']=='B')[0][0])
312-
self.assertIsNone(pmb.add_bond_in_df(10, 20, use_default_bond=False))
313-
self.assertIsNone(pmb.add_bond_in_df(10, 20, use_default_bond=True))
308+
df_management._DFManagement._add_value_to_df(df = pmb.df,
309+
key = ('particle_id',''),
310+
new_value = 10,
311+
index = np.where(pmb.df['name']=='A')[0][0])
312+
df_management._DFManagement._add_value_to_df(df = pmb.df,
313+
key = ('particle_id',''),
314+
new_value = 20,
315+
index = np.where(pmb.df['name']=='B')[0][0])
316+
self.assertIsNone(df_management._DFManagement._add_bond_in_df(pmb.df, 10, 20, use_default_bond=False))
317+
self.assertIsNone(df_management._DFManagement._add_bond_in_df(pmb.df, 10, 20, use_default_bond=True))
314318

315319
# check bond lengths
316320
self.assertAlmostEqual(pmb.get_bond_length('A', 'A'),

testsuite/charge_number_map_tests.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# Import pyMBE and other libraries
2020
import pyMBE
2121
import numpy as np
22+
import pyMBE.storage.df_management as df_management
2223

2324
# Create an instance of pyMBE library
2425
pmb = pyMBE.pymbe_library(seed=42)
@@ -49,7 +50,7 @@ def check_charge_number_map(input_parameters):
4950
print("*** get_charge_number_map unit tests ***")
5051
print("*** Unit test: check that get_charge_number_map works correctly for inert particles***")
5152
# Clean pmb.df
52-
pmb.setup_df()
53+
pmb.df = df_management._DFManagement._setup_df()
5354
input_parameters={"name":"I",
5455
"acidity": "inert",
5556
"pka": np.nan,
@@ -60,7 +61,7 @@ def check_charge_number_map(input_parameters):
6061
print("*** Unit test passed ***")
6162
print("*** Unit test: check that get_charge_number_map works correctly for acidic particles***")
6263
# Clean pmb.df
63-
pmb.setup_df()
64+
pmb.df = df_management._DFManagement._setup_df()
6465
input_parameters={"name":"A",
6566
"acidity": "acidic",
6667
"pka":4}
@@ -70,7 +71,7 @@ def check_charge_number_map(input_parameters):
7071
print("*** Unit test passed ***")
7172
print("*** Unit test: check that get_charge_number_map works correctly for basic particles***")
7273
# Clean pmb.df
73-
pmb.setup_df()
74+
pmb.df = df_management._DFManagement._setup_df()
7475
input_parameters={"name":"B",
7576
"acidity": "basic",
7677
"pka":4}

testsuite/lj_tests.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
# Import pyMBE and other libraries
2020
import pyMBE
21+
import pyMBE.storage.df_management as df_management
2122
import numpy as np
22-
2323
import logging
2424
import io
2525
# Create an in-memory log stream
@@ -48,7 +48,7 @@
4848
print("*** Unit test passed ***")
4949
print("*** Unit test: check that `offset` defaults to 0***")
5050
# Clean pmb.df
51-
pmb.setup_df()
51+
pmb.df = df_management._DFManagement._setup_df()
5252
# Define dummy particle
5353
pmb.define_particle(name="A")
5454

@@ -59,7 +59,7 @@
5959

6060
print("*** Unit test: check that `cutoff` defaults to `2**(1./6.) reduced_length` ***")
6161
# Clean pmb.df
62-
pmb.setup_df()
62+
pmb.df = df_management._DFManagement._setup_df()
6363
# Define dummy particle
6464
pmb.define_particle(name="A")
6565

@@ -95,7 +95,7 @@
9595
print("*** Unit test: test that setup_lj_interactions sets up inert particles correctly ***")
9696

9797
# Clean pmb.df
98-
pmb.setup_df()
98+
pmb.df = df_management._DFManagement._setup_df()
9999
# Define particles
100100
A_input_parameters={"name":"A",
101101
"sigma":1*pmb.units.nm,
@@ -130,7 +130,8 @@
130130
log_contents = log_stream.getvalue()
131131
assert "The following particles do not have a defined value of sigma or epsilon" in log_contents
132132

133-
pmb.delete_entries_in_df("X")
133+
df_management._DFManagement._delete_entries_in_df(df=pmb.df,
134+
entry_name="X")
134135

135136
# ValueError if combining-rule other than Lorentz_-Berthelot is used
136137
input_params = {"espresso_system":espresso_system, "combining_rule": "Geometric"}

testsuite/parameter_test.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import pyMBE
2121
import pandas as pd
2222
import numpy as np
23+
import pyMBE.storage.df_management as df_management
2324

2425
pmb = pyMBE.pymbe_library(seed=42)
2526

@@ -41,7 +42,7 @@
4142
path_to_pka=pmb.root / "parameters" / "pka_sets" / "Hass2015.json"
4243

4344
# First order of loading parameters
44-
pmb.setup_df() # clear the pmb_df
45+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
4546
pmb.load_interaction_parameters (filename=peptides_root / "Lunkad2021.json")
4647
pmb.load_pka_set(filename=pka_root / "Hass2015.json")
4748
df_1 = pmb.df.copy()
@@ -51,7 +52,7 @@
5152
# Drop bond_object (assert_frame_equal does not process it well)
5253
df_1 = df_1.sort_index(axis=1).drop(labels="bond_object", axis=1)
5354
# Second order of loading parameters
54-
pmb.setup_df() # clear the pmb_df
55+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
5556
pmb.load_pka_set (filename=path_to_pka)
5657
#print(pmb.df["acidity"])
5758
pmb.load_interaction_parameters(filename=path_to_interactions)
@@ -70,7 +71,7 @@
7071
print("*** Test passed ***")
7172

7273
print("*** Unit test: check that load_interaction_parameters loads FENE bonds correctly ***")
73-
pmb.setup_df() # clear the pmb_df
74+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
7475
pmb.load_interaction_parameters (filename=data_root / "test_FENE.json")
7576

7677
expected_parameters = {'r_0' : 0.4*pmb.units.nm,
@@ -89,7 +90,7 @@
8990
print("*** Test passed ***")
9091
print("*** Unit test: check that load_interaction_parameters loads residue, molecule and peptide objects correctly ***")
9192

92-
pmb.setup_df() # clear the pmb_df
93+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
9394
pmb.load_interaction_parameters (filename=data_root / "test_molecules.json")
9495

9596
expected_residue_parameters={"central_bead": "A", "side_chains": ["B","C"] }
@@ -117,12 +118,12 @@
117118
verbose=True)
118119
print("*** Test passed ***")
119120
print("*** Unit test: check that load_interaction_parameters raises a ValueError if one loads a data set with an unknown pmb_type ***")
120-
pmb.setup_df() # clear the pmb_df
121+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
121122
input_parameters={"filename": data_root / "test_non_valid_object.json"}
122123
np.testing.assert_raises(ValueError, pmb.load_interaction_parameters, **input_parameters)
123124
print("*** Test passed ***")
124125
print("*** Unit test: check that load_interaction_parameters raises a ValueError if one loads a bond not supported by pyMBE ***")
125-
pmb.setup_df() # clear the pmb_df
126+
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
126127
input_parameters={"filename": data_root / "test_non_valid_bond.json"}
127128
np.testing.assert_raises(ValueError, pmb.load_interaction_parameters, **input_parameters)
128129
print("*** Test passed ***")

testsuite/read-write-df_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import numpy as np
2323
import logging
2424
import io
25+
import pyMBE.storage.df_management as df_management
2526

2627
# Create an in-memory log stream
2728
log_stream = io.StringIO()
@@ -168,5 +169,5 @@
168169

169170
# Test that copy_df_entry raises an error if one provides a non-valid column name
170171
print("*** Unit test: check that copy_df_entry raises an error if the entry does not exist ***")
171-
np.testing.assert_raises(ValueError, pmb.copy_df_entry, name='test', column_name='non_existing_column',number_of_copies=1)
172+
np.testing.assert_raises(ValueError, df_management._DFManagement._copy_df_entry, df = pmb.df, name='test', column_name='non_existing_column',number_of_copies=1)
172173
print("*** Unit test passed***")

testsuite/serialization_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@
2323
import pyMBE
2424
import pyMBE.lib.analysis
2525
import scipy.constants
26-
26+
import pyMBE.storage.df_management as df_management
2727

2828
class Serialization(ut.TestCase):
2929

3030
def test_json_encoder(self):
31-
encoder = pyMBE.pymbe_library.NumpyEncoder
31+
encoder = df_management._DFManagement._NumpyEncoder
3232
# Python types
3333
self.assertEqual(json.dumps(1, cls=encoder), "1")
3434
self.assertEqual(json.dumps([1, 2], cls=encoder), "[1, 2]")

testsuite/set_particle_acidity_test.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import numpy as np
2121
import pandas as pd
2222
import pyMBE
23+
import pyMBE.storage.df_management as df_management
2324

2425
# Create an instance of pyMBE library
2526
pmb = pyMBE.pymbe_library(seed=42)
@@ -71,7 +72,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
7172
print("*** Particle acidity unit tests ***")
7273
print("*** Unit test: check that all acid/base input parameters in define_particle for an inert particle are correctly stored in pmb.df***")
7374
# Clean pmb.df
74-
pmb.setup_df()
75+
pmb.df = df_management._DFManagement._setup_df()
7576
input_parameters={"name":"I",
7677
"acidity": pd.NA,
7778
"pka": pd.NA,
@@ -87,7 +88,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
8788
print("*** Unit test passed ***")
8889
print("*** Unit test: check that a deprecation warning is raised if the keyword 'inert' is used for acidity ***")
8990
# Clean pmb.df
90-
pmb.setup_df()
91+
pmb.df = df_management._DFManagement._setup_df()
9192
input_parameters={"name":"I",
9293
"acidity": "inert",
9394
"pka": pd.NA,
@@ -96,7 +97,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
9697
print("*** Unit test passed ***")
9798
print("*** Unit test: check that all acid/base input parameters in define_particle for an acid are correctly stored in pmb.df***")
9899
# Clean pmb.df
99-
pmb.setup_df()
100+
pmb.df = df_management._DFManagement._setup_df()
100101
input_parameters={"name":"A",
101102
"acidity": "acidic",
102103
"pka":4}
@@ -110,7 +111,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
110111
print("*** Unit test passed ***")
111112
print("*** Unit test: check that all acid/base input parameters in define_particle for a base are correctly stored in pmb.df***")
112113
# Clean pmb.df
113-
pmb.setup_df()
114+
pmb.df = df_management._DFManagement._setup_df()
114115
input_parameters={"name":"B",
115116
"acidity": "basic",
116117
"pka":9}

0 commit comments

Comments
 (0)