Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed
- 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)
- `define_particle_entry_in_df` is now a private method in pyMBE, as it is a convenience method for internal use. (#145)
- 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)

## [1.0.0] - 2025-10-08

### Changed
Expand Down
1,068 changes: 362 additions & 706 deletions pyMBE/pyMBE.py

Large diffs are not rendered by default.

483 changes: 483 additions & 0 deletions pyMBE/storage/df_management.py

Large diffs are not rendered by default.

48 changes: 26 additions & 22 deletions testsuite/bond_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import json
import io
import logging

import pyMBE.storage.df_management as df_management

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

def setUp(self):
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()

def check_bond_setup(self, bond_object, input_parameters, bond_type):
"""
Expand Down Expand Up @@ -91,7 +91,7 @@ def test_bond_harmonic(self):
# check bond deserialization
bond_params = bond_object.get_params()
bond_params["bond_id"] = bond_object._bond_id
deserialized = pmb.convert_str_to_bond_object(
deserialized = df_management._DFManagement._convert_str_to_bond_object(
f'{bond_object.__class__.__name__}({json.dumps(bond_params)})')
self.check_bond_setup(bond_object=deserialized,
input_parameters=bond,
Expand Down Expand Up @@ -130,7 +130,7 @@ def test_bond_fene(self):
# check bond deserialization
bond_params = bond_object.get_params()
bond_params["bond_id"] = bond_object._bond_id
deserialized = pmb.convert_str_to_bond_object(
deserialized = df_management._DFManagement._convert_str_to_bond_object(
f'{bond_object.__class__.__name__}({json.dumps(bond_params)})')
self.check_bond_setup(bond_object=deserialized,
input_parameters=bond,
Expand Down Expand Up @@ -278,22 +278,22 @@ def test_bond_framework(self):

# check deserialization exceptions
with self.assertRaises(ValueError):
pmb.convert_str_to_bond_object('Not_A_Bond()')
df_management._DFManagement._convert_str_to_bond_object('Not_A_Bond()')
with self.assertRaises(json.decoder.JSONDecodeError):
pmb.convert_str_to_bond_object('HarmonicBond({invalid_json})')
df_management._DFManagement._convert_str_to_bond_object('HarmonicBond({invalid_json})')
with self.assertRaises(NotImplementedError):
pmb.convert_str_to_bond_object('QuarticBond({"r_0": 1., "k": 1.})')
df_management._DFManagement._convert_str_to_bond_object('QuarticBond({"r_0": 1., "k": 1.})')

# check bond keys
self.assertEqual(pmb.find_bond_key('A', 'A'), 'A-A')
self.assertEqual(pmb.find_bond_key('B', 'B'), 'B-B')
self.assertEqual(pmb.find_bond_key('A', 'A', use_default_bond=True), 'A-A')
self.assertEqual(pmb.find_bond_key('Z', 'Z', use_default_bond=True), 'default')
self.assertIsNone(pmb.find_bond_key('A', 'B'))
self.assertIsNone(pmb.find_bond_key('B', 'A'))
self.assertIsNone(pmb.find_bond_key('Z', 'Z'))
self.assertEqual(pmb.find_bond_key('A', 'B', use_default_bond=True), 'default')
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'A'), 'A-A')
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'B', particle_name2 = 'B'), 'B-B')
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'A', use_default_bond=True), 'A-A')
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'Z', particle_name2 = 'Z', use_default_bond=True), 'default')
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'B'))
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'B', particle_name2 = 'A'))
self.assertIsNone(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'Z', particle_name2 = 'Z'))
self.assertEqual(df_management._DFManagement._find_bond_key(df = pmb.df, particle_name1 = 'A', particle_name2 = 'B', use_default_bond=True), 'default')

self.assertIsNone(pmb.search_bond('A', 'B', hard_check=False))
log_contents = log_stream.getvalue()
self.assertIn("Bond not defined between particles A and B", log_contents)
Expand All @@ -305,12 +305,16 @@ def test_bond_framework(self):
pmb.search_bond('A', 'B' , hard_check=True)

# check invalid bond index
pmb.add_value_to_df(key=('particle_id',''), new_value=10,
index=np.where(pmb.df['name']=='A')[0][0])
pmb.add_value_to_df(key=('particle_id',''), new_value=20,
index=np.where(pmb.df['name']=='B')[0][0])
self.assertIsNone(pmb.add_bond_in_df(10, 20, use_default_bond=False))
self.assertIsNone(pmb.add_bond_in_df(10, 20, use_default_bond=True))
df_management._DFManagement._add_value_to_df(df = pmb.df,
key = ('particle_id',''),
new_value = 10,
index = np.where(pmb.df['name']=='A')[0][0])
df_management._DFManagement._add_value_to_df(df = pmb.df,
key = ('particle_id',''),
new_value = 20,
index = np.where(pmb.df['name']=='B')[0][0])
self.assertIsNone(df_management._DFManagement._add_bond_in_df(pmb.df, 10, 20, use_default_bond=False))
self.assertIsNone(df_management._DFManagement._add_bond_in_df(pmb.df, 10, 20, use_default_bond=True))

# check bond lengths
self.assertAlmostEqual(pmb.get_bond_length('A', 'A'),
Expand Down
7 changes: 4 additions & 3 deletions testsuite/charge_number_map_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Import pyMBE and other libraries
import pyMBE
import numpy as np
import pyMBE.storage.df_management as df_management

# Create an instance of pyMBE library
pmb = pyMBE.pymbe_library(seed=42)
Expand Down Expand Up @@ -49,7 +50,7 @@ def check_charge_number_map(input_parameters):
print("*** get_charge_number_map unit tests ***")
print("*** Unit test: check that get_charge_number_map works correctly for inert particles***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"I",
"acidity": "inert",
"pka": np.nan,
Expand All @@ -60,7 +61,7 @@ def check_charge_number_map(input_parameters):
print("*** Unit test passed ***")
print("*** Unit test: check that get_charge_number_map works correctly for acidic particles***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"A",
"acidity": "acidic",
"pka":4}
Expand All @@ -70,7 +71,7 @@ def check_charge_number_map(input_parameters):
print("*** Unit test passed ***")
print("*** Unit test: check that get_charge_number_map works correctly for basic particles***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"B",
"acidity": "basic",
"pka":4}
Expand Down
11 changes: 6 additions & 5 deletions testsuite/lj_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

# Import pyMBE and other libraries
import pyMBE
import pyMBE.storage.df_management as df_management
import numpy as np

import logging
import io
# Create an in-memory log stream
Expand Down Expand Up @@ -48,7 +48,7 @@
print("*** Unit test passed ***")
print("*** Unit test: check that `offset` defaults to 0***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
# Define dummy particle
pmb.define_particle(name="A")

Expand All @@ -59,7 +59,7 @@

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

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

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

pmb.delete_entries_in_df("X")
df_management._DFManagement._delete_entries_in_df(df=pmb.df,
entry_name="X")

# ValueError if combining-rule other than Lorentz_-Berthelot is used
input_params = {"espresso_system":espresso_system, "combining_rule": "Geometric"}
Expand Down
13 changes: 7 additions & 6 deletions testsuite/parameter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import pyMBE
import pandas as pd
import numpy as np
import pyMBE.storage.df_management as df_management

pmb = pyMBE.pymbe_library(seed=42)

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

# First order of loading parameters
pmb.setup_df() # clear the pmb_df
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
pmb.load_interaction_parameters (filename=peptides_root / "Lunkad2021.json")
pmb.load_pka_set(filename=pka_root / "Hass2015.json")
df_1 = pmb.df.copy()
Expand All @@ -51,7 +52,7 @@
# Drop bond_object (assert_frame_equal does not process it well)
df_1 = df_1.sort_index(axis=1).drop(labels="bond_object", axis=1)
# Second order of loading parameters
pmb.setup_df() # clear the pmb_df
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
pmb.load_pka_set (filename=path_to_pka)
#print(pmb.df["acidity"])
pmb.load_interaction_parameters(filename=path_to_interactions)
Expand All @@ -70,7 +71,7 @@
print("*** Test passed ***")

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

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

pmb.setup_df() # clear the pmb_df
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
pmb.load_interaction_parameters (filename=data_root / "test_molecules.json")

expected_residue_parameters={"central_bead": "A", "side_chains": ["B","C"] }
Expand Down Expand Up @@ -117,12 +118,12 @@
verbose=True)
print("*** Test passed ***")
print("*** Unit test: check that load_interaction_parameters raises a ValueError if one loads a data set with an unknown pmb_type ***")
pmb.setup_df() # clear the pmb_df
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
input_parameters={"filename": data_root / "test_non_valid_object.json"}
np.testing.assert_raises(ValueError, pmb.load_interaction_parameters, **input_parameters)
print("*** Test passed ***")
print("*** Unit test: check that load_interaction_parameters raises a ValueError if one loads a bond not supported by pyMBE ***")
pmb.setup_df() # clear the pmb_df
pmb.df = df_management._DFManagement._setup_df() # clear the pmb_df
input_parameters={"filename": data_root / "test_non_valid_bond.json"}
np.testing.assert_raises(ValueError, pmb.load_interaction_parameters, **input_parameters)
print("*** Test passed ***")
Expand Down
3 changes: 2 additions & 1 deletion testsuite/read-write-df_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import numpy as np
import logging
import io
import pyMBE.storage.df_management as df_management

# Create an in-memory log stream
log_stream = io.StringIO()
Expand Down Expand Up @@ -168,5 +169,5 @@

# Test that copy_df_entry raises an error if one provides a non-valid column name
print("*** Unit test: check that copy_df_entry raises an error if the entry does not exist ***")
np.testing.assert_raises(ValueError, pmb.copy_df_entry, name='test', column_name='non_existing_column',number_of_copies=1)
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)
print("*** Unit test passed***")
4 changes: 2 additions & 2 deletions testsuite/serialization_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
import pyMBE
import pyMBE.lib.analysis
import scipy.constants

import pyMBE.storage.df_management as df_management

class Serialization(ut.TestCase):

def test_json_encoder(self):
encoder = pyMBE.pymbe_library.NumpyEncoder
encoder = df_management._DFManagement._NumpyEncoder
# Python types
self.assertEqual(json.dumps(1, cls=encoder), "1")
self.assertEqual(json.dumps([1, 2], cls=encoder), "[1, 2]")
Expand Down
9 changes: 5 additions & 4 deletions testsuite/set_particle_acidity_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import numpy as np
import pandas as pd
import pyMBE
import pyMBE.storage.df_management as df_management

# Create an instance of pyMBE library
pmb = pyMBE.pymbe_library(seed=42)
Expand Down Expand Up @@ -71,7 +72,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
print("*** Particle acidity unit tests ***")
print("*** Unit test: check that all acid/base input parameters in define_particle for an inert particle are correctly stored in pmb.df***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"I",
"acidity": pd.NA,
"pka": pd.NA,
Expand All @@ -87,7 +88,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
print("*** Unit test passed ***")
print("*** Unit test: check that a deprecation warning is raised if the keyword 'inert' is used for acidity ***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"I",
"acidity": "inert",
"pka": pd.NA,
Expand All @@ -96,7 +97,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
print("*** Unit test passed ***")
print("*** Unit test: check that all acid/base input parameters in define_particle for an acid are correctly stored in pmb.df***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"A",
"acidity": "acidic",
"pka":4}
Expand All @@ -110,7 +111,7 @@ def check_acid_base_setup(input_parameters, acidity_setup):
print("*** Unit test passed ***")
print("*** Unit test: check that all acid/base input parameters in define_particle for a base are correctly stored in pmb.df***")
# Clean pmb.df
pmb.setup_df()
pmb.df = df_management._DFManagement._setup_df()
input_parameters={"name":"B",
"acidity": "basic",
"pka":9}
Expand Down
24 changes: 18 additions & 6 deletions testsuite/test_in_out_pmb_df.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import unittest as ut
import logging
import re
import pyMBE.storage.df_management as df_management

# Create an in-memory log stream
log_stream = io.StringIO()
Expand Down Expand Up @@ -61,7 +62,10 @@ def test_add_to_df(self):
new_value='T2'
name=pmb.df.loc[index,key]
pmb_type=pmb.df.loc[index,('pmb_type','')]
pmb.add_value_to_df(index=index,key=key,new_value=new_value)
df_management._DFManagement._add_value_to_df(df = pmb.df,
index = index,
key = key,
new_value = new_value)
log_contents = log_stream.getvalue()
warning_msg1=f"You are attempting to redefine the properties of {name} of pmb_type {pmb_type}"
warning_msg2=f"pyMBE has preserved of the entry `{key}`: old_value = {old_value}. If you want to overwrite it with new_value = {new_value}, activate the switch overwrite = True"
Expand All @@ -76,7 +80,11 @@ def test_add_to_df(self):
new_value='T2'
name=pmb.df.loc[index,key]
pmb_type=pmb.df.loc[index,('pmb_type','')]
pmb.add_value_to_df(index=index,key=key,new_value=new_value,overwrite=True)
df_management._DFManagement._add_value_to_df(df = pmb.df,
index = index,
key = key,
new_value = new_value,
overwrite = True)
log_contents = log_stream.getvalue()
warning_msg1=f"You are attempting to redefine the properties of {name} of pmb_type {pmb_type}"
warning_msg2=f"Overwritting the value of the entry `{key}`: old_value = {old_value} new_value = {new_value}"
Expand All @@ -88,9 +96,11 @@ def test_add_to_df(self):
def test_delete_entries_df(self):
print("*** Unit test: test that entries in df are deleted properly using `delete_entries_in_df` ***")

pmb.delete_entries_in_df(entry_name="S1-S2")
pmb.df = df_management._DFManagement._delete_entries_in_df(df=pmb.df,
entry_name="S1-S2")
assert pmb.df[pmb.df["name"]=="S1-S2"].empty
pmb.delete_entries_in_df(entry_name="S1")
pmb.df = df_management._DFManagement._delete_entries_in_df(df=pmb.df,
entry_name="S1")
assert pmb.df[pmb.df["name"]=="S1"].empty

residue_parameters={"R1":{"name": "R1",
Expand All @@ -103,7 +113,8 @@ def test_delete_entries_df(self):
for parameter_set in residue_parameters.values():
pmb.define_residue(**parameter_set)

pmb.delete_entries_in_df(entry_name="R1")
pmb.df = df_management._DFManagement._delete_entries_in_df(df=pmb.df,
entry_name="R1")
assert pmb.df[pmb.df["name"]=="R1"].empty

molecule_parameters={"M1":{"name": "M1",
Expand All @@ -112,7 +123,8 @@ def test_delete_entries_df(self):
for parameter_set in molecule_parameters.values():
pmb.define_molecule(**parameter_set)

pmb.delete_entries_in_df(entry_name="M1")
pmb.df = df_management._DFManagement._delete_entries_in_df(df=pmb.df,
entry_name="M1")
assert pmb.df[pmb.df["name"]=="M1"].empty
print("*** Unit passed ***")

Expand Down