crisp-ase 1.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. CRISP/__init__.py +99 -0
  2. CRISP/_version.py +1 -0
  3. CRISP/cli.py +41 -0
  4. CRISP/data_analysis/__init__.py +38 -0
  5. CRISP/data_analysis/clustering.py +838 -0
  6. CRISP/data_analysis/contact_coordination.py +915 -0
  7. CRISP/data_analysis/h_bond.py +772 -0
  8. CRISP/data_analysis/msd.py +1199 -0
  9. CRISP/data_analysis/prdf.py +404 -0
  10. CRISP/data_analysis/volumetric_atomic_density.py +527 -0
  11. CRISP/py.typed +1 -0
  12. CRISP/simulation_utility/__init__.py +31 -0
  13. CRISP/simulation_utility/atomic_indices.py +155 -0
  14. CRISP/simulation_utility/atomic_traj_linemap.py +278 -0
  15. CRISP/simulation_utility/error_analysis.py +254 -0
  16. CRISP/simulation_utility/interatomic_distances.py +200 -0
  17. CRISP/simulation_utility/subsampling.py +241 -0
  18. CRISP/tests/DataAnalysis/__init__.py +1 -0
  19. CRISP/tests/DataAnalysis/test_clustering_extended.py +212 -0
  20. CRISP/tests/DataAnalysis/test_contact_coordination.py +184 -0
  21. CRISP/tests/DataAnalysis/test_contact_coordination_extended.py +465 -0
  22. CRISP/tests/DataAnalysis/test_h_bond_complete.py +326 -0
  23. CRISP/tests/DataAnalysis/test_h_bond_extended.py +322 -0
  24. CRISP/tests/DataAnalysis/test_msd_complete.py +305 -0
  25. CRISP/tests/DataAnalysis/test_msd_extended.py +522 -0
  26. CRISP/tests/DataAnalysis/test_prdf.py +206 -0
  27. CRISP/tests/DataAnalysis/test_volumetric_atomic_density.py +463 -0
  28. CRISP/tests/SimulationUtility/__init__.py +1 -0
  29. CRISP/tests/SimulationUtility/test_atomic_traj_linemap.py +101 -0
  30. CRISP/tests/SimulationUtility/test_atomic_traj_linemap_extended.py +469 -0
  31. CRISP/tests/SimulationUtility/test_error_analysis_extended.py +151 -0
  32. CRISP/tests/SimulationUtility/test_interatomic_distances.py +223 -0
  33. CRISP/tests/SimulationUtility/test_subsampling.py +365 -0
  34. CRISP/tests/__init__.py +1 -0
  35. CRISP/tests/test_CRISP.py +28 -0
  36. CRISP/tests/test_cli.py +87 -0
  37. CRISP/tests/test_crisp_comprehensive.py +679 -0
  38. crisp_ase-1.1.2.dist-info/METADATA +141 -0
  39. crisp_ase-1.1.2.dist-info/RECORD +42 -0
  40. crisp_ase-1.1.2.dist-info/WHEEL +5 -0
  41. crisp_ase-1.1.2.dist-info/entry_points.txt +2 -0
  42. crisp_ase-1.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,151 @@
