Difference between revisions of "Dynamics of interacting particles"

From Department of Theoretical and Applied Mechanics
Jump to: navigation, search
Line 64: Line 64:
 
     var SLJDenominator = 1 / (aCut * aCut - b2);    // denominator for calculation SLJ potential
 
     var SLJDenominator = 1 / (aCut * aCut - b2);    // denominator for calculation SLJ potential
  
     var thermostatEnabled = document.getElementById('checkbox_02').checked;    // термостат применяется к вязкости среды
+
     var thermostatEnabled = document.getElementById('checkbox_02').checked;    // thermostat for viscosity of the environment
     var addRandomV = document.getElementById('checkbox_03').checked;            // случайные скорости для разгона
+
     var addRandomV = document.getElementById('checkbox_03').checked;            // random speed for acceleration of the spheres
     var T0 = 1 * D;                    // масштаб температуры
+
     var T0 = 1 * D;                    // temperature
     var TGoal = TGoalK * T0;            // целевая температура системы
+
     var TGoal = TGoalK * T0;            // goal temperature
     var TActualMax = TActualMaxK * T0;  // макимальная температура, при которой работает термостатс (для избежания беск. скоростей)
+
     var TActualMax = TActualMaxK * T0;  // maximum temperature for thermostat
     var TActual = 0;                    // актуальная температура
+
     var TActual = 0;                    // actual temperature
     var k = 1;                          // постоянную Больцмана примем за единицу
+
     var k = 1;                          // Boltzmann constant
     var Tk = m / k;                    // ___в целях оптимизации___
+
     var Tk = m / k;                    // optimizing
     var viscFrictionTh = document.getElementById('checkbox_04').checked;        // термостат применяется к вязкости среды
+
     var viscFrictionTh = document.getElementById('checkbox_04').checked;        // application thermostat for the viscosity environment
     var internalFrictionTh = document.getElementById('checkbox_05').checked;    // термостат применяется к внутреннему трению
+
     var internalFrictionTh = document.getElementById('checkbox_05').checked;    // application thermostat for the internal friction
    var TempIntervalID;
 
  
     var Ka = K * a;                    // ___в целях оптимизации___
+
     var Ka = K * a;                    // optimizing
     var K2a2 = K * K * a2;              // ___в целях оптимизации___
+
     var K2a2 = K * K * a2;              // optimizing
  
     var dNd = null;                    // ссылка на захваченный курсором шар (drag & drop)
+
     var dNd = null;                    // taken sphere by cursor (drag & drop)
     var grad;                          // должен ли работать градиент (регулируется в функции setNy())
+
     var grad;                          // gradient
 
     var SLJEnabled = document.getElementById('checkbox_01').checked;
 
     var SLJEnabled = document.getElementById('checkbox_01').checked;
  
     this.setSlider_01 = function(c) {mg = c * m * g0;}; // функция для слайдера гравитации
+
     this.setSlider_01 = function(c) {mg = c * m * g0;}; // gravitation function
     this.setSlider_02 = function(c) {TGoal = c;};      // функция для слайдера термостата
+
     this.setSlider_02 = function(c) {TGoal = c;};      // thermostat function
 
     this.setNy = function(ny) {
 
     this.setNy = function(ny) {
 
         Ny = ny;
 
         Ny = ny;
 
         if (Ny > 8) {
 
         if (Ny > 8) {
             grad = false;                  // градиент не работает, если Ny > 8
+
             grad = false;                  // gradient doesn't work if Ny > 8
             context.fillStyle = "#3070d0";  // цвет, шара
+
             context.fillStyle = "#3070d0";  // sphere's colour
 
         } else
 
         } else
 
             grad = true;
 
             grad = true;
 
     };
 
     };
     this.setNy(Ny);                        // запускаем с уже присвоенным значением, чтобы обновились настройки градиента
+
     this.setNy(Ny);                        // start
 
     this.setCheckbox_01 = function(bool) {SLJEnabled = bool;};
 
     this.setCheckbox_01 = function(bool) {SLJEnabled = bool;};
 
     this.setCheckbox_02 = function(bool) {
 
     this.setCheckbox_02 = function(bool) {
Line 103: Line 102:
 
         document.getElementById('text_02').disabled = !bool;
 
         document.getElementById('text_02').disabled = !bool;
 
         if (bool) {
 
         if (bool) {
             TempIntervalID = setInterval(  // обновление информации о температуре
+
             TempIntervalID = setInterval(  // update informtion about temperature
 
                 function(){document.getElementById('Temperature').innerHTML = TActual.toFixed(3);}, 1000 / 3);
 
                 function(){document.getElementById('Temperature').innerHTML = TActual.toFixed(3);}, 1000 / 3);
 
         }
 
         }
 
         else {
 
         else {
             clearInterval(TempIntervalID);  // температура больше не подсчитывается - удаляем обновление информации о ней
+
             clearInterval(TempIntervalID);  // delete updating informtion about temperature
 
             document.getElementById('Temperature').innerHTML = "???"
 
             document.getElementById('Temperature').innerHTML = "???"
 
         }
 
         }
 
     };
 
     };
     this.setCheckbox_02(thermostatEnabled); // запускаем сразу, чтобы обновить состояния элементов интерфейса
+
     this.setCheckbox_02(thermostatEnabled); // start for updating parameters
 
     this.setCheckbox_03 = function(bool) {addRandomV = bool;};
 
     this.setCheckbox_03 = function(bool) {addRandomV = bool;};
 
     this.setCheckbox_04 = function(bool) {viscFrictionTh = bool;};
 
     this.setCheckbox_04 = function(bool) {viscFrictionTh = bool;};
 
     this.setCheckbox_05 = function(bool) {internalFrictionTh = bool;};
 
     this.setCheckbox_05 = function(bool) {internalFrictionTh = bool;};
  
     // Настройка интерфейса
+
     // Setup of the interface
  
 
     slider_01.min = 0;              slider_01.max = 5;
 
     slider_01.min = 0;              slider_01.max = 5;
 
     slider_01.step = 0.05;
 
     slider_01.step = 0.05;
     slider_01.value = mg / m / g0;          // начальное значение ползунка должно задаваться после min и max
