@@ -4,39 +4,19 @@ Output the simulation state
4
4
===========================
5
5
6
6
Here, the code is modified to allow us to follow the evolution of the system
7
- during the simulation. To do so, the *Output * class is modified.
8
-
9
- Update the MinimizeEnergy class
10
- -------------------------------
11
-
12
- Let us start by calling two additional methods within the for loop of the
13
- *MinimizeEnergy * class, within the *run() * method.
14
-
15
- .. label :: start_MinimizeEnergy_class
16
-
17
- .. code-block :: python
18
-
19
- def run (self ):
20
- (... )
21
- for self .step in range (0 , self .maximum_steps+ 1 ):
22
- (... )
23
- self .displacement *= 0.2 # Multiply the displacement by a factor 0.2
24
- log_simulation_data(self )
25
- update_dump_file(self , " dump.min.lammpstrj" )
26
-
27
- .. label :: end_MinimizeEnergy_class
28
-
29
- The two methods named *update_log_minimize() * and *update_dump_file() *, are used
30
- to print the information in the terminal and in a LAMMPS-type data file, respectively.
31
- These two methods will be written in the following.
7
+ during the simulation. To do so, two new files will be created: one dedicated
8
+ to logging information, and the other to printing the atom positions to a
9
+ file, thus allowing for their visualization with software like
10
+ VMD :cite: `humphrey1996vmd ` or Ovito :cite: `stukowski2009visualization `.
32
11
33
12
Create logger
34
13
-------------
35
14
36
- Let us create functions named *log_simulation_data * to a file named *logger.py *.
15
+ Let us create a function named *log_simulation_data() * to a file named *logger.py *.
37
16
With the logger, some output are being printed in a file, as well as in the terminal.
38
- The frequency of printing is set by *thermo_period *, see :ref: `chapter3-label `.
39
- All quantities are re-dimensionalized before getting outputed.
17
+ The period of printing, which is a multiple of the simulation steps, will be set
18
+ by a new parameter named *thermo_period * (integer). All quantities are
19
+ converted into *real * units before getting outputed (see :ref: `chapter2-label `).
40
20
41
21
.. label :: start_logger_class
42
22
@@ -76,6 +56,7 @@ All quantities are re-dimensionalized before getting outputed.
76
56
return logger
77
57
78
58
def log_simulation_data (code ):
59
+ # TOFIX: ceurrently, MaxF is returned dimensionless
79
60
80
61
# Setup the logger with the folder name, overwriting the log if code.step is 0
81
62
logger = setup_logger(code.data_folder, overwrite = (code.step == 0 ))
@@ -106,14 +87,22 @@ All quantities are re-dimensionalized before getting outputed.
106
87
107
88
.. label :: end_logger_class
108
89
109
- Create dumper
90
+ For simplicify, the *logger * uses the |logging | library, which provides a
91
+ flexible framework for emitting log messages from Python programs. Depending on
92
+ the value of *thermo_outputs *, different informations are printed, such as
93
+ *step *, *Epot *, or/and *MaxF *.
94
+
95
+ .. |logging | raw :: html
96
+
97
+ <a href="https://docs.python.org/3/library/logging.html" target="_blank">logging</a>
98
+
99
+ Create Dumper
110
100
-------------
111
101
112
- Let us create a function named *update_dump_file * to a file named
113
- *dumper.py *. The dumper will print a *.lammpstrj file *, which contains the box
114
- dimensions and atom positions at every chosen frame (set by *dumping_period *,
115
- see :ref: `chapter3-label `). All quantities are dimensionalized before getting outputed, and the file follows
116
- a LAMMPS dump format, and can be read by molecular dynamics softwares like VMD.
102
+ Let us create a function named *update_dump_file() * in a file named
103
+ *dumper.py *. The dumper will print a *.lammpstrj * file, which contains
104
+ the box dimensions and atom positions at every chosen frame (set by
105
+ *dumping_period *). All quantities are dimensionalized before being output.
117
106
118
107
.. label :: start_dumper_class
119
108
@@ -228,11 +217,35 @@ parameters are passed the InitializeSimulation method:
228
217
229
218
.. label :: end_InitializeSimulation_class
230
219
220
+ Update the MinimizeEnergy class
221
+ -------------------------------
222
+
223
+ As a final step, let us call two functions *log_simulation_data * and
224
+ *update_dump_file * within the *for * loop of the
225
+ *MinimizeEnergy * class, within the *run() * method:
226
+
227
+ .. label :: start_MinimizeEnergy_class
228
+
229
+ .. code-block :: python
230
+
231
+ def run (self ):
232
+ (... )
233
+ for self .step in range (0 , self .maximum_steps+ 1 ):
234
+ (... )
235
+ self .displacement *= 0.2 # Multiply the displacement by a factor 0.2
236
+ log_simulation_data(self )
237
+ update_dump_file(self , " dump.min.lammpstrj" )
238
+
239
+ .. label :: end_MinimizeEnergy_class
240
+
241
+ The name *dump.min.lammpstrj * will be used when printing the dump file
242
+ during energy minimization.
243
+
231
244
Test the code
232
245
-------------
233
246
234
- One can use a test similar as the previous ones. Let us ask out code to print
235
- information in the dump and the log files, and then let us assert the
247
+ One can use a test similar as the previous ones. Let us ask our code to print
248
+ information in the * dump * and the * log * files, and then let us assert that the
236
249
files were indeed created without the *Outputs/ * folder:
237
250
238
251
.. label :: start_test_5a_class
@@ -276,8 +289,10 @@ files were indeed created without the *Outputs/* folder:
276
289
277
290
# Test function using pytest
278
291
def test_output_files ():
279
- assert os.path.exists(" Outputs/dump.min.lammpstrj" ), " Test failed: dump file was not created"
280
- assert os.path.exists(" Outputs/simulation.log" ), " Test failed: log file was not created"
292
+ assert os.path.exists(" Outputs/dump.min.lammpstrj" ), \
293
+ " Test failed: the dump file was not created"
294
+ assert os.path.exists(" Outputs/simulation.log" ), \
295
+ " Test failed: the log file was not created"
281
296
print (" Test passed" )
282
297
283
298
# If the script is run directly, execute the tests
@@ -288,8 +303,9 @@ files were indeed created without the *Outputs/* folder:
288
303
289
304
.. label :: end_test_5a_class
290
305
291
- I addition to the files getting created, information must be printed in the terminal
292
- during the simulation:
306
+ In addition to making sure that both files were created, let us verify
307
+ that the expected information was printed to the terminal during the
308
+ simulation. The content of the *simulation.log * file should resemble:
293
309
294
310
.. code-block :: bw
295
311
@@ -302,8 +318,8 @@ during the simulation:
302
318
303
319
The data from the *simulation.log * can be used to generate plots using softwares
304
320
line XmGrace, GnuPlot, or Python/Pyplot. For the later, one can use a simple data
305
- reader to import the data from *Outputs/ simulation.log * into Python. Copy the
306
- following lines in a file named *reader.py *:
321
+ reader to import the data from *simulation.log * into Python. Copy the
322
+ following lines in a new file named *reader.py *:
307
323
308
324
.. label :: start_reader_class
309
325
@@ -342,14 +358,28 @@ The *import_data* function from *reader.py* can simply be used as follows:
342
358
343
359
.. label :: start_test_5b_class
344
360
361
+ .. code-block :: python
362
+
345
363
from reader import import_data
346
364
347
- file_path = "Outputs/simulation.log"
348
- header, data = import_data(file_path)
365
+ def test_file_not_empty ():
366
+ # Import data from the file
367
+ file_path = " Outputs/simulation.log"
368
+ header, data = import_data(file_path)
369
+ # Check if the header and data are not empty
370
+ assert header, " Error, no header in simulation.log"
371
+ assert data, " Error, no data in simulation.log"
372
+ assert len (data) > 0 , " Data should contain at least one row"
349
373
350
- print(header)
351
- for row in data:
352
- print(row)
374
+ print (header)
375
+ for row in data:
376
+ print (row)
377
+
378
+ # If the script is run directly, execute the tests
379
+ if __name__ == " __main__" :
380
+ import pytest
381
+ # Run pytest programmatically
382
+ pytest.main([" -s" , __file__ ])
353
383
354
384
.. label :: end_test_5b_class
355
385
@@ -362,4 +392,4 @@ Which must return:
362
392
[25.0, -2.12, 1.22]
363
393
[50.0, -2.19, 2.85]
364
394
[75.0, -2.64, 0.99]
365
- [100.0, -2.64, 0.51]
395
+ [100.0, -2.64, 0.51]
0 commit comments