1
+ """Extended tests for error analysis module."""
2
+ import pytest
3
+ import numpy as np
4
+
5
+ from CRISP.simulation_utility.error_analysis import (
6
+ optimal_lag,
7
+ vector_acf,
8
+ )
9
+
10
+
11
+ class TestErrorAnalysisBasic:
12
+ """Basic error analysis tests."""
13
+
14
+ def test_optimal_lag_converged(self):
15
+ """Test optimal lag finding for converged ACF."""
16
+ acf_values = np.array([1.0, 0.9, 0.7, 0.4, 0.1, 0.02, 0.0, -0.01])
17
+
18
+ lag = optimal_lag(acf_values, threshold=0.05)
19
+
20
+ # Should find lag where ACF drops below threshold
21
+ assert lag >= 0
22
+ assert lag < len(acf_values)
23
+
24
+ def test_optimal_lag_not_converged(self):
25
+ """Test optimal lag when ACF doesn't converge (raises warning)."""
26
+ acf_values = np.array([1.0, 0.95, 0.90, 0.85, 0.80])
27
+
28
+ with pytest.warns(UserWarning):
29
+ lag = optimal_lag(acf_values, threshold=0.05)
30
+
31
+ # Should return last index when not converged
32
+ assert lag == len(acf_values) - 1
33
+
34
+ def test_optimal_lag_immediate_convergence(self):
35
+ """Test optimal lag with immediate convergence."""
36
+ acf_values = np.array([1.0, 0.01, 0.0])
37
+
38
+ lag = optimal_lag(acf_values, threshold=0.05)
39
+
40
+ assert lag == 1
41
+
42
+ def test_vector_acf_basic(self):
43
+ """Test vector ACF calculation."""
44
+ data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
45
+
46
+ acf_result = vector_acf(data, max_lag=2)
47
+
48
+ assert acf_result is not None
49
+ assert isinstance(acf_result, (np.ndarray, list, tuple))
50
+
51
+ def test_vector_acf_1d_input(self):
52
+ """Test vector ACF with properly shaped 2D input."""
53
+ # vector_acf expects 2D input with shape (n_frames, n_dimensions)
54
+ # Create 2D array instead of 1D
55
+ data = np.array([[1, 0], [2, 1], [3, 2], [4, 3], [5, 4], [6, 5], [7, 6], [8, 7]])
56
+
57
+ acf_result = vector_acf(data, max_lag=3)
58
+
59
+ assert acf_result is not None
60
+
61
+
62
+ class TestErrorAnalysisParametrized:
63
+ """Parametrized error analysis tests."""
64
+
65
+ @pytest.mark.parametrize("threshold", [0.01, 0.05, 0.1, 0.2])
66
+ def test_optimal_lag_various_thresholds(self, threshold):
67
+ """Test optimal lag with different thresholds."""
68
+ acf_values = np.array([1.0, 0.8, 0.5, 0.2, 0.05, 0.01, 0.001])
69
+
70
+ lag = optimal_lag(acf_values, threshold=threshold)
71
+
72
+ assert lag >= 0
73
+ assert lag < len(acf_values)
74
+
75
+ @pytest.mark.parametrize("max_lag", [1, 2, 5, 10])
76
+ def test_vector_acf_various_lags(self, max_lag):
77
+ """Test vector ACF with different max lags."""
78
+ data = np.random.random((20, 3))
79
+
80
+ acf_result = vector_acf(data, max_lag=max_lag)
81
+
82
+ assert acf_result is not None
83
+
84
+
85
+ class TestErrorAnalysisEdgeCases:
86
+ """Test error analysis edge cases."""
87
+
88
+ def test_optimal_lag_empty_array(self):
89
+ """Test optimal lag with very short array."""
90
+ acf_values = np.array([1.0])
91
+
92
+ lag = optimal_lag(acf_values, threshold=0.05)
93
+
94
+ assert lag == 0
95
+
96
+ def test_optimal_lag_negative_values(self):
97
+ """Test optimal lag with negative ACF values."""
98
+ acf_values = np.array([1.0, 0.5, 0.0, -0.3, -0.5])
99
+
100
+ lag = optimal_lag(acf_values, threshold=0.05)
101
+
102
+ assert lag >= 0
103
+ assert lag < len(acf_values)
104
+
105
+ def test_vector_acf_short_data(self):
106
+ """Test vector ACF with short data."""
107
+ data = np.array([[1, 2], [3, 4], [5, 6]])
108
+
109
+ try:
110
+ acf_result = vector_acf(data, max_lag=1)
111
+ assert acf_result is not None
112
+ except (ValueError, IndexError):
113
+ pass # Acceptable for short data
114
+
115
+ def test_vector_acf_single_sample(self):
116
+ """Test vector ACF with single sample."""
117
+ data = np.array([[1, 2, 3]])
118
+
119
+ try:
120
+ acf_result = vector_acf(data, max_lag=1)
121
+ # May raise error or return zeros
122
+ assert acf_result is not None or acf_result is None
123
+ except (ValueError, ZeroDivisionError):
124
+ pass
125
+
126
+
127
+ class TestErrorAnalysisIntegration:
128
+ """Integration tests for error analysis."""
129
+
130
+ def test_lag_finding_workflow(self):
131
+ """Test complete workflow of finding optimal lag."""
132
+ # Create synthetic data with known correlation
133
+ np.random.seed(42)
134
+ data = np.cumsum(np.random.randn(100, 3), axis=0)
135
+
136
+ acf_result = vector_acf(data, max_lag=10)
137
+ assert acf_result is not None
138
+
139
+ # Convert to array if needed
140
+ if isinstance(acf_result, (list, tuple)):
141
+ acf_array = np.array(acf_result)
142
+ else:
143
+ acf_array = acf_result
144
+
145
+ if len(acf_array) > 0:
146
+ lag = optimal_lag(acf_array, threshold=0.05)
147
+ assert lag >= 0
148
+
149
+
150
+ if __name__ == '__main__':
151
+ pytest.main([__file__, '-v'])
@@ -0,0 +1,223 @@
1
+ """Tests for interatomic distances calculation module."""
2
+ import pytest
3
+ import numpy as np
4
+ import os
5
+ import tempfile
6
+ import shutil
7
+ from CRISP.simulation_utility.interatomic_distances import (
8
+ indices,
9
+ distance_calculation,
10
+ save_distance_matrices,
11
+ calculate_interatomic_distances,
12
+ )
13
+
14
+ try:
15
+ from ase import Atoms
16
+ ASE_AVAILABLE = True
17
+ except ImportError:
18
+ ASE_AVAILABLE = False
19
+
20
+
21
+ @pytest.fixture
22
+ def temp_output_dir():
23
+ """Create temporary directory for outputs."""
24
+ temp_dir = tempfile.mkdtemp()
25
+ yield temp_dir
26
+ shutil.rmtree(temp_dir, ignore_errors=True)
27
+
28
+
29
+ @pytest.fixture
30
+ def sample_atoms():
31
+ """Create sample atoms object."""
32
+ if not ASE_AVAILABLE:
33
+ return None
34
+ positions = np.array([
35
+ [0.0, 0.0, 0.0],
36
+ [1.0, 0.0, 0.0],
37
+ [0.5, 0.866, 0.0],
38
+ [0.5, 0.289, 0.816]
39
+ ])
40
+ return Atoms('H4', positions=positions, cell=[10, 10, 10], pbc=True)
41
+
42
+
43
+ @pytest.mark.skipif(not ASE_AVAILABLE, reason="ASE not available")
44
+ class TestIndicesFunction:
45
+ """Test the indices extraction function."""
46
+
47
+ def test_indices_all_atoms(self, sample_atoms):
48
+ """Test extracting all atom indices."""
49
+ result = indices(sample_atoms, "all")
50
+ assert isinstance(result, np.ndarray)
51
+ assert len(result) == 4
52
+ np.testing.assert_array_equal(result, np.array([0, 1, 2, 3]))
53
+
54
+ def test_indices_none_defaults_to_all(self, sample_atoms):
55
+ """Test that None defaults to all atoms."""
56
+ result = indices(sample_atoms, None)
57
+ assert isinstance(result, np.ndarray)
58
+ assert len(result) == 4
59
+
60
+ def test_indices_with_list_integers(self, sample_atoms):
61
+ """Test extracting specific atom indices by integers."""
62
+ result = indices(sample_atoms, [0, 2])
63
+ assert isinstance(result, np.ndarray)
64
+ np.testing.assert_array_equal(result, np.array([0, 2]))
65
+
66
+ def test_indices_with_chemical_symbols(self, sample_atoms):
67
+ """Test extracting atoms by chemical symbol."""
68
+ result = indices(sample_atoms, ["H"])
69
+ assert isinstance(result, np.ndarray)
70
+ assert len(result) == 4 # All atoms are H
71
+
72
+
73
+ @pytest.mark.skipif(not ASE_AVAILABLE, reason="ASE not available")
74
+ class TestDistanceCalculation:
75
+ """Test distance calculation function."""
76
+
77
+ def test_distance_calculation_invalid_file(self):
78
+ """Test with nonexistent trajectory file."""
79
+ with pytest.raises((FileNotFoundError, ValueError)):
80
+ distance_calculation(
81
+ traj_path="nonexistent_trajectory.traj",
82
+ frame_skip=1,
83
+ index_type="all"
84
+ )
85
+
86
+ def test_distance_calculation_module_import(self):
87
+ """Test that module can be imported."""
88
+ from CRISP.simulation_utility import interatomic_distances
89
+ assert hasattr(interatomic_distances, 'distance_calculation')
90
+ assert hasattr(interatomic_distances, 'calculate_interatomic_distances')
91
+ assert hasattr(interatomic_distances, 'indices')
92
+
93
+
94
+ @pytest.mark.skipif(not ASE_AVAILABLE, reason="ASE not available")
95
+ class TestSaveDistanceMatrices:
96
+ """Test saving distance matrices."""
97
+
98
+ def test_save_distance_matrices_basic(self, temp_output_dir, sample_atoms):
99
+ """Test saving distance matrices."""
100
+ # Create sample distance matrices
101
+ dm1 = sample_atoms.get_all_distances(mic=True)
102
+ dm2 = sample_atoms.get_all_distances(mic=True)
103
+
104
+ full_dms = [dm1, dm2]
105
+ sub_dms = [dm1[:2, :2], dm2[:2, :2]]
106
+
107
+ save_distance_matrices(
108
+ full_dms=full_dms,
109
+ sub_dms=sub_dms,
110
+ index_type="all",
111
+ output_dir=temp_output_dir
112
+ )
113
+
114
+ # Check that output file was created
115
+ output_file = os.path.join(temp_output_dir, "distance_matrices.pkl")
116
+ assert os.path.exists(output_file)
117
+
118
+
119
+ @pytest.mark.skipif(not ASE_AVAILABLE, reason="ASE not available")
120
+ class TestCalculateInteratomicDistances:
121
+ """Test main wrapper function."""
122
+
123
+ def test_calculate_interatomic_distances_basic(self, sample_atoms, temp_output_dir):
124
+ """Test basic interatomic distance calculation."""
125
+ # Create a temporary trajectory file
126
+ traj_file = os.path.join(temp_output_dir, "test.traj")
127
+ sample_atoms.write(traj_file)
128
+
129
+ result = calculate_interatomic_distances(
130
+ traj_path=traj_file,
131
+ frame_skip=1,
132
+ index_type="all",
133
+ output_dir=temp_output_dir,
134
+ save_results=True
135
+ )
136
+
137
+ assert isinstance(result, dict)
138
+ assert "full_dms" in result
139
+ assert isinstance(result["full_dms"], list)
140
+ assert len(result["full_dms"]) > 0
141
+
142
+ def test_calculate_with_atom_indices(self, sample_atoms, temp_output_dir):
143
+ """Test calculation with specific atom indices."""
144
+ traj_file = os.path.join(temp_output_dir, "test.traj")
145
+ sample_atoms.write(traj_file)
146
+
147
+ result = calculate_interatomic_distances(
148
+ traj_path=traj_file,
149
+ frame_skip=1,
150
+ index_type=[0, 1],
151
+ output_dir=temp_output_dir,
152
+ save_results=True
153
+ )
154
+
155
+ assert "full_dms" in result
156
+ assert "sub_dms" in result
157
+
158
+
159
+ @pytest.mark.skipif(not ASE_AVAILABLE, reason="ASE not available")
160
+ class TestInteratomicDistancesEdgeCases:
161
+ """Test edge cases and error handling."""
162
+
163
+ def test_single_atom(self, temp_output_dir):
164
+ """Test with single atom."""
165
+ if not ASE_AVAILABLE:
166
+ pytest.skip("ASE not available")
167
+
168
+ atoms = Atoms('H', positions=[[0, 0, 0]], cell=[10, 10, 10], pbc=True)
169
+ traj_file = os.path.join(temp_output_dir, "single_atom.traj")
170
+ atoms.write(traj_file)
171
+
172
+ result = calculate_interatomic_distances(
173
+ traj_path=traj_file,
174
+ frame_skip=1,
175
+ output_dir=temp_output_dir,
176
+ save_results=False
177
+ )
178
+
179
+ # Single atom should have 1x1 distance matrix
180
+ assert result["full_dms"][0].shape == (1, 1)
181
+
182
+ def test_invalid_trajectory_file(self, temp_output_dir):
183
+ """Test with invalid trajectory file."""
184
+ with pytest.raises((FileNotFoundError, ValueError)):
185
+ calculate_interatomic_distances(
186
+ traj_path="nonexistent.traj",
187
+ frame_skip=1,
188
+ output_dir=temp_output_dir
189
+ )
190
+
191
+ def test_distance_matrix_symmetry(self, sample_atoms, temp_output_dir):
192
+ """Test that distance matrices are symmetric."""
193
+ traj_file = os.path.join(temp_output_dir, "test.traj")
194
+ sample_atoms.write(traj_file)
195
+
196
+ result = calculate_interatomic_distances(
197
+ traj_path=traj_file,
198
+ frame_skip=1,
199
+ output_dir=temp_output_dir,
200
+ save_results=False
201
+ )
202
+
203
+ dm = result["full_dms"][0]
204
+
205
+ # Check symmetry
206
+ np.testing.assert_array_almost_equal(dm, dm.T, decimal=5)
207
+
208
+ def test_distance_matrix_diagonal_zeros(self, sample_atoms, temp_output_dir):
209
+ """Test that distance matrix diagonal is zero."""
210
+ traj_file = os.path.join(temp_output_dir, "test.traj")
211
+ sample_atoms.write(traj_file)
212
+
213
+ result = calculate_interatomic_distances(
214
+ traj_path=traj_file,
215
+ frame_skip=1,
216
+ output_dir=temp_output_dir,
217
+ save_results=False
218
+ )
219
+
220
+ dm = result["full_dms"][0]
221
+
222
+ # Check diagonal is zero
223
+ np.testing.assert_array_almost_equal(np.diag(dm), np.zeros(len(dm)), decimal=5)