From b373c874c717e945e5add2f0aa678f3c4d7f74dd Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 09:41:13 +0200 Subject: [PATCH 1/8] Solving misterious problems... --- docs/source/_static/info_plot.png | Bin 145924 -> 145924 bytes src/dynsight/_internal/descriptors/misc.py | 7 +++++-- src/dynsight/_internal/vision/label_tool.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/info_plot.png b/docs/source/_static/info_plot.png index 96c9d0af45b6a6f784cea59d2c07cc313cb0b9a5..d21b384058c1d87f84f3f5ab53d197c78376411a 100644 GIT binary patch delta 49 zcmZqq!qM`DV}hrgnT|q6Nl8JmmA-y%Vo5 NDArray[np.float64]: """Computes the average alignment for all the atoms in a frame.""" phi_t = np.zeros(len(frame_vel)) @@ -114,7 +114,10 @@ def _compute_aver_align( continue # no self-counting, no neighbors with v = 0.0 alignments = np.array( - [1 - cosine(atom_i, frame_vel[j]) for j in valid_neighbors] + [ + 1 - cosine(np.array(atom_i), frame_vel[j]) + for j in valid_neighbors + ] ) phi_t[i] = np.mean(alignments) return phi_t diff --git a/src/dynsight/_internal/vision/label_tool.py b/src/dynsight/_internal/vision/label_tool.py index f3f2d62f..e17692b9 100644 --- a/src/dynsight/_internal/vision/label_tool.py +++ b/src/dynsight/_internal/vision/label_tool.py @@ -14,7 +14,7 @@ def log_message(self, fmt: str, *args: object) -> None: pass # do_POST must be uppercase - def do_POST(self) -> None: + def do_POST(self) -> None: # noqa: N802 if self.path == "/shutdown": self.send_response(200) self.end_headers() From 445063588e0924c21cda2530edaab96a8319fdc5 Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 09:46:39 +0200 Subject: [PATCH 2/8] Solved array shape issue in LENS. --- src/dynsight/_internal/lens/lens.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/dynsight/_internal/lens/lens.py b/src/dynsight/_internal/lens/lens.py index 7aba2acf..987066b6 100644 --- a/src/dynsight/_internal/lens/lens.py +++ b/src/dynsight/_internal/lens/lens.py @@ -108,13 +108,17 @@ def neighbour_change_in_time( Returns: tuple: - lens_array: The calculated LENS parameter. - It's a numpy.array of shape (n_particles, n_frames - 1) + It's a np.array of shape (n_particles, n_frames - delay + 1) - number_of_neighs: The count of neighbors per frame. - It's a numpy.array of shape (n_particles, n_frames) + It's a np.array of shape (n_particles, n_frames) - lens_numerators: The numerators used for calculating LENS. - It's a numpy.array of shape (n_particles, n_frames - 1) + It's a np.array of shape (n_particles, n_frames - delay + 1) - lens_denominators: The denominators used for calculating LENS. - It's a numpy.array of shape (n_particles, n_frames - 1) + It's a np.array of shape (n_particles, n_frames - delay + 1) + + Note: + The first frame of the output array is identically zero. This is due + to compatibility with older code. Example: @@ -151,10 +155,10 @@ def neighbour_change_in_time( n_atoms = np.asarray(neigh_list_per_frame, dtype=object).shape[1] n_frames = np.asarray(neigh_list_per_frame, dtype=object).shape[0] - lens_array = np.zeros((n_atoms, n_frames)) # The LENS values - number_of_neighs = np.zeros((n_atoms, n_frames), dtype=int) # The NN - lens_numerators = np.zeros((n_atoms, n_frames)) # LENS numerator - lens_denominators = np.zeros((n_atoms, n_frames)) # LENS denominator + lens_array = np.zeros((n_atoms, n_frames - delay + 1)) + number_of_neighs = np.zeros((n_atoms, n_frames), dtype=int) + lens_numerators = np.zeros((n_atoms, n_frames - delay + 1)) + lens_denominators = np.zeros((n_atoms, n_frames - delay + 1)) # each nnlist contains also the atom that generates them, # so 0 nn is a 1 element list From 5ba08cbcfbddd841a8577a4a387cdc0ff7f14adb Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 09:49:57 +0200 Subject: [PATCH 3/8] Solved bug in warning message. --- src/dynsight/_internal/trajectory/cluster_insight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynsight/_internal/trajectory/cluster_insight.py b/src/dynsight/_internal/trajectory/cluster_insight.py index 94594af8..99c7f6cc 100644 --- a/src/dynsight/_internal/trajectory/cluster_insight.py +++ b/src/dynsight/_internal/trajectory/cluster_insight.py @@ -446,7 +446,7 @@ def dump_colored_trj(self, trj: Trj, file_path: Path) -> None: if self.labels.shape != (n_atoms, n_frames): msg = ( f"Shape mismatch: Trj should have {self.labels.shape[0]} " - f"atoms, {self.labels.shape[0]} frames, but has {n_atoms} " + f"atoms, {self.labels.shape[1]} frames, but has {n_atoms} " f"atoms, {n_frames} frames." ) logger.log(msg) From db587181092f90bff7b5932ff28157e5a6283945 Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 10:15:11 +0200 Subject: [PATCH 4/8] New ruff shenanigans. --- docs/source/_static/recipes/descr_from_trj.py | 4 ++-- docs/source/_static/recipes/info_gain.py | 2 +- examples/lens.py | 2 +- examples/onion_analysis.py | 4 ++-- examples/sample_entropy.py | 2 +- examples/video_to_trajectory.py | 2 +- src/dynsight/_internal/analysis/entropy.py | 2 +- src/dynsight/_internal/vision/label_tool.py | 2 +- tests/analysis/test_sample_entropy.py | 2 +- tests/trajectory/test_trj.py | 6 +++--- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/_static/recipes/descr_from_trj.py b/docs/source/_static/recipes/descr_from_trj.py index 01cba368..8ffe130d 100644 --- a/docs/source/_static/recipes/descr_from_trj.py +++ b/docs/source/_static/recipes/descr_from_trj.py @@ -32,14 +32,14 @@ def main() -> None: ) # Computing number of neighbors from scratch - neigcounts, n_neig = trj.get_coord_number( + neigcounts, _n_neig = trj.get_coord_number( r_cut=2.0, # cutoff radius for neighbors list selection="all", # compute on a selection of particles neigcounts=None, # it will be computed and returned ) # Now for LENS we already have neigcounts - _, lens = trj.get_lens( + _, _lens = trj.get_lens( r_cut=2.0, # cutoff radius for neighbors list selection="all", # compute on a selection of particles neigcounts=neigcounts, # no need to compute it again diff --git a/docs/source/_static/recipes/info_gain.py b/docs/source/_static/recipes/info_gain.py index 873839a0..3321713f 100644 --- a/docs/source/_static/recipes/info_gain.py +++ b/docs/source/_static/recipes/info_gain.py @@ -161,7 +161,7 @@ def main() -> None: _, n_frames = data.shape delta_t_list = np.unique(np.geomspace(2, n_frames, 10, dtype=int)) - n_cl, cl_frac, info_gain, cl_entr, h_0 = info_gain_with_onion( + _n_cl, cl_frac, _info_gain, cl_entr, h_0 = info_gain_with_onion( delta_t_list, data, ) diff --git a/examples/lens.py b/examples/lens.py index 25fc2004..129c43cd 100644 --- a/examples/lens.py +++ b/examples/lens.py @@ -66,7 +66,7 @@ def main() -> None: input_universe=universe, cutoff=cutoff, ) - lens, nn, *_ = dynsight.lens.neighbour_change_in_time(neigcounts) + lens, _nn, *_ = dynsight.lens.neighbour_change_in_time(neigcounts) fig, axes = plt.subplots(2, sharey=True) for i in range(4): diff --git a/examples/onion_analysis.py b/examples/onion_analysis.py index a230a596..529ae8dd 100644 --- a/examples/onion_analysis.py +++ b/examples/onion_analysis.py @@ -98,7 +98,7 @@ def main() -> None: coord_1d = Insight(coord_2d.dataset[:, :, 0]) # Test onion clustering on a wide range of time resolutions - delta_t_list, n_clust, unclass_frac = coord_1d.get_onion_analysis( + _delta_t_list, _n_clust, _unclass_frac = coord_1d.get_onion_analysis( fig1_path=data_path / "time-res_1d.png", fig2_path=data_path / "pop_fracs_1d.png", ) @@ -110,7 +110,7 @@ def main() -> None: onion_results.plot_state_populations(data_path / "state_pops_1d.png") # Test onion clustering on a wide range of time resolutions - delta_t_list, n_clust, unclass_frac = coord_2d.get_onion_analysis( + _delta_t_list, _n_clust, _unclass_frac = coord_2d.get_onion_analysis( fig1_path=data_path / "time-res_2d.png", fig2_path=data_path / "pop_fracs_2d.png", ) diff --git a/examples/sample_entropy.py b/examples/sample_entropy.py index d4101217..0efa9746 100644 --- a/examples/sample_entropy.py +++ b/examples/sample_entropy.py @@ -141,7 +141,7 @@ def main() -> None: fractions = [] for _, delta_t in enumerate(delta_t_list): reshaped_data = dynsight.onion.helpers.reshape_from_nt(data, delta_t) - state_list, labels = dynsight.onion.onion_uni(reshaped_data) + _, labels = dynsight.onion.onion_uni(reshaped_data) tmp_list = [] tmp_frac = [] diff --git a/examples/video_to_trajectory.py b/examples/video_to_trajectory.py index 97d0dfaf..7903191e 100644 --- a/examples/video_to_trajectory.py +++ b/examples/video_to_trajectory.py @@ -30,7 +30,7 @@ def plot_results( n_detections = [len(result) for result in instance.prediction_results] - fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) + _, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) ax1.plot(n_detections, marker="o") ax1.set_title("N° Detections in Time") diff --git a/src/dynsight/_internal/analysis/entropy.py b/src/dynsight/_internal/analysis/entropy.py index 878a63d9..30790072 100644 --- a/src/dynsight/_internal/analysis/entropy.py +++ b/src/dynsight/_internal/analysis/entropy.py @@ -216,7 +216,7 @@ def compute_shannon_multi( if data.size == 0: msg = "data is empty" raise ValueError(msg) - n_points, n_dims = data.shape + _, n_dims = data.shape if n_dims != len(data_ranges) or n_dims != len(n_bins): msg = "Mismatch between data dimensions, data_ranges, and n_bins" raise ValueError(msg) diff --git a/src/dynsight/_internal/vision/label_tool.py b/src/dynsight/_internal/vision/label_tool.py index e17692b9..f3f2d62f 100644 --- a/src/dynsight/_internal/vision/label_tool.py +++ b/src/dynsight/_internal/vision/label_tool.py @@ -14,7 +14,7 @@ def log_message(self, fmt: str, *args: object) -> None: pass # do_POST must be uppercase - def do_POST(self) -> None: # noqa: N802 + def do_POST(self) -> None: if self.path == "/shutdown": self.send_response(200) self.end_headers() diff --git a/tests/analysis/test_sample_entropy.py b/tests/analysis/test_sample_entropy.py index 65d82c44..efd68a07 100644 --- a/tests/analysis/test_sample_entropy.py +++ b/tests/analysis/test_sample_entropy.py @@ -40,7 +40,7 @@ def test_too_short( def test_too_small_rfact(random_data: NDArray[np.float64]) -> None: """Test that a too small r_factor raises a RuntimeError.""" - with pytest.raises(RuntimeError, match="No matching sequences found."): + with pytest.raises(RuntimeError, match=r"No matching sequences found."): dynsight.analysis.sample_entropy(random_data, r_factor=0.0) diff --git a/tests/trajectory/test_trj.py b/tests/trajectory/test_trj.py index 6522bb2d..5480cbdb 100644 --- a/tests/trajectory/test_trj.py +++ b/tests/trajectory/test_trj.py @@ -161,19 +161,19 @@ def test_onion_analysis(universe: MDAnalysis.Universe) -> None: def test_insight_load_errors(file_paths: dict[str, Path]) -> None: """Test insight load errors.""" with pytest.raises( - ValueError, match="'dataset_file' key not found in JSON file." + ValueError, match=r"'dataset_file' key not found in JSON file." ): _ = Insight.load_from_json(file_paths["files_dir"] / "empty.json") with pytest.raises( - ValueError, match="'labels_file' key not found in JSON file." + ValueError, match=r"'labels_file' key not found in JSON file." ): _ = ClusterInsight.load_from_json( file_paths["files_dir"] / "ins_1_test.json" ) with pytest.raises( - ValueError, match="'reshaped_data_file' key not found in JSON file." + ValueError, match=r"'reshaped_data_file' key not found in JSON file." ): _ = OnionInsight.load_from_json( file_paths["files_dir"] / "cl_ins_test.json" From a6725464c6dfa1f327ebb638accf2b4196dfb5cc Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 10:32:20 +0200 Subject: [PATCH 5/8] Adding option for no-cache installation. --- justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/justfile b/justfile index 9269edd3..2b769fe4 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,9 @@ docs: dev: pip install -e '.[dev]' +dev-fresh: + pip install --no-cache-dir -e '.[dev]' + # Run code checks. check: #!/usr/bin/env bash From 1157a21809c7c4db4ebca1e3fe7938c5e7320520 Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 12:10:11 +0200 Subject: [PATCH 6/8] Undo unnecessary things. --- justfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/justfile b/justfile index 2b769fe4..9269edd3 100644 --- a/justfile +++ b/justfile @@ -12,9 +12,6 @@ docs: dev: pip install -e '.[dev]' -dev-fresh: - pip install --no-cache-dir -e '.[dev]' - # Run code checks. check: #!/usr/bin/env bash From 6c8acae841f8dcc50629a0e29acb19f769f6d287 Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 12:13:40 +0200 Subject: [PATCH 7/8] Ignoring ruff problems. --- examples/lens.py | 2 +- examples/onion_analysis.py | 4 ++-- pyproject.toml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/lens.py b/examples/lens.py index 129c43cd..25fc2004 100644 --- a/examples/lens.py +++ b/examples/lens.py @@ -66,7 +66,7 @@ def main() -> None: input_universe=universe, cutoff=cutoff, ) - lens, _nn, *_ = dynsight.lens.neighbour_change_in_time(neigcounts) + lens, nn, *_ = dynsight.lens.neighbour_change_in_time(neigcounts) fig, axes = plt.subplots(2, sharey=True) for i in range(4): diff --git a/examples/onion_analysis.py b/examples/onion_analysis.py index 529ae8dd..a230a596 100644 --- a/examples/onion_analysis.py +++ b/examples/onion_analysis.py @@ -98,7 +98,7 @@ def main() -> None: coord_1d = Insight(coord_2d.dataset[:, :, 0]) # Test onion clustering on a wide range of time resolutions - _delta_t_list, _n_clust, _unclass_frac = coord_1d.get_onion_analysis( + delta_t_list, n_clust, unclass_frac = coord_1d.get_onion_analysis( fig1_path=data_path / "time-res_1d.png", fig2_path=data_path / "pop_fracs_1d.png", ) @@ -110,7 +110,7 @@ def main() -> None: onion_results.plot_state_populations(data_path / "state_pops_1d.png") # Test onion clustering on a wide range of time resolutions - _delta_t_list, _n_clust, _unclass_frac = coord_2d.get_onion_analysis( + delta_t_list, n_clust, unclass_frac = coord_2d.get_onion_analysis( fig1_path=data_path / "time-res_2d.png", fig2_path=data_path / "pop_fracs_2d.png", ) diff --git a/pyproject.toml b/pyproject.toml index 763cb51f..59d31883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ convention = "google" "ANN001", "PLR0912", "PLR0915", + "RUF059", ] "docs/source/conf.py" = ["D100", "INP001"] From 5d0d078f44ca2507ca3a73ffec450e34b2cbd5a9 Mon Sep 17 00:00:00 2001 From: Matteo Becchi Date: Thu, 11 Sep 2025 12:15:29 +0200 Subject: [PATCH 8/8] Ignoring ruff problems. --- docs/source/_static/recipes/descr_from_trj.py | 4 ++-- docs/source/_static/recipes/info_gain.py | 2 +- pyproject.toml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/recipes/descr_from_trj.py b/docs/source/_static/recipes/descr_from_trj.py index 8ffe130d..01cba368 100644 --- a/docs/source/_static/recipes/descr_from_trj.py +++ b/docs/source/_static/recipes/descr_from_trj.py @@ -32,14 +32,14 @@ def main() -> None: ) # Computing number of neighbors from scratch - neigcounts, _n_neig = trj.get_coord_number( + neigcounts, n_neig = trj.get_coord_number( r_cut=2.0, # cutoff radius for neighbors list selection="all", # compute on a selection of particles neigcounts=None, # it will be computed and returned ) # Now for LENS we already have neigcounts - _, _lens = trj.get_lens( + _, lens = trj.get_lens( r_cut=2.0, # cutoff radius for neighbors list selection="all", # compute on a selection of particles neigcounts=neigcounts, # no need to compute it again diff --git a/docs/source/_static/recipes/info_gain.py b/docs/source/_static/recipes/info_gain.py index 3321713f..873839a0 100644 --- a/docs/source/_static/recipes/info_gain.py +++ b/docs/source/_static/recipes/info_gain.py @@ -161,7 +161,7 @@ def main() -> None: _, n_frames = data.shape delta_t_list = np.unique(np.geomspace(2, n_frames, 10, dtype=int)) - _n_cl, cl_frac, _info_gain, cl_entr, h_0 = info_gain_with_onion( + n_cl, cl_frac, info_gain, cl_entr, h_0 = info_gain_with_onion( delta_t_list, data, ) diff --git a/pyproject.toml b/pyproject.toml index 59d31883..f5d2ee5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ convention = "google" "RUF059", ] "docs/source/conf.py" = ["D100", "INP001"] +"docs/source/_static/recipes/*" = ["RUF059"] [tool.mypy] show_error_codes = true