+
     slider_01.value = mg / m / g0;          // the first position of the slider
 
     text_01.value = mg / m / g0;
 
     text_01.value = mg / m / g0;
 
     slider_02.min = 0;              slider_02.max = 5;
 
     slider_02.min = 0;              slider_02.max = 5;
 
     slider_02.step = 0.05;
 
     slider_02.step = 0.05;
     slider_02.value = TGoal;                // начальное значение ползунка должно задаваться после min и max
+
     slider_02.value = TGoal;                // the first position of the slider
 
     text_02.value = TGoal.toFixed(1);
 
     text_02.value = TGoal.toFixed(1);
  
     // Запуск новой системы
+
     // Start of the new system
  
     // следующие переменные должны пересчитываться каждый раз, когда мы изменяем значение Ny
+
     // the following variables have to be recalculated every time when we change value Ny
 
     var scale, w, h;
 
     var scale, w, h;
 
     var rScale13, rScaleShift;
 
     var rScale13, rScaleShift;
 
     this.newSystem = function() {
 
     this.newSystem = function() {
         scale = canvas.height / Ny / a0;    // масштабный коэффициент для перехода от расчетных к экранным координатам
+
         scale = canvas.height / Ny / a0;    // coefficient for transition from settlement to screen coordinates
         w = canvas.width / scale;          // ширина окна в расчетных координатах
+
         w = canvas.width / scale;          // window width in settlement coordinates
         h = canvas.height / scale;          // высота окна в расчетных координатах
+
         h = canvas.height / scale;          // window height in settlement coordinates
  
         rScale13 = r * scale * 1.3;        // ___в целях оптимизации___
+
         rScale13 = r * scale * 1.3;        // optimizing
         rScaleShift = r * scale / 5;        // ___в целях оптимизации___
+
         rScaleShift = r * scale / 5;        // optimizing
  
         this.setRandom();                  // задаем случайную конфигурацию
+
         this.setRandom();                  // set random configuration
 
     };
 
     };
  
 
     // Работа с мышью
 
     // Работа с мышью
  
     var mx_, my_;                              // буфер позиции мыши (для расчета скорости при отпускании шара)
