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,305 @@
1
+ """Comprehensive tests for Mean Square Displacement (MSD) module."""
2
+ import pytest
3
+ import numpy as np
4
+ import pandas as pd
5
+ import os
6
+ import tempfile
7
+ import shutil
8
+ from CRISP.data_analysis.msd import (
9
+ calculate_diffusion_coefficient,
10
+ calculate_save_msd,
11
+ analyze_from_csv,
12
+ )
13
+
14
+
15
+ class TestMSDCalculations:
16
+ """Test MSD calculation core functionality."""
17
+
18
+ def test_calculate_diffusion_coefficient_basic(self):
19
+ """Test basic diffusion coefficient calculation."""
20
+ # Create linear MSD data (slope = 0.4)
21
+ msd_times = np.array([0, 1, 2, 3, 4, 5])
22
+ msd_values = np.array([0, 0.4, 0.8, 1.2, 1.6, 2.0])
23
+
24
+ D, error = calculate_diffusion_coefficient(
25
+ msd_times=msd_times,
26
+ msd_values=msd_values,
27
+ start_index=0,
28
+ end_index=len(msd_values),
29
+ with_intercept=True
30
+ )
31
+
32
+ assert isinstance(D, float)
33
+ assert isinstance(error, float)
34
+ assert D > 0
35
+ assert error >= 0
36
+
37
+ def test_calculate_diffusion_without_intercept(self):
38
+ """Test diffusion coefficient calculation without intercept."""
39
+ msd_times = np.array([0, 1, 2, 3, 4])
40
+ msd_values = np.array([0, 0.2, 0.4, 0.6, 0.8])
41
+
42
+ D, error = calculate_diffusion_coefficient(
43
+ msd_times=msd_times,
44
+ msd_values=msd_values,
45
+ start_index=0,
46
+ end_index=len(msd_values),
47
+ with_intercept=False
48
+ )
49
+
50
+ assert D > 0
51
+ assert error >= 0
52
+
53
+ def test_calculate_diffusion_subset(self):
54
+ """Test diffusion coefficient with subset of data."""
55
+ msd_times = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
56
+ msd_values = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0, 2.0, 3.0])
57
+
58
+ # Use only first 5 points
59
+ D, error = calculate_diffusion_coefficient(
60
+ msd_times=msd_times,
61
+ msd_values=msd_values,
62
+ start_index=0,
63
+ end_index=5,
64
+ with_intercept=True
65
+ )
66
+
67
+ assert D > 0
68
+ assert error >= 0
69
+
70
+ def test_calculate_diffusion_different_start_end(self):
71
+ """Test diffusion coefficient with different start/end indices."""
72
+ msd_times = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8])
73
+ msd_values = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8])
74
+
75
+ # Use middle portion (indices 2-6)
76
+ D, error = calculate_diffusion_coefficient(
77
+ msd_times=msd_times,
78
+ msd_values=msd_values,
79
+ start_index=2,
80
+ end_index=7,
81
+ with_intercept=True
82
+ )
83
+
84
+ assert D > 0
85
+
86
+ def test_calculate_diffusion_single_point(self):
87
+ """Test with insufficient data points - returns result but with nan R²."""
88
+ msd_times = np.array([0])
89
+ msd_values = np.array([0])
90
+
91
+ # Function doesn't raise error, just returns result with warnings
92
+ result = calculate_diffusion_coefficient(
93
+ msd_times=msd_times,
94
+ msd_values=msd_values,
95
+ start_index=0,
96
+ end_index=1
97
+ )
98
+ # Should return a tuple
99
+ assert result is not None
100
+ assert len(result) == 2
101
+
102
+ def test_calculate_diffusion_empty_data(self):
103
+ """Test with empty data."""
104
+ with pytest.raises((ValueError, IndexError)):
105
+ calculate_diffusion_coefficient(
106
+ msd_times=np.array([]),
107
+ msd_values=np.array([]),
108
+ start_index=0,
109
+ end_index=0
110
+ )
111
+
112
+
113
+ class TestMSDFileOperations:
114
+ """Test MSD file reading and writing."""
115
+
116
+ @pytest.fixture
117
+ def sample_csv_file(self):
118
+ """Create sample MSD CSV file."""
119
+ temp_dir = tempfile.mkdtemp()
120
+ csv_path = os.path.join(temp_dir, 'msd_sample.csv')
121
+
122
+ # Create realistic MSD data
123
+ data = {
124
+ 'Time (fs)': np.arange(0, 100, 10),
125
+ 'MSD': np.array([0, 0.5, 1.2, 2.1, 3.2, 4.5, 5.8, 7.2, 8.8, 10.5])
126
+ }
127
+
128
+ df = pd.DataFrame(data)
129
+ df.to_csv(csv_path, index=False)
130
+
131
+ yield csv_path
132
+ shutil.rmtree(temp_dir)
133
+
134
+ def test_analyze_from_csv_basic(self, sample_csv_file):
135
+ """Test MSD analysis from CSV file."""
136
+ D, error = analyze_from_csv(
137
+ csv_file=sample_csv_file,
138
+ fit_start=0,
139
+ fit_end=5,
140
+ with_intercept=True
141
+ )
142
+
143
+ assert isinstance(D, float)
144
+ assert isinstance(error, float)
145
+ assert D > 0
146
+
147
+ def test_analyze_from_csv_different_fit_range(self, sample_csv_file):
148
+ """Test MSD analysis with different fit ranges."""
149
+ # Test with different ranges
150
+ D1, _ = analyze_from_csv(
151
+ csv_file=sample_csv_file,
152
+ fit_start=0,
153
+ fit_end=3,
154
+ with_intercept=True
155
+ )
156
+
157
+ D2, _ = analyze_from_csv(
158
+ csv_file=sample_csv_file,
159
+ fit_start=3,
160
+ fit_end=8,
161
+ with_intercept=True
162
+ )
163
+
164
+ assert D1 > 0
165
+ assert D2 > 0
166
+ assert D1 != D2 # Different ranges should give different results
167
+
168
+ def test_analyze_from_csv_invalid_file(self):
169
+ """Test analysis with nonexistent file returns None."""
170
+ D, error = analyze_from_csv(
171
+ csv_file='nonexistent_file.csv',
172
+ fit_start=0,
173
+ fit_end=5
174
+ )
175
+ # Function catches error and returns (None, None)
176
+ assert D is None
177
+ assert error is None
178
+
179
+ def test_analyze_from_csv_with_blocks(self, sample_csv_file):
180
+ """Test MSD analysis with block averaging."""
181
+ D, error = analyze_from_csv(
182
+ csv_file=sample_csv_file,
183
+ fit_start=0,
184
+ fit_end=5,
185
+ with_intercept=True,
186
+ n_blocks=2
187
+ )
188
+
189
+ assert D > 0
190
+ assert error >= 0
191
+
192
+
193
+ class TestMSDEdgeCases:
194
+ """Test edge cases and error handling."""
195
+
196
+ def test_negative_msd_values(self):
197
+ """Test handling of negative MSD values (physically impossible)."""
198
+ msd_times = np.array([0, 1, 2])
199
+ msd_values = np.array([0, -0.1, -0.2]) # Negative values
200
+
201
+ # Function should handle gracefully
202
+ D, error = calculate_diffusion_coefficient(
203
+ msd_times=msd_times,
204
+ msd_values=msd_values,
205
+ start_index=0,
206
+ end_index=3
207
+ )
208
+
209
+ assert isinstance(D, float)
210
+
211
+ def test_constant_msd_values(self):
212
+ """Test with constant MSD values (no motion)."""
213
+ msd_times = np.array([0, 1, 2, 3, 4])
214
+ msd_values = np.array([0, 0, 0, 0, 0]) # No motion
215
+
216
+ D, error = calculate_diffusion_coefficient(
217
+ msd_times=msd_times,
218
+ msd_values=msd_values,
219
+ start_index=0,
220
+ end_index=5
221
+ )
222
+
223
+ # D should be close to 0
224
+ assert D >= 0
225
+ assert D < 0.1
226
+
227
+ def test_mismatched_array_lengths(self):
228
+ """Test with mismatched array lengths - may work due to slicing."""
229
+ # Function uses slicing with start_index:end_index, so mismatch may work
230
+ try:
231
+ result = calculate_diffusion_coefficient(
232
+ msd_times=np.array([0, 1, 2]),
233
+ msd_values=np.array([0, 1]), # Different length
234
+ start_index=0,
235
+ end_index=2
236
+ )
237
+ # If it works, result should be a tuple
238
+ assert result is not None
239
+ except (ValueError, IndexError):
240
+ # Or it may raise an error, which is also acceptable
241
+ pass
242
+
243
+ def test_invalid_index_range(self):
244
+ """Test with invalid start/end indices."""
245
+ msd_times = np.array([0, 1, 2, 3])
246
+ msd_values = np.array([0, 0.1, 0.2, 0.3])
247
+
248
+ with pytest.raises((ValueError, IndexError)):
249
+ calculate_diffusion_coefficient(
250
+ msd_times=msd_times,
251
+ msd_values=msd_values,
252
+ start_index=5, # Out of bounds
253
+ end_index=10
254
+ )
255
+
256
+ def test_start_greater_than_end(self):
257
+ """Test with start index greater than end index."""
258
+ msd_times = np.array([0, 1, 2, 3, 4])
259
+ msd_values = np.array([0, 0.1, 0.2, 0.3, 0.4])
260
+
261
+ with pytest.raises((ValueError, Exception)):
262
+ calculate_diffusion_coefficient(
263
+ msd_times=msd_times,
264
+ msd_values=msd_values,
265
+ start_index=4,
266
+ end_index=2 # Start > End
267
+ )
268
+
269
+
270
+ class TestMSDIntegration:
271
+ """Test MSD module integration."""
272
+
273
+ def test_msd_module_imports(self):
274
+ """Test that all MSD functions can be imported."""
275
+ from CRISP.data_analysis.msd import (
276
+ calculate_diffusion_coefficient,
277
+ calculate_save_msd,
278
+ analyze_from_csv,
279
+ )
280
+
281
+ assert callable(calculate_diffusion_coefficient)
282
+ assert callable(calculate_save_msd)
283
+ assert callable(analyze_from_csv)
284
+
285
+ def test_diffusion_values_reasonable(self):
286
+ """Test that diffusion values are physically reasonable."""
287
+ # Create MSD data typical for molecular dynamics
288
+ msd_times = np.linspace(0, 1000, 50) # Time in fs
289
+ # D ≈ 1e-5 cm²/s = 1e-9 m²/s, in Angstrom²/fs: 1e-9 * 1e20 * 1e-15 ≈ 0.001
290
+ msd_values = 4 * 0.001 * msd_times # MSD = 4*D*t
291
+
292
+ D, error = calculate_diffusion_coefficient(
293
+ msd_times=msd_times,
294
+ msd_values=msd_values,
295
+ start_index=0,
296
+ end_index=len(msd_values)
297
+ )
298
+
299
+ # D should be positive and reasonable
300
+ assert D > 0
301
+ assert D < 10 # Not unreasonably large
302
+
303
+
304
+ if __name__ == '__main__':
305
+ pytest.main([__file__, '-v'])