diff --git a/.github/workflows/project-ci.yaml b/.github/workflows/project-ci.yaml index ea94844..e397426 100644 --- a/.github/workflows/project-ci.yaml +++ b/.github/workflows/project-ci.yaml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-24.04, windows-2025, macos-15] - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout repo uses: actions/checkout@v5.0.0 @@ -40,10 +40,10 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v5.0.0 - - name: Set up Python 3.13 + - name: Set up Python 3.14 uses: actions/setup-python@v6.0.0 with: - python-version: 3.13 + python-version: 3.14 - name: Install python dependencies run: | pip install --upgrade pip @@ -56,10 +56,10 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v5.0.0 - - name: Set up Python 3.13 + - name: Set up Python 3.14 uses: actions/setup-python@v6.0.0 with: - python-version: 3.13 + python-version: 3.14 - name: Install python dependencies run: | pip install --upgrade pip @@ -75,7 +75,7 @@ jobs: timeout-minutes: 15 strategy: matrix: - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] mdanalysis-version: ["2.9.0", "latest"] name: MDAnalysis Compatibility Tests steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index ef622a5..8185b8f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,7 +23,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6.0.0 with: - python-version: 3.13 + python-version: 3.14 - name: Get latest release from pip id: latestreleased @@ -133,7 +133,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6.0.0 with: - python-version: 3.13 + python-version: 3.14 - name: Install flit run: | diff --git a/CodeEntropy/main.py b/CodeEntropy/main.py index f88ed62..3bf24ee 100644 --- a/CodeEntropy/main.py +++ b/CodeEntropy/main.py @@ -25,4 +25,4 @@ def main(): if __name__ == "__main__": - main() + main() # pragma: no cover diff --git a/pyproject.toml b/pyproject.toml index 7900c86..a864344 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Natural Language :: English", @@ -37,15 +38,15 @@ classifiers = [ keywords = ["entropy", "macromolecular systems", "MD simulation"] requires-python = ">=3.11" dependencies = [ - "numpy==2.2.3", - "mdanalysis>=2.9.0", - "pandas==2.2.3", - "psutil==5.9.5", - "PyYAML==6.0.2", - "python-json-logger==3.3.0", - "rich==14.0.0", + "numpy==2.3.4", + "mdanalysis>=2.10.0", + "pandas==2.3.3", + "psutil==7.1.3", + "PyYAML==6.0.3", + "python-json-logger==4.0.0", + "rich==14.2.0", "art==6.5", - "waterEntropy==1.2.0", + "waterEntropy==1.2.2", "requests>=2.32.5", ] @@ -56,14 +57,14 @@ Documentation = "https://codeentropy.readthedocs.io" [project.optional-dependencies] testing = [ - "pytest==8.2.2", - "pytest-cov==5.0.0", - "pytest-sugar==1.0.0" + "pytest>=8.4.2", + "pytest-cov>=7.0.0", + "pytest-sugar>=1.1.1" ] pre-commit = [ - "pre-commit==3.7.1", - "pylint==3.2.5" + "pre-commit>=4.3.0", + "pylint>=4.0.0" ] docs = [ "sphinx", diff --git a/tests/test_CodeEntropy/test_data_logger.py b/tests/test_CodeEntropy/test_data_logger.py index 9e679c8..9d657f1 100644 --- a/tests/test_CodeEntropy/test_data_logger.py +++ b/tests/test_CodeEntropy/test_data_logger.py @@ -115,7 +115,6 @@ def test_save_dataframes_as_json(self): def test_log_tables_rich_output(self): console = LoggingConfig.get_console() - console.clear_live() self.logger.add_results_data( 0, "united_atom", "Transvibrational", 653.4041220313459 diff --git a/tests/test_CodeEntropy/test_entropy.py b/tests/test_CodeEntropy/test_entropy.py index 22f9af8..e6c09a1 100644 --- a/tests/test_CodeEntropy/test_entropy.py +++ b/tests/test_CodeEntropy/test_entropy.py @@ -1657,6 +1657,51 @@ def test_assign_conformation(self): assert np.all(result >= 0) assert np.issubdtype(result.dtype, np.floating) + def test_assign_conformation_last_bin_peak(self): + """ + Test that the last bin in the histogram is correctly evaluated as a peak + when its population is greater than or equal to its neighbors. + """ + + dihedral = MagicMock() + dihedral.value = MagicMock(side_effect=[5, 10, 250, 260, 350, 355]) + + # Mock trajectory frames + mock_timesteps = [MagicMock(frame=i) for i in range(6)] + data_container = MagicMock() + data_container.trajectory.__getitem__.return_value = mock_timesteps + + # Create dummy universe and managers + tprfile = os.path.join(self.test_data_dir, "md_A4_dna.tpr") + trrfile = os.path.join(self.test_data_dir, "md_A4_dna_xf.trr") + u = mda.Universe(tprfile, trrfile) + + args = MagicMock(bin_width=60, temperature=300, selection_string="all") + run_manager = RunManager("mock_folder/job001") + level_manager = LevelManager() + data_logger = DataLogger() + group_molecules = MagicMock() + + ce = ConformationalEntropy( + run_manager, args, u, data_logger, level_manager, group_molecules + ) + + result = ce.assign_conformation( + data_container=data_container, + dihedral=dihedral, + number_frames=6, + bin_width=60, + start=0, + end=6, + step=1, + ) + + # Basic checks + assert isinstance(result, np.ndarray) + assert len(result) == 6 + assert np.all(result >= 0) + assert np.issubdtype(result.dtype, np.floating) + def test_conformational_entropy_calculation(self): """ Test `conformational_entropy_calculation` method to verify diff --git a/tests/test_CodeEntropy/test_levels.py b/tests/test_CodeEntropy/test_levels.py index 03e739c..0779fdc 100644 --- a/tests/test_CodeEntropy/test_levels.py +++ b/tests/test_CodeEntropy/test_levels.py @@ -1065,6 +1065,73 @@ def test_update_force_torque_matrices_united_atom(self): np.testing.assert_array_equal(torque_avg["ua"][key], t_mat_mock) self.assertEqual(frame_counts["ua"][key], 1) + def test_update_force_torque_matrices_united_atom_increment(self): + """ + Test that `update_force_torque_matrices` correctly updates force and torque + matrices for the 'united_atom' level when the key already exists. + """ + level_manager = LevelManager() + entropy_manager = MagicMock() + mol = MagicMock() + + # Simulate one residue with two atoms + residue = MagicMock() + residue.atoms.indices = [0, 1] + mol.residues = [residue] + mol.trajectory.__getitem__.return_value = None + + selected_atoms = MagicMock() + entropy_manager._run_manager.new_U_select_atom.return_value = selected_atoms + selected_atoms.trajectory.__getitem__.return_value = None + + f_mat_1 = np.array([[1.0]], dtype=np.float64) + t_mat_1 = np.array([[2.0]], dtype=np.float64) + f_mat_2 = np.array([[3.0]], dtype=np.float64) + t_mat_2 = np.array([[4.0]], dtype=np.float64) + + level_manager.get_matrices = MagicMock(return_value=(f_mat_1, t_mat_1)) + + force_avg = {"ua": {}, "res": [None], "poly": [None]} + torque_avg = {"ua": {}, "res": [None], "poly": [None]} + frame_counts = {"ua": {}, "res": [None], "poly": [None]} + + # First call: initialize + level_manager.update_force_torque_matrices( + entropy_manager=entropy_manager, + mol=mol, + group_id=0, + level="united_atom", + level_list=["residue", "united_atom"], + time_index=0, + num_frames=10, + force_avg=force_avg, + torque_avg=torque_avg, + frame_counts=frame_counts, + ) + + # Second call: update + level_manager.get_matrices = MagicMock(return_value=(f_mat_2, t_mat_2)) + + level_manager.update_force_torque_matrices( + entropy_manager=entropy_manager, + mol=mol, + group_id=0, + level="united_atom", + level_list=["residue", "united_atom"], + time_index=1, + num_frames=10, + force_avg=force_avg, + torque_avg=torque_avg, + frame_counts=frame_counts, + ) + + expected_force = f_mat_1 + (f_mat_2 - f_mat_1) / 2 + expected_torque = t_mat_1 + (t_mat_2 - t_mat_1) / 2 + + np.testing.assert_array_almost_equal(force_avg["ua"][(0, 0)], expected_force) + np.testing.assert_array_almost_equal(torque_avg["ua"][(0, 0)], expected_torque) + self.assertEqual(frame_counts["ua"][(0, 0)], 2) + def test_update_force_torque_matrices_residue(self): """ Test that `update_force_torque_matrices` correctly updates force and torque