-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinteractiveMD.html
2020 lines (1864 loc) · 79.1 KB
/
interactiveMD.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<!--
A molecular dynamics simulation in JavaScript, using HTML5 canvas for graphics
Copyright 2013-2014, Daniel V. Schroeder
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated data and documentation (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be used in
advertising or otherwise to promote the sale, use or other dealings in this
Software without prior written authorization.
Revision history:
Based in part on a REALbasic program written in 2002
Java version December 2007; additional features June 2008
Bare-bones HTML5/JavaScript version February 2013
Added numerous features and interactivity October 2013
Added presets, cell-list optimization, and "safety" features November 2013
Added atom selection, more data options, new box size handling, fixed-T atoms December 2013
Added selected atom popup, UI improvements January 2014
Minor tweaks and bug fixes February 2014, August 2014
To do:
Figure out what's limiting performance in iOS.
Fix touch event issues on various mobile devices.
Fix data font size in Chrome for Android.
Fix (and test) recognition of various mobile devices including Kindle Silk browser.
Maybe calculate and display angular momentum and moment of inertia.
Maybe add an option to turn off the attractive part of the Lennard-Jones force.
Maybe add an option to draw atoms with fuzzy edges using gradients and transparency (check performance).
Maybe draw at higher resolution on retina displays.
-->
<html>
<head>
<meta charset="utf-8">
<title>Interactive Molecular Dynamics</title>
<style>
body {background-color:#ffffff; font-size:15px; font-family:Arial, sans-serif;}
p {margin-left:auto; margin-right:auto;} /* keep paragraphs narrow and centered */
li {margin-bottom:8px;}
input[type=range] {
width:230px;
height:20px;
padding:0px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
input[type="range"]::-ms-tooltip {
display: none; /* hide automatic readout in IE */
}
input[type="range"]::-ms-track {
border: none;
color: transparent; /* hide tick marks in IE */
}
input[type="range"]::-ms-fill-lower {
background: #808080;
}
input[type="range"]::-ms-fill-upper {
background: #e8e8e8;
}
input[type="range"]::-ms-thumb {
border: none;
}
select {font-size:16px;}
.custombutton { /* this class turns an ordinary link or span into a nice attractive push-button */
display: inline-block;
width:78px;
height:28px;
line-height:28px;
font-size:15px;
font-family:Arial, sans-serif;
text-align:center;
color:black;
background:-webkit-linear-gradient(white,#eeeeee,#eeeeee,#e0e0e0);
background:linear-gradient(white,#eeeeee,#eeeeee,#e0e0e0);
text-decoration:none;
border:1px solid gray;
border-radius: 5px;
-webkit-user-select: none;
-moz-user-select: -moz-none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.custombutton:active {
background:-webkit-linear-gradient(#909090,#808080,#808080,#707070);
background:linear-gradient(#909090,#808080,#808080,#707070);
}
.startbutton {
background:-webkit-linear-gradient(#dfffdf,#ceeece,#ceeece,#c0e0c0);
background:linear-gradient(#dfffdf,#ceeece,#ceeece,#c0e0c0); /* green tint */
}
.startbutton:active {
background:-webkit-linear-gradient(#909090,#808080,#808080,#707070);
background:linear-gradient(#909090,#808080,#808080,#707070);
}
</style>
</head>
<body>
<!-- Here's the HTML to create the canvas, text, and GUI controls... -->
<h1 style="font-size:22px; text-align:center; margin-bottom:8px;">Interactive Molecular Dynamics</h1>
<div id="mainDiv" style="width:770px; margin-left:auto; margin-right:auto;">
<div id="appDiv" style="background-color:#d0d0d0; border:5px solid #d0d0d0;">
<div id="controlPanel" style="float:right; width:250px; margin-left:10px; text-align:center;">
<div style="margin-bottom:4px;">
<a href="javascript:void(0)" id="startButton" class="custombutton startbutton" onclick="startStop()" ontouchstart="">Start</a>
<a href="javascript:void(0)" class="custombutton" onclick="simulate()" ontouchstart="">Step</a>
<a href="javascript:void(0)" class="custombutton" onclick="restart()" ontouchstart="">Restart</a>
</div>
<div style="margin-bottom:4px;">
<a href="javascript:void(0)" class="custombutton" onclick="speedFactor(0.9)" ontouchstart="">Slower</a>
<a href="javascript:void(0)" class="custombutton" style="width:36px; font-size:14px;" onclick="speedFactor(0.99)" ontouchstart="">−1%</a>
<a href="javascript:void(0)" class="custombutton" style="width:36px; font-size:14px;" onclick="speedFactor(1.01)" ontouchstart="">+1%</a>
<a href="javascript:void(0)" class="custombutton" onclick="speedFactor(1.1)" ontouchstart="">Faster</a>
</div>
<div style="margin-bottom:4px;">
<a href="javascript:void(0)" class="custombutton" onclick="speedFactor(0)" ontouchstart="">Freeze</a>
<a href="javascript:void(0)" class="custombutton" onclick="speedFactor(-1)" ontouchstart="">Reverse</a>
<select id="bondSelect" style="width:78px; font-size:15px;" onchange="createOrReleaseBonds()" onblur="deselectBonds()">
<option selected>Bonds</option>
<option value="create">Create</option>
<option value="release">Release</option>
</select>
</div>
<div>
<select id="presetSelect" onchange="loadPreset()" onblur="deselectPreset()">
<option value="presets" selected>Presets</option>
</select>
</div>
<div style="margin-top:5px;">
Mouse/touch:
<select id="mouseSelect">
<option value="drag" selected>Drag</option>
<option value="select">Select</option>
<option value="anchor">Anchor</option>
<option value="connect">Connect</option>
<option value="fixT">Fix T</option>
</select>
</div>
<div style="margin-top:8px; text-align:left;">
<div style="float:right;">
<span class="custombutton" style="width:16px; height:16px; line-height:16px;" onclick="changeN(-1);" ontouchstart="">−</span>
<span class="custombutton" style="width:16px; height:16px; line-height:16px;" onclick="changeN(1);" ontouchstart="">+</span>
</div>
Number of atoms = <span id="nReadout">250</span>
</div>
<div>
<input id="nSlider" type="range" min="1" max="2500" value="500" oninput="changeN()" onchange="changeN()">
</div>
<div style="margin-top:5px; text-align:left;">
<div style="float:right;"><input id="dtPBC" type="checkbox">PBC</div>
Box size = <span id="sizeReadout"></span>
</div>
<div>
<input id="sizeSlider" type="range" min="5" max="100" value="10" oninput="changeSize()" onchange="changeSize()">
</div>
<div style="margin-top:5px; text-align:left;">
<div style="float:right;"><input id="gravx10" type="checkbox" onchange="setGravity()">x10</div>
Gravity = <span id="gravReadout">0.000</span>
</div>
<div>
<input id="gravSlider" type="range" min="0" max="0.1" step="0.001" value="0" oninput="setGravity()" onchange="setGravity()">
</div>
<div style="margin-top:5px; text-align:left;">
<div style="float:right;"><input id="dtFixed" type="checkbox">Fixed</div>
Time step = <span id="dtReadout">0.020</span>
</div>
<div>
<input id="dtSlider" type="range" min="0.001" max="0.04" step="0.001" value="0.02" oninput="changedt()" onchange="changedt()">
</div>
<div style="margin-top:5px; text-align:left;">Steps per frame = <span id="stepsReadout">25</span></div>
<div>
<input id="stepsSlider" type="range" min="1" max="200" value="25" oninput="changeSteps()" onchange="changeSteps()">
</div>
<div style="margin-top:6px;">
Atom color:
<select id="mColorSelect" onchange="assignRandomColors()">
<option value="#6400c8">Purple</option>
<option value="#00ff00">Green</option>
<option value="#ffff00">Yellow</option>
<option value="#ff8000">Orange</option>
<option value="#ff0000">Red</option>
<option value="#ff00ff">Magenta</option>
<option value="#00ffff">Cyan</option>
<option value="#0000ff">Blue</option>
<option value="#008032">Forest</option>
<option value="#000000">Black</option>
<option value="#ffffff">White</option>
<option>Random</option>
<option selected>By speed</option>
</select>
</div>
<div>
Background:
<select id="bgColorSelect" onchange="paintCanvas()">
<option value="#000000" selected>Black</option>
<option value="#ffffff">White</option>
<option value="#fff5e6">Beige</option>
<option value="#ffe1f5">Pink</option>
<option value="#f0e1ff">Lavender</option>
<option value="#e6f0ff">Sky</option>
<option value="#e8f8f0">Sage</option>
<option value="#000064">Navy</option>
<option value="#320064">Plum</option>
<option value="#320000">Brown</option>
<option value="#3c3c3c">Gray</option>
</select>
</div>
<!-- <div style="margin-top:5px;"><input id="cellListCheck" type="checkbox" checked onchange="resetStepsPerSec()">Use cell list</div> -->
</div>
<div id="canvasDiv" style="width:500px; position:relative;">
<canvas id="theCanvas" width="500" height="500">
Canvas not supported; please update your browser.
</canvas>
<div id="selectDataPanel" style="display:none; position:absolute; left:0px; top:0px; width:90px;
background-color:rgba(245,245,245,0.8); padding:3px; border:1px solid gray; font-family:monospace; font-size:12px;">
Test
</div>
<div id="fixTPanel" style="display:none; position:absolute; left:0px; top:0px; width:220px;
background-color:rgba(221,221,221,0.9); padding:3px; border:1px solid gray; border-radius:5px; text-align:center;">
<div style="margin-bottom:2px;">Atom number <span id="atomNumber"></span></div>
<div style="margin-bottom:2px;">Temperature = <span id="atomTemp"></span></div>
<div style="margin-bottom:2px;"><input id="tempSlider" type="range" min="0" max="5" step="0.01" value="0" style="width:210px;" oninput="changeAtomTemp()" onchange="changeAtomTemp()"></div>
<div>
<a href="javascript:void(0)" class="custombutton" onclick="unfixT()" ontouchstart="">Unfix T</a>
<a href="javascript:void(0)" class="custombutton" onclick="fixT()" ontouchstart="">Fix T</a>
</div>
</div>
</div>
<div style="float:right; width:250px; margin-left:10px; text-align:center;">
<a href="javascript:void(0)" class="custombutton" style="width:100px; height:20px; line-height:20px;" onclick="reset()" ontouchstart="">Reset stats</a>
<a href="javascript:void(0)" id="moreButton" class="custombutton" style="width:100px; height:20px; line-height:20px;" onclick="showDataPanel()" ontouchstart="">↓ Data ↓</a>
</div>
<div id="dataReadout" style="font-family:monospace; font-size:15px; line-height:22px; -webkit-text-size-adjust: 100%;">t = 0, E = 0, T = 0, P = 0</div>
<div style="clear:both;"></div>
<div id="dataPanel" style="display:none;">
<div id="energyReadout" style="font-family:monospace; font-size:15px; -webkit-text-size-adjust: 100%;"> </div>
<div id="dataControlPanel" style="width:250px; float:right; margin-left:10px; text-align:center;">
<div style="margin-bottom:4px;">
Data type:
<select id="dataSelect" onchange="dataSelectChange()">
<option value="system" selected>System totals</option>
<option value="selected">Selected atom</option>
<option value="all">All atoms</option>
</select>
</div>
<div style="margin-bottom:4px;">
<a href="javascript:void(0)" class="custombutton" style="width:100px;" onclick="writeStats()" ontouchstart="">Write data</a>
<a href="javascript:void(0)" class="custombutton" style="width:100px;" onclick="clearDataArea()" ontouchstart="">Clear</a>
</div>
<div id="autoIntervalControl" style="margin-bottom:4px;">
Auto interval:
<select id="autoDataSelect" onchange="autoDataSelectChange()">
<option value="0" selected>None</option>
<option value="0.1">0.1</option>
<option value="1">1</option>
<option value="10">10</option>
<option value="100">100</option>
<option value="1000">1000</option>
<option value="10000">10000</option>
</select>
</div>
<div id="moreDetailCheckPanel">
<input id="moreDetailCheck" type="checkbox">More detail
</div>
<div id="allAtomsDataButtons" style="display:none;">
<a href="javascript:void(0)" class="custombutton" style="width:100px;" onclick="inputState()" ontouchstart="">Input state</a>
<a href="javascript:void(0)" class="custombutton" style="width:100px;" onclick="showJS()" ontouchstart="">Show JS</a>
</div>
</div>
<textarea id="dataArea" rows="10" style="width:494px; resize:vertical; font-family:monospace"></textarea>
</div>
</div>
<p>This web app <a href="http://en.wikipedia.org/wiki/Molecular_dynamics">simulates the dynamics</a>
of simple atoms and molecules in a two-dimensional
universe. The force between the atoms is weakly attractive at short distances,
but strongly repulsive when they touch. Use the simulation to explore
<a href="http://www.chem.purdue.edu/gchelp/liquids/character.html">phases of matter</a>,
<a href="http://en.wikipedia.org/wiki/Emergence">emergent behavior</a>,
<a href="http://en.wikipedia.org/wiki/Irreversibility">irreversibility</a>,
and <a href="http://en.wikipedia.org/wiki/Equipartition_theorem">thermal effects</a> at the
<a href="http://en.wikipedia.org/wiki/Nanotechnology">nanoscale</a>.</p>
<p><b>Note:</b> This version of the simulation includes an enhanced <b>Presets</b> menu to
accompany the figures and selected exercises in
<a href="http://physics.weber.edu/schroeder/md/InteractiveMD.pdf">the accompanying article</a> (pdf),
published in the <a href="http://dx.doi.org/10.1119/1.4901185">American Journal of Physics</a> <b>83</b> (3),
210-218 (2015), <a href="http://arxiv.org/abs/1502.06169">arXiv:1502.06169 [physics.ed-ph]</a>.
Here is a <a href="exercises.html">version of the
exercises from the article</a> that includes specific directions and hints for use with this software.
For a version of this simulation with the original (shorter) Presets menu,
<a href="http://physics.weber.edu/schroeder/md/">click here</a>.</p>
<p><b>The physics:</b></p>
<ul>
<li>Each atom in the simulation simply moves in response to the forces exerted by nearby atoms and
the container walls, in accord with
<a href="http://en.wikipedia.org/wiki/Newton%27s_laws_of_motion">Newton’s laws of motion</a>.
The simulation code knows nothing
about phase transformations or crystal structure or irreversibility, yet these high-level
phenomena, and others, emerge from the microscopic physics.</li>
<li>The force between the atoms is calculated from the
<a href="http://en.wikipedia.org/wiki/Lennard-Jones_potential">Lennard-Jones formula</a>
(truncated at a distance of 3 molecular diameters). This is a reasonably accurate model
of the interactions between
<a href="http://en.wikipedia.org/wiki/Noble_gas">noble gas</a> atoms.</li>
<li>The simulation approximates Newton’s laws using
the <a href="http://en.wikipedia.org/wiki/Verlet_integration#Velocity_Verlet">Verlet
algorithm</a> with the indicated <b>Time step</b>. Using too large a time step can
make the simulation inaccurate and sometimes even unstable (see below).</li>
<li>The simulation uses a natural system of units,
with the atomic diameter, the atomic mass, the depth of the Lennard-Jones
potential, and <a href="http://en.wikipedia.org/wiki/Boltzmann_constant">Boltzmann’s constant</a>
all set equal to 1. For argon (for example),
the unit of distance is 3.4 <a href="http://en.wikipedia.org/wiki/Angstrom">angstroms</a>,
the unit of mass
is 40 <a href="http://en.wikipedia.org/wiki/Atomic_mass_unit">atomic mass units</a>,
and the unit of energy
is 0.012 <a href="http://en.wikipedia.org/wiki/Electron-volt">electron-volts</a>;
the corresponding unit of time is then 2
picoseconds, the unit of velocity is 170 meters per second,
and the unit of temperature is 140 kelvin.</li>
<li>The walls exert a linear (spring)
force on the molecules, with a spring constant of 50 in natural units.</li>
<li>There’s an optional uniform downward force, controlled by the <b>Gravity</b>
slider. The magnitude of this force, however, is not meant to be realistic.
<a href="http://en.wikipedia.org/wiki/Earth%27s_gravity">Earth’s gravitational constant</a> is
utterly negligible in the units used here (a little over 10<sup>−13</sup> for argon).</li>
</ul>
<p><b>The user interface:</b></p>
<ul>
<li>Press <b>Start</b> to start the simulation, or <b>Step</b> to step forward in
time by a small amount. The <b>Steps per frame</b> slider controls both the speed
of continuous running and the number of time steps in a “step”.</li>
<li>The <b>Faster</b> and <b>Slower</b> buttons increase and decrease the speeds of all
the atoms by 10%. Press them repeatedly for a greater effect, or use the <b>+1%</b>
and <b>−1%</b> buttons for fine adjustments. The <b>Freeze</b>
button sets all the speeds to zero. Using
these buttons puts the system out of thermal equilibrium; it’s fun to then watch it try
to equilibrate.</li>
<li>Don’t expect the <b>Reverse</b> button to accurately run the motion backwards for long;
the motion is almost always <a href="http://en.wikipedia.org/wiki/Chaos_theory">chaotic</a>!</li>
<li>The <b>Box size</b> slider changes the width of the container (in units of atomic diameters);
since the container is always drawn to fill the same area on the screen, this setting also determines the
scale (zoom level) of the image. When the simulation is running it limits the rate at which
the box size can be changed.</li>
<li>The statistics displayed below the image are time, total energy, temperature, and pressure.
The temperature is computed from the average kinetic energy, so it isn’t accurate
when there’s organized motion on a large scale. The temperature and pressure are averaged over time.</li>
<li>Be sure to explore the <b>Presets</b>. (As noted above, most of the presets in this version
are labeled in correspondence with figures and exercises in the accompanying
<a href="http://physics.weber.edu/schroeder/md/InteractiveMD.pdf">article</a>.)</li>
<li>Sometimes the simulation becomes unstable, producing a runaway effect of exponentially
increasing energy. This is a consequence of approximating the relations between
position, velocity, and acceleration using small differences instead of derivatives.
As long as <b>Fixed</b> isn’t checked, the simulation will try to reduce the time step
as needed to keep the calculation stable. You may need to manually drag the time step slider back
up from time to time.
If the simulation becomes unstable, it will stop running and an alert
box will appear. Do what it says.</li>
<li>Yes, you can drag the atoms around with the mouse or other pointing mechanism (at least on
traditional computers and iOS touch-screen devices). If you drag
an atom outside the box when the simulation is paused, that atom is deleted.
When the simulation is running, dragging an atom actually pulls it with a simulated elastic cord. Try it!</li>
<li>When the atoms are colored <b>By speed</b>, the sequence of
20 colors is assigned linearly according to speed. The brightest color is used for
all speeds greater than 3.0.</li>
</ul>
<p><b>Additional features:</b></p>
<ul>
<li>You can artificially anchor one or more atoms in space. Choose <b>Anchor</b> from the
<b>Mouse/touch</b> menu and then click/tap on the desired atom(s). A second click/tap
unanchors an atom. Anchored atoms are drawn in light gray.</li>
<li>You can also connect any two atoms together with a “bond” that creates a spring-like
force between them (in addition to the Lennard-Jones force). The spring constant is 100 in
natural units, and the equilibrium position is the same as for the Lennard-Jones force.
Although this feature allows some interesting qualitative
demonstrations, it is not realistic: Actual covalent bonds are thousands of
times stiffer. To connect two atoms, choose <b>Connect</b> from the <b>Mouse/touch</b> menu.
Then press on one atom, drag to another that’s nearby, and release. Click or tap on an atom
to delete all its bonds. Use the <b>Bonds</b> menu to create bonds between all nearby pairs, or
to delete all bonds. Bonds are drawn as thin gray lines.</li>
<li>Finally, you can “fix the temperature” of an atom, causing it to move randomly as if it is
in contact with a heat bath at a specified temperature. Choose <b>Fix T</b> from the <b>Mouse/touch</b>
menu, then click on an atom and adjust the slider in the popup box to the desired temperature.
The fixed-T atom will then be marked with a gray dot. (If the atom is part of a rigid solid,
this feature has the side effect of exerting a drag force that impedes any macroscopic motion
of the solid.)</li>
</ul>
<p><b>Gathering data:</b></p>
<ul>
<li>To get accurate temperature and pressure values, you should let the system
equilibrate, then press the <b>Reset stats</b> button and wait for the values to stabilize.</li>
<li>To see the coordinates and velocity of any atom, choose <b>Select</b> from the <b>Mouse/touch</b>
menu and then click/tap on the atom. Click/tap on the atom again to keep it selected but hide the
small data panel.</li>
<li>Click the <b>Data</b> button to see more statistics: kinetic energy, potential energy (from interactions between
atoms and interactions with the walls), gravitational energy, and the rate at which the simulation
is running in steps per second. The Data button also reveals a text area where you can record three
different types of information, chosen from the <b>Data type</b> menu. <b>System totals</b> shows
macroscopic data (energy, temperature, pressure, etc.) as in the instantaneous readouts,
with a checkbox to indicate if you want more detail.
Use the <b>Auto interval</b> menu to write this information at regular intervals of simulation time.
Note that the data is tab-delimited for convenient copying into a spreadsheet.
Choose <b>Selected atom</b> to instead record the position and velocity of a single atom (chosen first using the
<b>Select</b> option from the <b>Mouse/touch</b> menu). The <b>All atoms</b> data type shows the
positions and velocities of all atoms, and allows this information to be edited (perhaps in a spreadsheet)
and read back into the simulation using the <b>Input state</b> button. The <b>Show JS</b> button shows the current
state in JavaScript syntax, which you can use to customize the presets data file, mdpresets.js,
if you’re running the simulation from your own server or hard drive.</li>
</ul>
<p><b>Technicalities:</b></p>
<ul>
<li>This simulation was created by <a href="http://physics.weber.edu/schroeder/">Daniel V. Schroeder</a>,
<a href="http://physics.weber.edu">Physics Department</a>,
<a href="http://weber.edu">Weber State University</a>.</li>
<li>If you can’t see the graphics display or the <a href="http://caniuse.com/#feat=input-range">slider
controls</a>, your browser is probably out of date.</li>
<li>This simulation is computationally intensive! It typically performs hundreds of basic computations
per atom per time step. Yet my personal computer can do this for a thousand atoms at a rate of well over
a thousand time steps per second. As of this writing, the latest versions of all major browsers on
traditional computers are quite fast (Opera and Chrome are fastest, followed by Firefox, and then by Safari
and Internet Explorer). Performance on mobile devices and their browsers varies enormously.</li>
<li>You are currently running version 1.0 of this simulation, last modified on August 6, 2014.
I apologize for the lack of full support for certain touch-screen devices and browsers. I hope
to improve touch-screen support in a future version, but the lack of standardization makes this
difficult. Future versions will be posted at
<a href="http://physics.weber.edu/schroeder/md/">http://physics.weber.edu/schroeder/md/</a>.</li>
<li>To look at the HTML5/JavaScript source code, just use your browser’s View Source
or Page Source command (possibly hidden under a “Developer” menu).
I’ve tried to make the code human-readable, with comments and such.</li>
</ul>
<hr><p>
<blockquote style="margin-bottom:0px;">
“If, in some cataclysm, all of scientific knowledge were to be destroyed,
and only one sentence passed on to the next generation of creatures, what statement would contain
the most information in the fewest words? I believe it is the <i>atomic hypothesis</i>
(or the atomic <i>fact</i>, or whatever you wish to call it) that <i>all things are made of
atoms—<i></i>little particles that move around in perpetual motion, attracting each
other when they are a little distance apart, but repelling upon being squeezed
into one another.</i> In that one sentence, you will see, there is an <i>enormous</i> amount of
information about the world, if just a little imagination and thinking are applied.”
</blockquote>
<p style="text-align:right; margin-top:3px;">
—<a href="http://www.feynmanlectures.caltech.edu/I_01.html#Ch1-S2">Richard Feynman</a></p>
</p>
<hr><p>
</div>
<script src="articlepresets.js"></script> <!-- contains big array of preset data, presetList[] -->
<script>
// ------------------------------ DECLARATIONS AND INITIALIZATIONS ------------------------------
// Assign DOM elements to global variables (unnecessary in most browsers when names coincide):
var canvas = document.getElementById('theCanvas');
var context = canvas.getContext('2d');
var canvasDiv = document.getElementById('canvasDiv');
var startButton = document.getElementById('startButton');
var nReadout = document.getElementById('nReadout');
var nSlider = document.getElementById('nSlider');
var sizeReadout = document.getElementById('sizeReadout');
var sizeSlider = document.getElementById('sizeSlider');
var gravReadout = document.getElementById('gravReadout');
var gravSlider = document.getElementById('gravSlider');
var gravx10 = document.getElementById('gravx10');
var dtReadout = document.getElementById('dtReadout');
var dtSlider = document.getElementById('dtSlider');
var stepsReadout = document.getElementById('stepsReadout');
var stepsSlider = document.getElementById('stepsSlider');
var presetSelect = document.getElementById('presetSelect');
var mouseSelect = document.getElementById('mouseSelect');
var bondSelect = document.getElementById('bondSelect');
var mColorSelect = document.getElementById('mColorSelect');
var bgColorSelect = document.getElementById('bgColorSelect');
//var cellListCheck = document.getElementById('cellListCheck'); // for diagnostic tests
var selectDataPanel = document.getElementById('selectDataPanel');
var fixTPanel = document.getElementById('fixTPanel');
var atomNumber = document.getElementById('atomNumber');
var atomTemp = document.getElementById('atomTemp');
var tempSlider = document.getElementById('tempSlider');
var dataReadout = document.getElementById('dataReadout');
var moreButton = document.getElementById('moreButton');
var dataPanel = document.getElementById('dataPanel');
var energyReadout = document.getElementById('energyReadout');
var dataArea = document.getElementById('dataArea');
var dataSelect = document.getElementById('dataSelect');
var autoIntervalControl = document.getElementById('autoIntervalControl');
var autoDataSelect = document.getElementById('autoDataSelect');
var moreDetailCheckPanel = document.getElementById('moreDetailCheckPanel');
var moreDetailCheck = document.getElementById('moreDetailCheck');
var allAtomsDataButtons = document.getElementById('allAtomsDataButtons');
var dtPBC = document.getElementById('dtPBC');
// Miscellaneous global variables:
var mobile = navigator.userAgent.match(/iPhone|iPad|iPod|Android|BlackBerry|Opera Mini|IEMobile|Kindle/i)
var nMax = Number(nSlider.max); // maximum number of molecules
var N = 0; // current number of molecules
var time = 0; // simulation time in natural units
var kineticE, potentialE, gravitationalE; // energies
var averageT = 0, averageP = 0; // temperature and pressure averaged over time
var pressure; // instantaneous P, computed each step
var rf = 0;
var totalT, totalP, sampleCount; // variables for computing average T and P
var lastSampleTime = 0; // simulation time when T and P were last sampled
var lastAutoRecordTime = 0; // used for auto-recording data at regular intervals
var momentumX, momentumY; // total momentum of system
var pxPerUnit = 1; // molecule diameter in pixels (dummy value until init)
var boxWidth = canvas.width / pxPerUnit; // width of box in natural units
var running = false; // will be true when running
var stepCount = 0; // number of calculation steps (used to monitor performance)
var startTime = 0; // clock time when stepCount was zero
var selectedAtom = -1; // index of atom selected for highlight/data (-1 if none)
var clickedAtom = 0; // index of atom that was clicked on
var dragging = false; // true when mouse (or touch) action is in progress
var mouseX, mouseY; // mouse coordinates in physics units (valid when dragging)
var drawingBond = false; // true when we're creating a bond between atoms
var recentSizeDecrease = false; // true for brief time after size is increased, to limit rate
var targetSize; // box size that we're moving toward, if resize in progress
var sizeStepTimer = 0; // timer (in simulation units) that counts down until next resize step
// Arrays of atoms' positions, velocities, and accelerations:
var x = new Array(nMax), y = new Array(nMax);
var vx = new Array(nMax), vy = new Array(nMax);
var ax = new Array(nMax), ay = new Array(nMax);
var atomColor = new Array(nMax); // and colors!
// Carefully constructed list of colors for indicating speeds:
var speedColorList = ['#0000e0','#0000ff','#4800f4','#6000e8','#7800d0','#9000b0','#b00080',
'#d00060','#e80030','#ff0000','#ff3800','#ff5000','#ff6800','#ff8000',
'#ff9600','#ffb400','#ffd200','#ffe600','#ffff00','#ffff78'];
// List of colors for selected atom, chosen to contrast with colors of the rest (kindof a kludge):
var selectedAtomColor = ['#00ff00','#ff00ff','#00ff00','#00ff00','#00ff00','#00ff00','#ff00ff',
'#00ff00','#00ff00','#00ff00','#00ff00','#808080','#00ff00'];
var fixedCount = 0; // number of atoms that are fixed (anchored)
var fixedList = new Array(nMax); // list of indices of fixed atoms
var maxBonds = nMax * 3; // maximum number of bonded pairs
var bondCount = 0; // current number of bonds
var bondList = new Array(maxBonds*2); // entry 0 is bonded to 1, 2 to 3, etc.
var fixedTList = new Array(); // list of fixed-T atoms
// Mysterious incantation that sometimes helps for smooth animation:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1); // second parameter is time in ms
};
})();
window.onload = init;
// Initializations
function init() {
// initialize differently for computers and mobile devices:
if (mobile) sizeSlider.value = "25"; else sizeSlider.value = "50";
if (mobile) nSlider.value = "100"; else nSlider.value = "500";
changeSize();
changeN();
// fill the presets menu:
for (var item=0; item<presetList.length; item++) {
var thePreset = document.createElement("option");
thePreset.text = presetList[item].name;
presetSelect.add(thePreset, null);
}
// register mouse/touch event listeners:
canvas.addEventListener('mousedown', mouseDown, false);
document.body.addEventListener('mousemove', mouseMove, false);
document.body.addEventListener('mouseup', mouseUp, false); // button release could occur outside canvas
canvas.addEventListener('touchstart', mouseDown, false);
document.body.addEventListener('touchmove', mouseMove, false);
document.body.addEventListener('touchend', mouseUp, false);
}
// ------------------------------ PHYSICS SIMULATION CODE ------------------------------
// Simulate function executes a bunch of steps and then schedules another call to itself:
function simulate() {
// Execute a bunch of time steps:
var stepsPerFrame = Number(stepsSlider.value);
for (var step=0; step<stepsPerFrame; step++) {
doStep();
}
paintCanvas();
stepCount += stepsPerFrame;
computeStats();
showStats();
if (running) {
// schedule the next animation frame:
//requestAnimFrame(function() { simulate(); }); // limits the frame rate
window.setTimeout(simulate, 1); // runs as fast as possible (nominal 1 ms delay)
}
}
// Execute a single time step (Verlet algorithm):
function doStep() {
var dt = Number(dtSlider.value);
var halfdt = 0.5 * dt;
var halfdtsquared = halfdt * dt;
for (var i=0; i<N; i++) {
x[i] += vx[i]*dt + ax[i]*halfdtsquared;
y[i] += vy[i]*dt + ay[i]*halfdtsquared;
vx[i] += ax[i]*halfdt;
vy[i] += ay[i]*halfdt;
}
computeAccelerations();
for (var i=0; i<N; i++) {
vx[i] += ax[i]*halfdt;
vy[i] += ay[i]*halfdt;
}
for (var i=0; i<fixedCount; i++) { // force v = 0 for fixed molecules
vx[fixedList[i]] = 0;
vy[fixedList[i]] = 0;
}
// Assign random velocities to fixed-T atoms:
for (var i=0; i<fixedTList.length; i++) {
if (Math.random() < 5*dt) { // do this only a small percentage of the time
var x1, x2, w;
do {
x1 = 2 * Math.random() - 1;
x2 = 2 * Math.random() - 1;
w = x1*x1 + x2*x2;
} while (w >= 1.0);
var u = Math.sqrt(-2*Math.log(w)/w); // polar Box-Muller transformation to get Gaussian distribution
vx[fixedTList[i].pointer] = u * x1 * Math.sqrt(fixedTList[i].temp);
vy[fixedTList[i].pointer] = u * x2 * Math.sqrt(fixedTList[i].temp);
}
}
time += dt;
updateTandP();
autoRecordData();
resizeStep();
}
// Compute accelerations of all molecules:
function computeAccelerations() {
var dx, dy, dx2, dy2, r, rSquared, rSquaredInv, attract, repel, fOverR, fx, fy;
var forceCutoff = 3.0; // distance beyond which we set force=0
var forceCutoff2 = forceCutoff*forceCutoff;
var pEatCutoff = 4 * (Math.pow(forceCutoff,-12) - Math.pow(forceCutoff,-6));
var g = Number(getGravity());
var wallStiffness = 50; // spring constant for bouncing off walls
var wallForce = 0.0;
potentialE = 0.0;
// Periodic boundary conditions
if (dtPBC.checked) {
for (var i=0; i<N; i++) { // simple double-loop over atoms for small system
x[i] = x[i] - Math.floor(x[i] / boxWidth) * boxWidth
y[i] = y[i] - Math.floor(y[i] / boxWidth) * boxWidth
ax[i] = 0.0;
ay[i] = 0.0;
rf = 0.0;
}
} else {
// first check for bounces off walls:
for (var i=0; i<N; i++) {
if (x[i] < 0.5) {
ax[i] = wallStiffness * (0.5 - x[i]);
wallForce += ax[i];
potentialE += 0.5 * wallStiffness * (0.5-x[i]) * (0.5-x[i]);
} else
if (x[i] > (boxWidth - 0.5)) {
ax[i] = wallStiffness * (boxWidth - 0.5 - x[i]);
wallForce -= ax[i];
potentialE += 0.5 * wallStiffness * (boxWidth-0.5-x[i]) * (boxWidth-0.5-x[i]);
} else
ax[i] = 0.0;
if (y[i] < 0.5) {
ay[i] = (wallStiffness * (0.5 - y[i]));
wallForce += ay[i];
potentialE += 0.5 * wallStiffness * (0.5-y[i]) * (0.5-y[i]);
} else
if (y[i] > (boxWidth - 0.5)) {
ay[i] = (wallStiffness * (boxWidth - 0.5 - y[i]));
wallForce -= ay[i];
potentialE += 0.5 * wallStiffness * (boxWidth-0.5-y[i]) * (boxWidth-0.5-y[i]);
} else
ay[i] = 0;
ay[i] -= g; // add gravity if any
}
pressure = wallForce / (4*boxWidth); // instantaneous pressure
}
// now compute interaction forces (Lennard-Jones potential):
// (this is where we spend most of our computation time, so try to optimize)
if ((N < 100) || (boxWidth < 4*forceCutoff) /* || !cellListCheck.checked */ ) {
for (var i=0; i<N; i++) { // simple double-loop over atoms for small system
for (var j=0; j<i; j++) {
dx = x[i] - x[j];
if (dtPBC.checked) {
dx = dx - Math.round(dx / boxWidth) * boxWidth
}
dx2 = dx * dx;
if (dx2 < forceCutoff2) { // make sure they're close enough to bother
dy = y[i] - y[j];
if (dtPBC.checked) {
dy = dy - Math.round(dy / boxWidth) * boxWidth
}
dy2 = dy * dy;
if (dy2 < forceCutoff2) {
rSquared = dx2 + dy2;
if (rSquared < forceCutoff2) {
rSquaredInv = 1.0 / rSquared;
attract = rSquaredInv * rSquaredInv * rSquaredInv;
repel = attract * attract;
potentialE += (4.0 * (repel - attract)) - pEatCutoff;
fOverR = 24.0 * ((2.0 * repel) - attract) * rSquaredInv;
fx = fOverR * dx;
fy = fOverR * dy;
ax[i] += fx; // add this force on to i's acceleration (m = 1)
ay[i] += fy;
ax[j] -= fx; // Newton's 3rd law
ay[j] -= fy;
if (dtPBC.checked) {
rf += dx * fx + dy * fy;
}
}
}
}
}
}
} else { // tricky O(N) cell-based approach for large system
var nCells, cellWidth, xCell, yCell, thisCell, neighborCell, xNeighborCell, yNeighborCell;
var neighborOffset = [{x:0, y:0}, {x:1, y:0}, {x:1, y:1}, {x:0, y:1}, {x:-1, y:1}]; // here, E, NE, N, and NW
nCells = Math.floor(boxWidth/forceCutoff); // number of cells in a row
cellWidth = boxWidth / nCells;
var listHeader = new Array(nCells*nCells); // linked list headers
for (var cell=0; cell<nCells*nCells; cell++) listHeader[cell] = -1; // set all cells to empty
var linkedList = new Array(N); // element i will point to next atom in same cell
for (var i=0; i<N; i++) { // this loop assembles the linked list of atoms by cell
xCell = Math.floor(x[i] / cellWidth); // figure out which cell the atom is in
yCell = Math.floor(y[i] / cellWidth);
if (dtPBC.checked) {
if (xCell < 0) xCell = nCells - 1;
if (xCell >= nCells) xCell = 0;
if (yCell < 0) yCell = nCells - 1;
if (yCell >= nCells) yCell = 0;
} else {
if (xCell < 0) xCell = 0;
if (xCell >= nCells) xCell = nCells - 1;
if (yCell < 0) yCell = 0;
if (yCell >= nCells) yCell = nCells - 1;
}
var cellHeaderIndex = xCell + nCells*yCell; // flatten 2D structure into 1D array
linkedList[i] = listHeader[cellHeaderIndex]; // this atom now points where the header used to
listHeader[cellHeaderIndex] = i; // header now points to his atom
} // linked list is now complete
for (xCell=0; xCell<nCells; xCell++) { // loop over cells
for (yCell=0; yCell<nCells; yCell++) {
thisCell = xCell + nCells*yCell; // index of this cell in header list
for (var neighborIndex=0; neighborIndex<5; neighborIndex++) { // loop over neighboring cells
if (dtPBC.checked) {
xNeighborCell = xCell + neighborOffset[neighborIndex].x;
if (xNeighborCell < 0) xNeighborCell=nCells - 1; // some neighbors DO actually exist
if (xNeighborCell >= nCells) xNeighborCell=0; // some neighbors DO actually exist
yNeighborCell = yCell + neighborOffset[neighborIndex].y;
if (yNeighborCell >= nCells) yNeighborCell=0;
neighborCell = xNeighborCell + nCells*yNeighborCell; // index of neighbor cell in header list
} else {
xNeighborCell = xCell + neighborOffset[neighborIndex].x;
if ((xNeighborCell < 0) || (xNeighborCell >= nCells)) continue; // some neighbors don't actually exist
yNeighborCell = yCell + neighborOffset[neighborIndex].y;
if (yNeighborCell >= nCells) continue;
neighborCell = xNeighborCell + nCells*yNeighborCell; // index of neighbor cell in header list
}
var i = listHeader[thisCell];
while (i > -1) { // loop over atoms in this cell
var j = listHeader[neighborCell];
if (neighborCell == thisCell) j = linkedList[i]; // be sure not to count atoms in this cell twice
while (j > -1) { // loop over atoms in neighbor cell
dx = x[i] - x[j];
if (dtPBC.checked) {
dx = dx - Math.round(dx / boxWidth) * boxWidth
}
dx2 = dx * dx;
if (dx2 < forceCutoff2) { // make sure they're close enough to bother
dy = y[i] - y[j];
if (dtPBC.checked) {
dy = dy - Math.round(dy / boxWidth) * boxWidth
}
dy2 = dy * dy;
if (dy2 < forceCutoff2) {
rSquared = dx2 + dy2;
if (rSquared < forceCutoff2) {
rSquaredInv = 1.0 / rSquared;
attract = rSquaredInv * rSquaredInv * rSquaredInv;
repel = attract * attract;
potentialE += (4.0 * (repel - attract)) - pEatCutoff;
fOverR = 24.0 * ((2.0 * repel) - attract) * rSquaredInv;
fx = fOverR * dx;
fy = fOverR * dy;
ax[i] += fx; // add this force on to i's acceleration (m = 1)
ay[i] += fy;
ax[j] -= fx; // Newton's 3rd law
ay[j] -= fy;
if (dtPBC.checked) {
rf += dx * fx + dy * fy;
}
}
}
}
j = linkedList[j];
} // end of loop over j
i = linkedList[i];
} // end of loop over i
} // end of loop over neighborIndex
} // end of loop over yCell
} // end of loop over xCell
} // end if (and end of L-J force computation)
// add elastic forces between bonded atoms:
var bondStrength = 100; // spring constant (vastly less than actual covalent bonds!)
for (var i=0; i<bondCount*2; i+=2) {
var i1 = bondList[i];
var i2 = bondList[i+1];
dx = x[i1] - x[i2];
dy = y[i1] - y[i2];
r = Math.sqrt(dx*dx + dy*dy);
var rOffset = r - 1.122462; // offset by L-J equilibrium position
potentialE += 0.5 * bondStrength * rOffset*rOffset;
fx = bondStrength * rOffset * dx / r;
fy = bondStrength * rOffset * dy / r;
ax[i1] -= fx;
ay[i1] -= fy;
ax[i2] += fx;
ay[i2] += fy;
}
// fixed atoms don't accelerate:
for (var i=0; i<fixedCount; i++) {
ax[fixedList[i]] = 0;
ay[fixedList[i]] = 0;
}
// if we're pulling on an atom it feels an elastic force toward mouse location:
if (dragging) {
var pullStrength = 1.0; // spring constant
dx = mouseX - x[clickedAtom];
dy = mouseY - y[clickedAtom];
ax[clickedAtom] += pullStrength * dx;
ay[clickedAtom] += pullStrength * dy;
}
} // end of function computeAccelerations
// Compute statistical data from current system state:
function computeStats() {
kineticE = 0;
gravitationalE = 0;
momentumX = 0;
momentumY = 0;
var g = Number(getGravity());
for (var i=0; i<N; i++) {
kineticE += 0.5 * (vx[i]*vx[i] + vy[i]*vy[i]);
gravitationalE += g * y[i];
momentumX += vx[i];
momentumY += vy[i];
}
var currentT = kineticE / (N - fixedCount);
safetyCheck(currentT);
}
// Periodically update the temperature and pressure accumulators
function updateTandP() {
var sampleInterval = 0.5; // more often than this would probably be pointless
if (time - lastSampleTime >= sampleInterval) {
sampleCount++;
kineticE = 0;
for (var i=0; i<N; i++) kineticE += 0.5 * (vx[i]*vx[i] + vy[i]*vy[i]);
var currentT = kineticE / (N - fixedCount);
totalT += currentT;
safetyCheck(currentT);
averageT = totalT / sampleCount;
if (dtPBC.checked) {
var currentP = N * averageT / (boxWidth * boxWidth) + 1 / (4 * boxWidth * boxWidth) * rf;
totalP += currentP;
averageP = totalP / sampleCount;
// totalP = rf;
} else {
totalP += pressure;
averageP = totalP / sampleCount;
}
lastSampleTime += sampleInterval;
}
}
// Check for run-away instability, and try to keep dt small enough to prevent it:
function safetyCheck(T) {
if (T > 1000) { // handle run-away instability
running = false;
var alertString = "Oops! The simulation has become unstable.\n";
alertString += "Avoid placing atoms so they overlap, and use a smaller time step at high temperature.\n";
alertString += "Click OK to restart.";
alert(alertString);
restart();
} else {
var safetyFactor = 4000;
var dt = Number(dtSlider.value);
if ((T > 1/(safetyFactor*dt*dt)) && (!dtFixed.checked)) {
var newdt = Math.max(Math.sqrt(1/(safetyFactor*T)) - 0.001, 0.001);
dtSlider.value = newdt.toFixed(3);
dtReadout.innerHTML = newdt.toFixed(3);
}
}
}
// Function to reset the time and the averages for temperature and pressure:
function reset() {
time = 0.0;
totalT = 0.0;
totalP = 0.0;
sampleCount = 0;
lastSampleTime = 0.0;
lastAutoRecordTime = 0.0;
showStats();