+
     var mx_, my_;                              // cursor position
  
     canvas.onmousedown = function(e) {          // функция при нажатии клавиши мыши
+
     canvas.onmousedown = function(e) {          // function for press on the cursor
         var m = mouseCoords(e);                // получаем расчетные координаты курсора мыши
+
         var m = mouseCoords(e);                // coordinates for calculation of the cursor position
         // цикл в обратную сторону, чтобы захватывать шар, нарисованный "сверху"
+
         //return cycle
        // (т.к. цикл рисования идет в обычном порядке)
 
 
         for (var i = balls.length - 1; i >= 0; i--) {
 
         for (var i = balls.length - 1; i >= 0; i--) {
 
             var b = balls[i];
 
             var b = balls[i];
 
             var rx = b.x - m.x;
 
             var rx = b.x - m.x;
 
             var ry = b.y - m.y;
 
             var ry = b.y - m.y;
             var rLen2 = rx * rx + ry * ry;              // квадрат расстояния между курсором и центром шара
+
             var rLen2 = rx * rx + ry * ry;              // length between cursor and sphere
             if (rLen2 <= r2) {                          // курсор нажал на шар
+
             if (rLen2 <= r2) {                          // the cursor has pressed on a sphere
                 if (e.which == 1) {                    // нажата левая клавиша мыши
+
                 if (e.which == 1) {                    // mouse's left button has pressed
 
                     dNd = b;
 
                     dNd = b;
                     dNd.xPlus = dNd.x - m.x;            // сдвиг курсора относительно центра шара по x
+
                     dNd.xPlus = dNd.x - m.x;            // shift of the cursor on X
                     dNd.yPlus = dNd.y - m.y;            // сдвиг курсора относительно центра шара по y
+
                     dNd.yPlus = dNd.y - m.y;            // shift of the cursor on Y
 
                     mx_ = m.x;    my_ = m.y;
 
                     mx_ = m.x;    my_ = m.y;
                     canvas.onmousemove = mouseMove;    // пока клавиша нажата - работает функция перемещения
+
                     canvas.onmousemove = mouseMove;    // shift function work while button press
                 } else if (e.which == 3)                // нажата правая клавиша мыши
+
                 } else if (e.which == 3)                // mouse's right button has pressed
                     balls.splice(i, 1);                // удалить шар
+
                     balls.splice(i, 1);                // delete sphere
 
                 return;
 
                 return;
 
             }
 
             }
 
         }
 
         }
  
         // если не вышли по return из цикла - нажатие было вне шара, добавляем новый
+
         // if don't exit - add new sphere
 
         if (e.which == 1) {
 
         if (e.which == 1) {
             dNd = addNewBall(m.x, m.y, true);  // добавляем шар и сразу захватываем его курсором
+
             dNd = addNewBall(m.x, m.y, true);  // add sphere and take it
             if (dNd == null) return;            // если шар не добавился (из за стен или других шаров) - возвращаемся
+
             if (dNd == null) return;            // return if the ball doesn't add
             dNd.xPlus = 0;  dNd.yPlus = 0;      // держим шар по центру
+
             dNd.xPlus = 0;  dNd.yPlus = 0;      // set ball in center position
 
             mx_ = m.x;    my_ = m.y;
 
             mx_ = m.x;    my_ = m.y;
             canvas.onmousemove = mouseMove;    // пока клавиша нажата - работает функция перемещения
+
             canvas.onmousemove = mouseMove;    // shift function works while mouse's button has pressed
 
         }
 
         }
 
     };
 
     };
  
     document.onmouseup = function(e) {          // функция при отпускании клавиши мыши
+
     document.onmouseup = function(e) {          // function when mouse's button let off
         canvas.onmousemove = null;              // когда клавиша отпущена - функции перемещения нету
+
         canvas.onmousemove = null;              // shift function equal null when mouse's button let of
         dNd = null;                            // когда клавиша отпущена - захваченного курсором шара нету
+
         dNd = null;                            // ball doesn't exist when mouse's button let of
 
     };
 
     };
  
     function mouseMove(e) {                    // функция при перемещении мыши, работает только с зажатой ЛКМ
+
     function mouseMove(e) {                    // shift function when left mouse's button pressed
         var m = mouseCoords(e);                // получаем расчетные координаты курсора мыши
+
         var m = mouseCoords(e);                // coordinates for calculation
 
         dNd.x = m.x + dNd.xPlus;
 
         dNd.x = m.x + dNd.xPlus;
 
         dNd.y = m.y + dNd.yPlus;
 
         dNd.y = m.y + dNd.yPlus;
Line 192: Line 190:
 
     }
 
     }
  
     function mouseCoords(e) {                  // функция возвращает расчетные координаты курсора мыши
+
     function mouseCoords(e) {                  // function returns settlement coordinates of the cursor of a mouse
 
         var m = [];
 
         var m = [];
 
         var rect = canvas.getBoundingClientRect();
 
         var rect = canvas.getBoundingClientRect();
Line 202: Line 200:
 
     // Работа с массивом
 
     // Работа с массивом
  
     var balls = [];                            // массив шаров
+
     var balls = [];                            // sphere's massive
 
     var addNewBall =  function(x, y, check) {
 
     var addNewBall =  function(x, y, check) {
         // проверка - не пересекается ли новый шар со стенами или уже существующими шарами
+
         // check - whether is crossed a new sphere with walls or already existing spheres
 
         if (check) {
 
         if (check) {
 
             if (x - r < 0 || x + r > w || y - r < 0 || y + r > h) return null;
 
             if (x - r < 0 || x + r > w || y - r < 0 || y + r > h) return null;
Line 217: Line 215:
 
         var b = [];
 
         var b = [];
  
         b.x = x;                b.y = y;        // расчетные координаты шара
+
         b.x = x;                b.y = y;        // calculation cordinates
         b.fx = 0;              b.fy = mg;      // сила, действующая на шар
+
         b.fx = 0;              b.fy = mg;      // force
         b.vx = 0;              b.vy = 0;      // скорость
+
         b.vx = 0;              b.vy = 0;      // velocity
  
         balls[balls.length] = b;                // добавить элемент в конец массива
+
         balls[balls.length] = b;                // add an element in end of massive
 
         return b;
 
         return b;
 
     };
 
     };
  
     this.setEmpty = function() {balls = [];};  // пустое поле
+
     this.setEmpty = function() {balls = [];};  // free area
  
     this.setRandom = function() {              // случайная конфигурация
+
     this.setRandom = function() {              // random configuration
 
         balls = [];
 
         balls = [];
 
         for (var i = 0; i < 1000; i++)
 
         for (var i = 0; i < 1000; i++)
Line 234: Line 232:
  
 
     var sqrt3 = Math.sqrt(3);
 
     var sqrt3 = Math.sqrt(3);
     this.setTriangularLattice = function() {            // задать на поле треугольную решетку
+
     this.setTriangularLattice = function() {            // triangle lattice
 
         balls = [];
 
         balls = [];
         var center = (w - Math.floor(w / r) * r) / 2;  // сдвиг, решетка будет появляться по середине по горизонтали
+
         var center = (w - Math.floor(w / r) * r) / 2;  // shift
 
         for (var j = 0; j < Math.floor(h / (sqrt3 * r)); j++)
 
         for (var j = 0; j < Math.floor(h / (sqrt3 * r)); j++)
 
             for (var i = 0; i < Math.floor(w / r) - 1; i++)
 
             for (var i = 0; i < Math.floor(w / r) - 1; i++)
Line 242: Line 240:
 
     };
 
     };
  
     // Основной цикл программы
+
     // program's cycle
  
 
     function control() {
 
     function control() {
Line 249: Line 247:
 
     }
 
     }
  
     // Расчетная часть программы
+
     // calculation part
  
     function physics() {                            // то, что происходит каждый шаг времени
+
     function physics() {                             
 
         for (var s = 1; s <= spf; s++) {
 
         for (var s = 1; s <= spf; s++) {
  
Line 265: Line 263:
 
                     TActual = Tk * v2Average;
 
                     TActual = Tk * v2Average;
  
                     if (addRandomV) {              // случайные скорости, если температура слишком мала
+
                     if (addRandomV) {              // random velocities if the temperature is low
 
                         if (TGoal > 0.15 && TActual < 0.1) {
 
                         if (TGoal > 0.15 && TActual < 0.1) {
 
                             for (var i2 = 0; i2 < balls.length; i2++) {
 
                             for (var i2 = 0; i2 < balls.length; i2++) {
Line 274: Line 272:
 
                     }
 
                     }
  
                     if (TActual < TActualMax) {        // из за того, что мышкой можно задать шарам запредельную скорость
+
                     if (TActual < TActualMax) {        //because of the fact that a mouse spheres can set ultraboundary speed
                         if (viscFrictionTh) BViscTh = BVisc * (TActual - TGoal);                // действие термостата
+
                         if (viscFrictionTh) BViscTh = BVisc * (TActual - TGoal);                // thermostat
                         if (internalFrictionTh) BInternalTh = BInternal * (TActual - TGoal);    // действие термостата
+
                         if (internalFrictionTh) BInternalTh = BInternal * (TActual - TGoal);    // thermostat
 
                     }
 
                     }
 
                 } else
 
                 } else
                     TActual = 0;                        // для датчика температуры на странице
+
                     TActual = 0;                        // for thermostat on page
 
             }
 
             }
  
             // пересчет сил идет отдельным массивом, т.к. далее будут добавляться силы взаимодействия между шарами
+
             //recalculation of forces goes the certain massif since forces of interaction between spheres will be added further
 
             for (var i0 = 0; i0 < balls.length; i0++) {
 
             for (var i0 = 0; i0 < balls.length; i0++) {
 
                 balls[i0].fx = - BViscTh * balls[i0].vx;
 
                 balls[i0].fx = - BViscTh * balls[i0].vx;
Line 289: Line 287:
  
 
             for (var i = 0; i < balls.length; i++) {
 
             for (var i = 0; i < balls.length; i++) {
                 // расчет взаимодействия производится со всеми следующими шарами в массиве,
+
                 // calculation of interaction is made with all following spheres in the massif not to consider each interaction twice
                // чтобы не считать каждое взаимодействие дважды
 
 
                 var b = balls[i];
 
                 var b = balls[i];
 
                 for (var j = i + 1; j < balls.length; j++) {
 
                 for (var j = i + 1; j < balls.length; j++) {
 
                     var b2 = balls[j];
 
                     var b2 = balls[j];
                     var rx = b.x - b2.x;  var ry = b.y - b2.y;        // вектор смотрит на первый шар (b)
+
                     var rx = b.x - b2.x;  var ry = b.y - b2.y;        // vector on the first ball (b)
                     var r2 = rx * rx + ry * ry;                        // квадрат расстояния между шарами
+
                     var r2 = rx * rx + ry * ry;                        // length between spheres
 
                     var rLen = (Math.sqrt(r2));
 
                     var rLen = (Math.sqrt(r2));
                     if (rLen > aCut) continue;                          // проверка на радиус обрезания
+
                     if (rLen > aCut) continue;                          // check for cutting radius
  
                     // если расстояние между частицами мало, силы будут посчитаны для K * a
+
                     // if the distance between particles isn't enough, forces will be counted like K * a
 
                     if (rLen < Ka) {
 
                     if (rLen < Ka) {
                         if (rLen > 0.00001) {                          // проверка, чтобы избежать деления на 0
+
                         if (rLen > 0.00001) {                          // check
 
                             rx = rx / rLen * Ka;
 
                             rx = rx / rLen * Ka;
 
                             ry = ry / rLen * Ka;
 
                             ry = ry / rLen * Ka;
 
                         }
 
                         }
 
                         r2 = K2a2;
 
                         r2 = K2a2;
                         rLen = Ka;                                     // корень K2a2
+
                         rLen = Ka;                                
 
                     }
 
                     }
  
 
                     // сила взаимодействия
 
                     // сила взаимодействия
                     var s2 = a2 / r2;        var s4 = s2 * s2;        // ___в целях оптимизации___
+
                     var s2 = a2 / r2;        var s4 = s2 * s2;        // optimizing
                     var F = LJCoeff * s4 * s4 * (s4 * s2 - 1);          // сила взаимодействия Леннарда-Джонса
+
                     var F = LJCoeff * s4 * s4 * (s4 * s2 - 1);          // LJ interaction force
 
                     if (SLJEnabled) {
 
                     if (SLJEnabled) {
                         var kSLJ;                                          // k(r) - сглаживающий коэффициент SLJ потенциала
+
                         var kSLJ;                                          // k(r) - coefficient SLJ potential
 
                         if (r <= b) kSLJ = 1;
 
                         if (r <= b) kSLJ = 1;
 
                         else {
 
                         else {
 
                             var brackets = (r2 - b2) * SLJDenominator;
 
                             var brackets = (r2 - b2) * SLJDenominator;
 
                             kSLJ = 1 - brackets * brackets;
 
                             kSLJ = 1 - brackets * brackets;
                         }                                                   // случай rLen > aCut обработан выше
+
                         }                                                
 
                         F *= kSLJ;
 
                         F *= kSLJ;
 
                     }
 
                     }
  
                     // сила внутреннего трения между частицами
+
                     // internal friction between spheres
 
                     if (r2 < a2) {
 
                     if (r2 < a2) {
                         var vx21 = b.vx - b2.vx;    var vy21 = b.vy - b2.vy;    // вектор смотрит на первый шар (b)
+
                         var vx21 = b.vx - b2.vx;    var vy21 = b.vy - b2.vy;    // vector on the first ball (b)
 
                         var ex = rx / rLen;        var ey = ry / rLen;
 
                         var ex = rx / rLen;        var ey = ry / rLen;
 
                         var v = vx21 * ex + vy21 * ey;
 
                         var v = vx21 * ex + vy21 * ey;
Line 330: Line 327:
 
                     }
 
                     }
  
                     // суммируем силы
+
                     // summirize forces
 
                     var Fx = F * rx;        var Fy = F * ry;
 
                     var Fx = F * rx;        var Fy = F * ry;
  
Line 337: Line 334:
 
                 }
 
                 }
  
                 if (b == dNd) continue;  // если шар схвачен курсором - его вз. со стенами и перемещение не считаем
+
                 if (b == dNd) continue;  // if the sphere is grabbed with the cursor - his interaction with walls and we don't consider movement
  
 
                 if (b.y + r > h) { b.fy += -CWall * (b.y + r - h) - BWall * b.vy; }
 
                 if (b.y + r > h) { b.fy += -CWall * (b.y + r - h) - BWall * b.vy; }
Line 350: Line 347:
 
     }
 
     }
  
     // Рисование
+
     // drawing
 
     function draw() {
 
     function draw() {
         context.clearRect(0, 0, w * scale, h * scale);      // очистить экран
+
         context.clearRect(0, 0, w * scale, h * scale);      // clear
 
         for (var i = 0; i < balls.length; i++){
 
         for (var i = 0; i < balls.length; i++){
 
             var xS = balls[i].x * scale;          var yS = balls[i].y * scale;
 
             var xS = balls[i].x * scale;          var yS = balls[i].y * scale;
 
             if (grad) {
 
             if (grad) {
                 // расчет градиента нужно проводить для каждого шара
+
                 // calculation of a gradient needs to be carried out for each sphere
 
                 var gradient = context.createRadialGradient(xS, yS, rScale13, xS - rScaleShift, yS + rScaleShift, 0);
 
                 var gradient = context.createRadialGradient(xS, yS, rScale13, xS - rScaleShift, yS + rScaleShift, 0);
 
                 gradient.addColorStop(0, "#0000bb");
 
                 gradient.addColorStop(0, "#0000bb");
Line 370: Line 367:
 
     }
 
     }
  
     // Запуск системы
+
     // Start
 
     this.newSystem();
 
     this.newSystem();
 
     setInterval(control, 1000 / fps);
 
     setInterval(control, 1000 / fps);
     // след. функция обновляет информацию о количестве частиц на поле
+
     // function updates information on quantity of particles in the field
 
     setInterval(function(){document.getElementById('ballsNum').innerHTML = balls.length;}, 1000 / 20);
 
     setInterval(function(){document.getElementById('ballsNum').innerHTML = balls.length;}, 1000 / 20);
 
}
 
}
Line 393: Line 390:
 
         mg =
 
         mg =
 
         <input id="text_01" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
 
         <input id="text_01" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
             // если введено не число - строка не пройдет валидацию по паттерну выше, и checkValidity() вернет false
+
             // if not the number is entered - the line won't pass validation on a pattern above, and checkValidity () will return false
 
             if (!this.checkValidity()) return;
 
             if (!this.checkValidity()) return;
 
             app.setSlider_01(this.value);
 
             app.setSlider_01(this.value);
Line 427: Line 424:
 
         <input type="range" id="slider_02" style="width: 150px;" oninput="app.setSlider_02(this.value); document.getElementById('text_02').value = this.value;">
 
         <input type="range" id="slider_02" style="width: 150px;" oninput="app.setSlider_02(this.value); document.getElementById('text_02').value = this.value;">
 
         T ⋅ T0 = <input id="text_02" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
 
         T ⋅ T0 = <input id="text_02" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
             // если введено не число - строка не пройдет валидацию по паттерну выше, и checkValidity() вернет false
+
             // if not the number is entered - the line won't pass validation on a pattern above, and checkValidity () will return false
 
             if (!this.checkValidity()) return;
 
             if (!this.checkValidity()) return;
 
             app.setSlider_02(this.value);
 
             app.setSlider_02(this.value);

Revision as of 17:42, 29 May 2016

Virtual laboratory > Dynamics of interacting particles

Here is the latest version of the program, which simulates the dynamics of interacting particles. Each particle is a viscoelastic sphere. The interaction between the spheres is described by Lennard-Jones potential


Гравитация: mg = ⋅ m ⋅ g0

Сколько шаров помещается по вертикали:  

Конфигурация:

Short Lennard-Jones potential

Термостат: T ⋅ T0 =
Разгон случайными скоростями
Термостат действует на: Внешнее трение Внутреннее трение
T ≈

Количество частиц:

Download program: Balls_v6_release.zip

The text of the program is written in JavaScript (the developers are Кривцов Антон, Цветков Денис):

Файл "Balls_v6_release.js"

  1 function MainBalls(canvas, slider_01, text_01, slider_02, text_02) {
  2 
  3     canvas.onselectstart = function () {return false;};     // lock allocation canvas
  4 
  5     // Starting parameters
  6 
  7     var context = canvas.getContext("2d");                  // context for drawing
  8     canvas.oncontextmenu = function (e) {return false;};    // lock context menu
  9 
 10     var Pi = 3.1415926;                 // "pi"
 11 
 12     var m0 = 1;                         // weight
 13     var t0 = 1;                         // Time (period of fluctuation)
 14     var a0 = 1;                         // length (sphere's diameter)
 15 
 16     var g0 = a0 / t0 / t0;              // acceleration
 17     var k0 = 2 * Pi / t0;               // frequency
 18     var C0 = m0 * k0 * k0;              // rigidity
 19     var B0 = 2 * m0 * k0;               // viscosity
 20 
 21     // *** Physical parameters  ***
 22 
 23     var Ny = 5;                         // The number of the spheres which are placed vertically
 24     var m = 1 * m0;                     // weight
 25     var CWall = 10 * C0;                // wall's rigidity
 26     var CBall = 0.1 * CWall;            // rigidity between spheres
 27     var BVisc = 0.008 * B0;             // viscosity of the environment
 28     var BInternal = 0.01 * B0;          // internal friction
 29     var BWall = 0.03 * B0;              // viscosity of the walls
 30     var mg = 0.25 * m * g0;             // gravitational force
 31     var r = 0.5 * a0;                   // radius of the sphere
 32     var K = 0.7;                        // forces depending on radius
 33     var a = 2 * r;                      // equilibrium state between particles
 34     var aCut = 2 * a;                   // cutting radius
 35     var TGoalK = 2;                     // temperature of the system TGoalK * D
 36     var TActualMaxK = 200;              // maximum temperature when thermostat work
 37 
 38     // *** Calculation parameters ***
 39 
 40     var fps = 60;                       // frames per second - число кадров в секунду (качечтво отображения)
 41     var spf = 100;                      // steps per frame
 42     var dt  = 0.04 * t0 / fps;          // integration step
 43 
 44     // Running program 
 45 
 46     var r2 = r * r;                     // optimizing
 47     var a2 = a * a;                     // optimizing
 48     var D = a2 * CBall / 72;            // bond's energy between spheres
 49     var LJCoeff = 12 * D / a2;          // coefficient for Lennard-Jones potential
 50     var b = Math.pow(13 / 7, 6) * a;    // coefficient for SLJ potential
 51     var b2 = b * b;                     // optimizing
 52     var SLJDenominator = 1 / (aCut * aCut - b2);    // denominator for calculation SLJ potential
 53 
 54     var thermostatEnabled = document.getElementById('checkbox_02').checked;     // thermostat for viscosity of the environment 
 55     var addRandomV = document.getElementById('checkbox_03').checked;            // random speed for acceleration of the spheres
 56     var T0 = 1 * D;                     // temperature
 57     var TGoal = TGoalK * T0;            // goal temperature 
 58     var TActualMax = TActualMaxK * T0;  // maximum temperature for thermostat
 59     var TActual = 0;                    // actual temperature 
 60     var k = 1;                          // Boltzmann constant
 61     var Tk = m / k;                     // optimizing
 62     var viscFrictionTh = document.getElementById('checkbox_04').checked;        // application thermostat for the viscosity environment
 63     var internalFrictionTh = document.getElementById('checkbox_05').checked;    // application thermostat for the internal friction
 64 
 65     var Ka = K * a;                     // optimizing
 66     var K2a2 = K * K * a2;              // optimizing
 67 
 68     var dNd = null;                     // taken sphere by cursor (drag & drop)
 69     var grad;                           // gradient
 70     var SLJEnabled = document.getElementById('checkbox_01').checked;
 71 
 72     this.setSlider_01 = function(c) {mg = c * m * g0;}; // gravitation function
 73     this.setSlider_02 = function(c) {TGoal = c;};       // thermostat function
 74     this.setNy = function(ny) {
 75         Ny = ny;
 76         if (Ny > 8) {
 77             grad = false;                   // gradient doesn't work if Ny > 8
 78             context.fillStyle = "#3070d0";  // sphere's colour
 79         } else
 80             grad = true;
 81     };
 82     this.setNy(Ny);                         // start
 83     this.setCheckbox_01 = function(bool) {SLJEnabled = bool;};
 84     this.setCheckbox_02 = function(bool) {
 85         thermostatEnabled = bool;
 86         document.getElementById('checkbox_03').disabled = !bool;
 87         document.getElementById('checkbox_04').disabled = !bool;
 88         document.getElementById('checkbox_05').disabled = !bool;
 89         document.getElementById('slider_02').disabled = !bool;
 90         document.getElementById('text_02').disabled = !bool;
 91         if (bool) {
 92             TempIntervalID = setInterval(   // update informtion about temperature
 93                 function(){document.getElementById('Temperature').innerHTML = TActual.toFixed(3);}, 1000 / 3);
 94         }
 95         else {
 96             clearInterval(TempIntervalID);  // delete updating informtion about temperature
 97             document.getElementById('Temperature').innerHTML = "???"
 98         }
 99     };
100     this.setCheckbox_02(thermostatEnabled); // start for updating parameters
101     this.setCheckbox_03 = function(bool) {addRandomV = bool;};
102     this.setCheckbox_04 = function(bool) {viscFrictionTh = bool;};
103     this.setCheckbox_05 = function(bool) {internalFrictionTh = bool;};
104 
105     // Setup of the interface
106 
107     slider_01.min = 0;               slider_01.max = 5;
108     slider_01.step = 0.05;
109     slider_01.value = mg / m / g0;          // the first position of the slider
110     text_01.value = mg / m / g0;
111     slider_02.min = 0;               slider_02.max = 5;
112     slider_02.step = 0.05;
113     slider_02.value = TGoal;                // the first position of the slider
114     text_02.value = TGoal.toFixed(1);
115 
116     // Start of the new system
117 
118     // the following variables have to be recalculated every time when we change value Ny
119     var scale, w, h;
120     var rScale13, rScaleShift;
121     this.newSystem = function() {
122         scale = canvas.height / Ny / a0;    // coefficient for transition from settlement to screen coordinates
123         w = canvas.width / scale;           // window width in settlement coordinates
124         h = canvas.height / scale;          // window height in settlement coordinates
125 
126         rScale13 = r * scale * 1.3;         // optimizing
127         rScaleShift = r * scale / 5;        // optimizing
128 
129         this.setRandom();                   // set random configuration
130     };
131 
132     // Работа с мышью
133 
134     var mx_, my_;                               // cursor position
135 
136     canvas.onmousedown = function(e) {          // function for press on the cursor
137         var m = mouseCoords(e);                 // coordinates for calculation of the cursor position
138         //return cycle
139         for (var i = balls.length - 1; i >= 0; i--) {
140             var b = balls[i];
141             var rx = b.x - m.x;
142             var ry = b.y - m.y;
143             var rLen2 = rx * rx + ry * ry;              // length between cursor and sphere
144             if (rLen2 <= r2) {                          // the cursor has pressed on a sphere
145                 if (e.which == 1) {                     // mouse's left button has pressed
146                     dNd = b;
147                     dNd.xPlus = dNd.x - m.x;            // shift of the cursor on X
148                     dNd.yPlus = dNd.y - m.y;            // shift of the cursor on Y
149                     mx_ = m.x;    my_ = m.y;
150                     canvas.onmousemove = mouseMove;     // shift function work while button press
151                 } else if (e.which == 3)                // mouse's right button has pressed
152                     balls.splice(i, 1);                 // delete sphere
153                 return;
154             }
155         }
156 
157         // if don't exit - add new sphere
158         if (e.which == 1) {
159             dNd = addNewBall(m.x, m.y, true);   // add sphere and take it
160             if (dNd == null) return;            // return if the ball doesn't add
161             dNd.xPlus = 0;  dNd.yPlus = 0;      // set ball in center position
162             mx_ = m.x;    my_ = m.y;
163             canvas.onmousemove = mouseMove;     // shift function works while mouse's button has pressed
164         }
165     };
166 
167     document.onmouseup = function(e) {          // function when mouse's button let off
168         canvas.onmousemove = null;              // shift function equal null when mouse's button let of
169         dNd = null;                             // ball doesn't exist when mouse's button let of
170     };
171 
172     function mouseMove(e) {                     // shift function when left mouse's button pressed
173         var m = mouseCoords(e);                 // coordinates for calculation
174         dNd.x = m.x + dNd.xPlus;
175         dNd.y = m.y + dNd.yPlus;
176         dNd.vx = 0.6 * (m.x - mx_) / dt / fps;   dNd.vy = 0.6 * (m.y - my_) / dt / fps;
177         mx_ = m.x;    my_ = m.y;
178     }
179 
180     function mouseCoords(e) {                   // function returns settlement coordinates of the cursor of a mouse
181         var m = [];
182         var rect = canvas.getBoundingClientRect();
183         m.x = (e.clientX - rect.left) / scale;
184         m.y = (e.clientY - rect.top) / scale;
185         return m;
186     }
187 
188     // Работа с массивом
189 
190     var balls = [];                             // sphere's massive
191     var addNewBall =  function(x, y, check) {
192         // check - whether is crossed a new sphere with walls or already existing spheres
193         if (check) {
194             if (x - r < 0 || x + r > w || y - r < 0 || y + r > h) return null;
195             for (var i = 0; i < balls.length; i++) {
196                 var rx = balls[i].x - x;
197                 var ry = balls[i].y - y;
198                 var rLen2 = rx * rx + ry * ry;
199                 if (rLen2 < 4 * r2) return null;
200             }
201         }
202 
203         var b = [];
204 
205         b.x = x;                b.y = y;        // calculation cordinates
206         b.fx = 0;               b.fy = mg;      // force
207         b.vx = 0;               b.vy = 0;       // velocity
208 
209         balls[balls.length] = b;                // add an element in end of massive
210         return b;
211     };
212 
213     this.setEmpty = function() {balls = [];};   // free area
214 
215     this.setRandom = function() {               // random configuration
216         balls = [];
217         for (var i = 0; i < 1000; i++)
218             addNewBall(Math.random() * w, Math.random() * h, true);
219     };
220 
221     var sqrt3 = Math.sqrt(3);
222     this.setTriangularLattice = function() {            // triangle lattice
223         balls = [];
224         var center = (w - Math.floor(w / r) * r) / 2;   // shift
225         for (var j = 0; j < Math.floor(h / (sqrt3 * r)); j++)
226             for (var i = 0; i < Math.floor(w / r) - 1; i++)
227                 if ((i + j) % 2 == 0) addNewBall(r * (i + 1) + center, h - r * (1 + sqrt3 * j), false);
228     };
229 
230     // program's cycle
231 
232     function control() {
233         physics();
234         draw();
235     }
236 
237     // calculation part
238 
239     function physics() {                            
240         for (var s = 1; s <= spf; s++) {
241 
242             var BViscTh = BVisc;
243             var BInternalTh = BInternal;
244             // работа термостата
245             if (thermostatEnabled) {
246                 if (balls.length > 0) {
247                     var v2Sum = 0;
248                     for (var i1 = 0; i1 < balls.length; i1++)
249                         v2Sum += balls[i1].vx * balls[i1].vx + balls[i1].vy * balls[i1].vy;
250                     var v2Average = v2Sum / balls.length;
251                     TActual = Tk * v2Average;
252 
253                     if (addRandomV) {               // random velocities if the temperature is low
254                         if (TGoal > 0.15 && TActual < 0.1) {
255                             for (var i2 = 0; i2 < balls.length; i2++) {
256                                 balls[i2].vx += 0.3 * (1 - 2 * Math.random());
257                                 balls[i2].vy += 0.3 * (1 - 2 * Math.random());
258                             }
259                         }
260                     }
261 
262                     if (TActual < TActualMax) {         //because of the fact that a mouse spheres can set ultraboundary speed
263                         if (viscFrictionTh) BViscTh = BVisc * (TActual - TGoal);                // thermostat
264                         if (internalFrictionTh) BInternalTh = BInternal * (TActual - TGoal);    // thermostat
265                     }
266                 } else
267                     TActual = 0;                        // for thermostat on page
268             }
269 
270             //recalculation of forces goes the certain massif since forces of interaction between spheres will be added further
271             for (var i0 = 0; i0 < balls.length; i0++) {
272                 balls[i0].fx = - BViscTh * balls[i0].vx;
273                 balls[i0].fy = mg - BViscTh * balls[i0].vy;
274             }
275 
276             for (var i = 0; i < balls.length; i++) {
277                 // calculation of interaction is made with all following spheres in the massif not to consider each interaction twice
278                 var b = balls[i];
279                 for (var j = i + 1; j < balls.length; j++) {
280                     var b2 = balls[j];
281                     var rx = b.x - b2.x;   var ry = b.y - b2.y;         // vector on the first ball (b)
282                     var r2 = rx * rx + ry * ry;                         // length between spheres
283                     var rLen = (Math.sqrt(r2));
284                     if (rLen > aCut) continue;                          // check for cutting radius
285 
286                     // if the distance between particles isn't enough, forces will be counted like K * a
287                     if (rLen < Ka) {
288                         if (rLen > 0.00001) {                           // check
289                             rx = rx / rLen * Ka;
290                             ry = ry / rLen * Ka;
291                         }
292                         r2 = K2a2;
293                         rLen = Ka;                                 
294                     }
295 
296                     // сила взаимодействия
297                     var s2 = a2 / r2;         var s4 = s2 * s2;         // optimizing
298                     var F = LJCoeff * s4 * s4 * (s4 * s2 - 1);          // LJ interaction force
299                     if (SLJEnabled) {
300                         var kSLJ;                                           // k(r) - coefficient SLJ potential
301                         if (r <= b) kSLJ = 1;
302                         else {
303                             var brackets = (r2 - b2) * SLJDenominator;
304                             kSLJ = 1 - brackets * brackets;
305                         }                                                  
306                         F *= kSLJ;
307                     }
308 
309                     // internal friction between spheres
310                     if (r2 < a2) {
311                         var vx21 = b.vx - b2.vx;    var vy21 = b.vy - b2.vy;    // vector on the first ball (b)
312                         var ex = rx / rLen;         var ey = ry / rLen;
313                         var v = vx21 * ex + vy21 * ey;
314                         F -= F * BInternalTh / rLen * v;
315                     }
316 
317                     // summirize forces
318                     var Fx = F * rx;        var Fy = F * ry;
319 
320                     b.fx += Fx;             b.fy += Fy;
321                     b2.fx -= Fx;            b2.fy -= Fy;
322                 }
323 
324                 if (b == dNd) continue;  // if the sphere is grabbed with the cursor - his interaction with walls and we don't consider movement
325 
326                 if (b.y + r > h) { b.fy += -CWall * (b.y + r - h) - BWall * b.vy; }
327                 if (b.y - r < 0) { b.fy += -CWall * (b.y - r) - BWall * b.vy;}
328                 if (b.x + r > w) { b.fx += -CWall * (b.x + r - w) - BWall * b.vx; }
329                 if (b.x - r < 0) { b.fx += -CWall * (b.x - r) - BWall * b.vx; }
330 
331                 b.vx += b.fx / m * dt;        b.vy += b.fy / m * dt;
332                 b.x += b.vx * dt;             b.y += b.vy * dt;
333             }
334         }
335     }
336 
337     // drawing
338     function draw() {
339         context.clearRect(0, 0, w * scale, h * scale);      // clear
340         for (var i = 0; i < balls.length; i++){
341             var xS = balls[i].x * scale;           var yS = balls[i].y * scale;
342             if (grad) {
343                 // calculation of a gradient needs to be carried out for each sphere
344                 var gradient = context.createRadialGradient(xS, yS, rScale13, xS - rScaleShift, yS + rScaleShift, 0);
345                 gradient.addColorStop(0, "#0000bb");
346                 gradient.addColorStop(1, "#44ddff");
347                 context.fillStyle = gradient;
348             }
349 
350             context.beginPath();
351             context.arc(xS, yS, r * scale, 0, 2 * Math.PI, false);
352             context.closePath();
353             context.fill();
354         }
355     }
356 
357     // Start
358     this.newSystem();
359     setInterval(control, 1000 / fps);
360     // function updates information on quantity of particles in the field
361     setInterval(function(){document.getElementById('ballsNum').innerHTML = balls.length;}, 1000 / 20);
362 }

Файл "Balls_v6_release.html"

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8" />
 5     <title>Balls</title>
 6     <script src="Balls_v6_release.js"></script>
 7 </head>
 8 <body>
 9     <canvas id="canvasBalls" width="800" height="600" style="border:1px solid #000000;"></canvas>
10     <br>
11     <div>Gravitation: 
12         <input type="range" id="slider_01" style="width: 150px;" oninput="app.setSlider_01(this.value); document.getElementById('text_01').value = this.value;">
13         mg =
14         <input id="text_01" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
15             // if not the number is entered - the line won't pass validation on a pattern above, and checkValidity () will return false
16             if (!this.checkValidity()) return;
17             app.setSlider_01(this.value);
18             document.getElementById('slider_01').value = this.value;
19         ">
20      ⋅ m ⋅ g0</div><br>
21 
22     <div>The number of the spheres which are placed vertically: 
23         <input type="button" style="width: 30px" name="" onclick="app.setNy(3); app.newSystem();return false;" value="3"/>
24         <input type="button" style="width: 30px" name="" onclick="app.setNy(4); app.newSystem();return false;" value="4"/>
25         <input type="button" style="width: 30px" name="" onclick="app.setNy(5); app.newSystem();return false;" value="5"/>
26         <input type="button" style="width: 30px" name="" onclick="app.setNy(7); app.newSystem();return false;" value="7"/>
27         &nbsp;  <!--знак пробела-->
28         <input type="button" style="width: 30px" name="" onclick="app.setNy(9); app.newSystem();return false;" value="9"/>
29         <input type="button" style="width: 30px" name="" onclick="app.setNy(12); app.newSystem();return false;" value="12"/>
30         <input type="button" style="width: 30px" name="" onclick="app.setNy(15); app.newSystem();return false;" value="15"/>
31     </div><br>
32 
33     <div>Configuration:
34         <input type="button" name="" onclick="app.setTriangularLattice(); return false;" value="Triangular lattice"/>
35         <input type="button" name="" onclick="app.setRandom(); return false;" value="Systemless"/>
36         <input type="button" name="" onclick="app.setEmpty(); return false;" value="A blank field "/>
37     </div><br>
38 
39     <div>
40         <input type="checkbox" id="checkbox_01" name="" onchange="app.setCheckbox_01(this.checked);"/>
41         <a href="/SLJ" title="SLJ" class="mw-redirect">Short Lennard-Jones</a> potential
42     </div><br>
43 
44     <div>
45         <input type="checkbox" id="checkbox_02" name="" onchange="app.setCheckbox_02(this.checked);"/>
46         Thermostat:  
47         <input type="range" id="slider_02" style="width: 150px;" oninput="app.setSlider_02(this.value); document.getElementById('text_02').value = this.value;">
48         T ⋅ T0 = <input id="text_02" style="width: 5ex;" required pattern="[-+]?([0-9]*\.[0-9]+|[0-9]+)" oninput="
49             // if not the number is entered - the line won't pass validation on a pattern above, and checkValidity () will return false
50             if (!this.checkValidity()) return;
51             app.setSlider_02(this.value);
52             document.getElementById('slider_02').value = this.value;
53         ">
54         <br>
55         <input type="checkbox" checked id="checkbox_03" name="" onchange="app.setCheckbox_03(this.checked);"/>Acceleration is achieved by random rates 
56         <br>
57         The thermostat acts on: 
58         <input type="checkbox" checked id="checkbox_04" name="" onchange="app.setCheckbox_04(this.checked);"/>External friction
59         <input type="checkbox" checked id="checkbox_05" name="" onchange="app.setCheckbox_05(this.checked);"/>Internal friction
60         <div>T ≈ <span id="Temperature"></span></div>
61     </div><br>
62 
63     <div>Number of the particles: <span id="ballsNum"></span></div>
64 
65     <script type="text/javascript">var app = new MainBalls(
66             document.getElementById('canvasBalls'),
67             document.getElementById('slider_01'),
68             document.getElementById('text_01'),
69             document.getElementById('slider_02'),
70             document.getElementById('text_02')
71     );</script>
72 </body>
73 </html>

Here you can find the previous versions of the program.

The proposed directions of the stand development

  • Add the ability to set the temperature with the help of the Hoover‘s thermostat.
  • Explore the speed of the program in order to optimize it.
  • Apply the principle of copying.
  • Add different potentials.
  • Add the ability to set viscoelastic walls anywhere.
  • Add the ability of inclusion of the periodic boundary conditions.