Двумерное уравнение теплопроводности + волновое уравнение — различия между версиями

Материал из Department of Theoretical and Applied Mechanics
Перейти к: навигация, поиск
м (Замена текста — «<source lang="(.*)" first-line="(.*)">» на «<syntaxhighlight lang="$1" line start="$2" enclose="div">»)
Строка 17: Строка 17:
 
Текст программы на языке JavaScript (разработчики [[Цветков Денис]], [[Кривцов Антон]]): <toggledisplay status=hide showtext="Показать↓" hidetext="Скрыть↑" linkstyle="font-size:default">  
 
Текст программы на языке JavaScript (разработчики [[Цветков Денис]], [[Кривцов Антон]]): <toggledisplay status=hide showtext="Показать↓" hidetext="Скрыть↑" linkstyle="font-size:default">  
 
Файл '''"Wave_Heat_2D.js"'''
 
Файл '''"Wave_Heat_2D.js"'''
<source lang="javascript" first-line="1">
+
<syntaxhighlight lang="javascript" line start="1" enclose="div">
 
window.addEventListener("load", main_equations, false);
 
window.addEventListener("load", main_equations, false);
 
function main_equations() {
 
function main_equations() {
Строка 463: Строка 463:
 
</source>
 
</source>
 
Файл '''"Wave_Heat_2D.html"'''
 
Файл '''"Wave_Heat_2D.html"'''
<source lang="html" first-line="1">
+
<syntaxhighlight lang="html" line start="1" enclose="div">
 
<!DOCTYPE html>
 
<!DOCTYPE html>
 
<html>
 
<html>

Версия 18:52, 8 марта 2015

Виртуальная лаборатория > Двумерное уравнение теплопроводности + волновое уравнение

Численное решение двумерного уравнения теплопроводности и двумерного волнового уравнения.


Управление:

  • Левая кнопка мыши - нагреть область
  • Средняя кнопка мыши - придать области нормальную температуру
  • Правая кнопка мыши - охладить область


Скачать Wave_Heat_2D_v2-4_release.zip.

Текст программы на языке JavaScript (разработчики Цветков Денис, Кривцов Антон): <toggledisplay status=hide showtext="Показать↓" hidetext="Скрыть↑" linkstyle="font-size:default"> Файл "Wave_Heat_2D.js" <syntaxhighlight lang="javascript" line start="1" enclose="div"> window.addEventListener("load", main_equations, false); function main_equations() {

   // Предварительные установки
   var canv = init_canvas(Wave_Heat_2D_canvas);
   var a0 = 1;                             // масштаб расстояния
   var t0 = 1;                             // масштаб времени
   var m0 = 1;                             // масштаб массы
   var T0 = 1;                             // масштаб температуры
   var dx = 1 * a0;                        // шаг сетки по оси x
   var dy = 1 * a0;                        // шаг сетки по оси y
   var spf = 2;                            // steps per frame - сколько расчетов проходит за каждый кадр отображения
   var fps = 15;                           // frames per second - количество кадров в секунду
   var dt = 0.005 * t0;                    // шаг интегрирования по времени
   var t = 0;
   var p0 = m0 / (a0 * a0 * a0);           // единица плотности, кг/м3
   var c0 = a0 * a0 / (t0 * t0 * T0);      // единица удельной теплоемкости, Дж/(кг * К)    (Дж = кг * м2 / с2)
   var kap0 = m0 * a0 / (t0 * t0 * t0 * T0);// единица теплопроводности (каппа)
   var p = 0.15 * p0;
   var c = 0.2 * c0;
   var k = 1 * kap0;
   var X = k / (c * p);                    // температуропроводность (хи)
   var Nx = 50 + 2;                        // количество узлов по оси x + 2 для ГУ
   var Ny = 50 + 2;                        // количество узлов по оси y + 2 для ГУ
   var cell_w = canv.w / (Nx - 2);         // ширина клетки
   var cell_h = canv.h / (Ny - 2);         // высота клетки
   var T_max = 10 * T0;
   var T_min = - T_max;
   var k0 = 2 * Math.PI / t0;              // масштаб частоты
   var C0 = m0 * k0 * k0;                  // масштаб жесткости
   var m = 1 * m0;                 	    // масса
   var C = 1 * C0;                 	    // жесткость
   var omega = Math.sqrt(C / m);
   var cc = 2.3 * a0 * omega;
   var pause = false;
   button_pause.onclick = function () {pause = !pause;};
   button_clear.onclick = clear_button_func;
   function clear_button_func() {
       clear_all();
       draw();
   }
   function clear_all() {
       for (var i = 1; i < Nx - 1; i++)
       for (var j = 1; j < Ny - 1; j++) {
               T[i][j].u = 0;
               T[i][j].v = 0;
               T[i][j].BC = false;
       }
       BC2 = [];
   }
   var koeff1;
   // начальные условия - распределение Гаусса
   var T = [];
   for (var i = 0; i < Nx; i++) {
       T[i] = [];
       for (var j = 0; j < Ny; j++) {
           T[i][j] = {};
           var x = i / (Nx - 1);
           var y = j / (Ny - 1);
           var width = 0.05;
           var power = 15;
           var minus = 0;
           T[i][j].u = Math.exp(-Math.pow(x - 0.5, 2) / width) * Math.exp(-Math.pow(y - 0.5, 2) / width) * power - minus;
           T[i][j].v = 0;
           T[i][j].BC = false;
       }
   }
   function set_calc_func() {
       if (radio_heat.checked) {
           heat_equation = true;
           slider_damping_power.disabled = true;
           damping_span.style.color="#888888";
           koeff1 = X / (dx * dy);
       } else {
           heat_equation = false;
           slider_damping_power.disabled = false;
           damping_span.style.color="#000000";
           koeff1 = cc * cc / (dx * dy);
       }
   }
   var heat_equation;
   set_calc_func();
   radio_heat.onchange = set_calc_func;
   radio_wave.onchange = set_calc_func;
   function set_BC_func() {
       if (radio_BC_mirror.checked) BC_mirror();
       else if (radio_BC_periodic.checked) BC_periodic();
   }
   radio_BC_mirror.onclick = set_BC_func;
   radio_BC_periodic.onclick = set_BC_func;
   // функции и переменные, объявленные через this, можно будет вызывать по сокращенному названию (например, sin(x), PI)
   var math_methods = Object.getOwnPropertyNames(Math);
   for (var i in math_methods)
       this[math_methods[i]] = Math[math_methods[i]];
   var damping_power = 0;
   slider_damping_power.min = 0;
   slider_damping_power.max = 5;
   slider_damping_power.step = 5 / 100;
   slider_damping_power.value = damping_power;
   slider_damping_power.oninput = function() {damping_power = slider_damping_power.value};
   apply_IC_button.onclick = apply_IC;
   function apply_IC() {
       var script = document.createElement('script');
       script.innerHTML = "function IC_function(nx, ny, M, M_max, M_min, t, add_BC2) {" +
           "for (var i = 0; i < nx; i++) {\r\n" +
           "    for (var j = 0; j < ny; j++) {\r\n" +
           IC_text.value + "}}};";
       document.getElementsByTagName('head')[0].appendChild(script);
       clear_all();
       IC_function(nx, ny, M, T_max, T_min, t, add_BC2);
       draw();
   }
   clear_IC_window_button.onclick =  clear_IC_window;
   function clear_IC_window() {
       IC_text.value = "";

// IC_text.value = // "for (var i = 0; i < nx; i++) {\r\n" + // " for (var j = 0; j < ny; j++) {\r\n" + // " \r\n" + // " }\r\n" + // "}";

   }
   var functions_for_buttons = [
       "M[i][j].u = M_max * (sin(i / (nx - 1) * 2 * PI));\r\n"
      ,"M[i][j].v = M_max * (sin(i / (nx - 1) * 2 * PI));\r\n"
      ,"M[i][j].u = M_max * pow(sin(i / (nx - 1) * 2 * PI), 25);\r\n"
      ,"M[i][j].u = M_max * (sin((i + j) / (2*(nx - 1)) * 2 * PI));\r\n"
      ,"var lx = 0.5 - i / (nx - 1);\r\n" +
       "var ly = 0.5 - j / (ny - 1);\r\n" +
       "M[i][j].u = sin(sqrt(lx * lx + ly * ly) * 12 * PI) * 6;\r\n"
      ,"var lx = 0.5 - i / (nx - 1);\r\n" +
       "var ly = 0.5 - j / (ny - 1);\r\n" +
       "M[i][j].u = 1 / max(sqrt(lx * lx + ly * ly), 0.01);    // max - чтобы избежать деления на 0\r\n"
      ,"M[i][j].u = M_max * sin(i / (nx - 1) * 4 * PI) * sin(j / (ny - 1) * 4 * PI);\r\n"
      ,"M[i][j].u = M_max * sin(i / (nx - 1) * nx * PI) * sin(j / (ny - 1) * ny * PI);\r\n"
      ,"M[i][j].u = M_max * (min(sin(i / (nx - 1) * 9 * PI), sin(j / (ny - 1) * 9 * PI)));\r\n"
      ,"M[i][j].u = M_max * (2 * random() - 1);\r\n"
      ,"M[i][j].u = M_max * pow(2 * random() - 1, 201);\r\n"
      ,"M[i][j].u = - M_max * (sin(i / (nx - 1) * PI) * random());\r\n"
      ,"M[i][j].u = 2 * M_max * (i / (nx - 1)) - M_max;\r\n"
      ,"M[i][j].u = i / (nx - 1) * j / (ny - 1) * M_max;\r\n"
      ,"if (i == 0) {\r\n" +
       "    M[i][j].u = M_max;\r\n" +
       "    M[i][j].BC = true;\r\n" +
       "} else if (i == nx - 1) {\r\n" +
       "    M[i][j].u = M_min;\r\n" +
       "    M[i][j].BC = true;\r\n" +
       "}\r\n"
      ,"if (i == 0) {\r\n" +
       "    if ((j >= 35 && j <= ny)) M[i][j].u = M_max;\r\n" +
       "    else M[i][j].u = M_min;\r\n" +
       "    M[i][j].BC = true;\r\n" +
       "} else if (i == nx - 1) {\r\n" +
       "    if (Math.round(j - j % 10) % 8 == 0) M[i][j].u = M_min;\r\n" +
       "    else M[i][j].u = M_max;\r\n" +
       "M[i][j].BC = true;\r\n" +
       "}\r\n"
      ,"if (j >= 20 && j <= 29) {\r\n" +
       "    if (i == nx / 2 - 1) {\r\n" +
       "        M[i][j].u = M_max;\r\n" +
       "        M[i][j].BC = true;\r\n" +
       "    } else if (i == nx / 2) {\r\n" +
       "        M[i][j].u = M_min;\r\n" +
       "        M[i][j].BC = true;\r\n" +
       "    }\r\n" +
       "}\r\n"
      ,"if (j >= 24 && j <= 25 && (i == nx / 2 - 1 || i == nx / 2))\r\n" +
       "    add_BC2(function(t){return sin(t * 10) * 10;}, i, j);\r\n"

// ," if (((i == 20 || i == 30) && j >= 40) || ((j == ny - 1 || j == 40) && i > 20 && i < 30)) {\r\n" + // " M[i][j].u = 0;\r\n" + // " M[i][j].BC = true;\r\n" + // " }\r\n" + // " //M[25][40].BC = false;\r\n" + // " M[25][40].u = M_min;\r\n" + // " if (i > 20 && i < 30 && j > 40 && j < 49) {\r\n" + // " M[i][j].u = M_min;\r\n" + // " M[i][j].BC = true;\r\n" + // " }\r\n"

   ];
   function generate_function_buttons() {
       for (var i = 0; i < functions_for_buttons.length; i++) {
           var btn = document.createElement('input');
           btn.type = "button";
           btn.value = i + 1;
           btn.onclick = function(i) {
               return function() {
               IC_text.value =

// "for (var i = 0; i < nx; i++) {\r\n" + // " for (var j = 0; j < ny; j++) {\r\n" +

                       functions_for_buttons[i];// +

// " }\r\n" + // "}";

                   apply_IC();
               }
           }(i);
           generated_buttons_span.appendChild(btn);
       }
   }
   generate_function_buttons();
   IC_text.value = functions_for_buttons[0];


   // граничные условия
   function BC_periodic() {
       for (i = 1; i < Nx - 1; i++) {
           T[i][0] = T[i][Ny - 2];
           T[i][Ny - 1] = T[i][1];
       }
       for (j = 0; j < Ny; j++) {
           T[0][j] = T[Nx - 2][j];
           T[Nx - 1][j] = T[1][j];
       }
   }
   function BC_mirror() {
       for (i = 1; i < Nx - 1; i++) {
           T[i][0] = T[i][1];
           T[i][Ny - 1] = T[i][Ny - 2];
       }
       for (j = 0; j < Ny; j++) {
           T[0][j] = T[1][j];
           T[Nx - 1][j] = T[Nx - 2][j];
       }
   }
   BC_mirror();
   // дублирующий массив без ГУ и переменные, для внешнего интерфейса
   var M = [];
   for (var i = 0; i < Nx - 2; i++) {
       M[i] = [];
       for (var j = 0; j < Ny - 2; j++) {
           M[i][j] = T[i + 1][j + 1];
       }
   }
   var nx = Nx - 2;
   var ny = Ny - 2;
   // массив для граничных условий Коши 2-го рода
   var BC2 = [];
   function add_BC2(func, i, j) {
       BC2.push({F:func, i:i, j:j});
   }
   var color_N = 120;                           // цветов не больше, чем color_N, саму переменную color_N в расчетах лучше не использовать
   var colors = prepare_colors(color_N);
   var cell_pics = prepare_cell_pics(colors);
   function control() {
       if (!pause) {
           calculate_steps(spf, dt);
           draw();
       }
   }
   setInterval(control, 1000 / fps);                       // Запуск системы
   // Работа с мышью
   Wave_Heat_2D_canvas.onmousedown = function(e){           // функция при нажатии клавиши мыши
       var T1;
       if (e.which == 1) T1 = T_max * 0.3;        // при нажатии левой клавиши мыши клетка нагревается
       else if (e.which == 2) T1 = 0;        // при нажатии средней клавиши мыши клетка нагревается
       else if (e.which == 3) T1 = T_min * 0.3;              // при нажатии правой клавиши мыши клетка остывает
       else return false;
       var area = (e.which != 2);
       set_cell(e, T1, area);
       Wave_Heat_2D_canvas.onmousemove = function(e) {set_cell(e, T1, area);};   // функция, выполняющаяся при перемещении курсора мыши
       return false;
   };
   document.onmouseup = function(e){           // функция при отпускании клавиши мыши
       Wave_Heat_2D_canvas.onmousemove = null;              // когда клавиша отпущена - функции перемещения нету
   };
   function set_cell(e, T1, area){       // придать клетке определенное состояние с нажатия клавиши мыши
       var m = mouse_coords(e);                // обновить координаты в переменных mouseX, mouseY
       if (m.x < 0 || m.x >= canv.w || m.y < 0 || m.y >= canv.h) return;         // проверка на ошибочные координаты
       var i = Math.floor(m.x / cell_w) + 1;       // получаем ячейку по горизонтали
       var j = Math.floor(m.y / cell_h) + 1;       // получаем ячейку по вертикали
       // везде прибавляем 1 - из за периодических условий массив сдвинут на 1
       if (!area) {
           if (!T[i][j].BC) {
               T[i][j].u = T1;
               draw_cell(i, j);
           }
       }
       else for (var shift_i = -3; shift_i <= 3; shift_i++) {
           var ii;
           if (i + shift_i < 1) {
               if (!radio_BC_periodic.checked) continue;
               ii = i + shift_i + Nx - 2;
           }
           else if (i + shift_i > Nx - 2) {
               if (!radio_BC_periodic.checked) continue;
               ii = i + shift_i - Nx + 2;
           }
           else ii = shift_i + i;
           for (var shift_j = -3; shift_j <= 3; shift_j++) {
               var jj;
               if (j + shift_j < 1) {
                   if (!radio_BC_periodic.checked) continue;
                   jj = j + shift_j + Ny - 2;
               }
               else if (j + shift_j > Ny - 2) {
                   if (!radio_BC_periodic.checked) continue;
                   jj = j + shift_j - Ny + 2;
               }
               else jj = shift_j + j;
               if (T[ii][jj].BC) continue;
               var r = Math.abs(shift_i) + Math.abs(shift_j);
               var T_new;
               if (r == 0) T_new = T1;
               else if (r == 1) T_new = T1 / 20 * 15;
               else if (r == 2) T_new = T1 / 20 * 6;
               else if (r == 3) T_new = T1 / 20;
               else continue;
               
               T[ii][jj].u += T_new;
               if (T[ii][jj].u > T_max) T[ii][jj].u = T_max;
               if (T[ii][jj].u < T_min) T[ii][jj].u = T_min;
               draw_cell(ii, jj);
           }
       }
   }
   function mouse_coords(e) {                  // функция возвращает экранные координаты курсора мыши
       var m = [];
       var rect = Wave_Heat_2D_canvas.getBoundingClientRect();
       m.x = e.clientX - rect.left;
       m.y = e.clientY - rect.top;
       return m;
   }
   function calculate_steps(spf, dt) {
       for (var s = 0; s < spf; s++) {
           for (var i = 1; i < Nx - 1; i++) {
               for (var j = 1; j < Ny - 1; j++) {
                   if (T[i][j].BC) continue;
                   if (heat_equation) T[i][j].v = (T[i + 1][j].u + T[i][j + 1].u - 4 * T[i][j].u + T[i - 1][j].u + T[i][j - 1].u) * koeff1;
                   else T[i][j].v += ((T[i + 1][j].u + T[i][j + 1].u - 4 * T[i][j].u + T[i - 1][j].u + T[i][j - 1].u) * koeff1 - T[i][j].v * damping_power) * dt;
               }
           }
           for (var i = 1; i < Nx - 1; i++) {
               for (var j = 1; j < Ny - 1; j++) {
                   T[i][j].u += T[i][j].v * dt;
               }
           }
           for (var i = 0; i < BC2.length; i++) {
               var f = BC2[i];
               M[f.i][f.j].u = f.F(t);
           }
           t += dt;
       }
   }
   function draw() {
       canv.ctx.clearRect(0, 0, canv.w, canv.h);               // очистить экран
       for (var i = 1; i <= Nx - 1; i++) {
           for (var j = 1; j <= Ny - 1; j++) {
               var col = Math.round((T[i][j].u - T_min) * (colors.length - 1) / (T_max - T_min));            // 0 <= col <= color_N
               if (col < 0) col = 0;
               if (col >  (colors.length - 1)) col =  (colors.length - 1);
               canv.ctx.drawImage(cell_pics[col], (i - 1) * cell_w, (j - 1) * cell_h);
           }
       }
   }
   function draw_cell(i, j) {
       var col = Math.round((T[i][j].u - T_min) * (colors.length - 1) / (T_max - T_min));            // 0 < col < color_N
       canv.ctx.drawImage(cell_pics[col], (i - 1) * cell_w, (j - 1) * cell_h);
   }
   function RGBtoHEX(r, g, b) {
       return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
   }
   function prepare_colors(N) {
       var col = [];
       var n = N / 6 - 1;
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(255 - i, 255, 255));
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(0, 255 - i, 255));
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(0, 0, 255 - i));
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(i, 0, 0));
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(255, i, 0));
       for (var i = 0; i < 256; i += Math.floor(255 / n))
           col.push(RGBtoHEX(255, 255, i));
       return col;
   }
   function prepare_cell_pics(col) {
       var pics = [];
       for (var i = 0; i < col.length; i++) {
           var cell = document.createElement('canvas');
           var cell_ctx = cell.getContext('2d');
           cell_ctx.fillStyle = col[i];
           cell_ctx.beginPath();
           cell_ctx.rect(0, 0, cell_w, cell_h);
           cell_ctx.fill();
           pics.push(cell);
       }
       return pics;
   }
   function init_canvas(canvas) {
       canvas.onselectstart = function () {return false;};     // запрет выделения canvas
       canvas.oncontextmenu = function () {return false;};      // блокировка контекстного меню
       var canv_obj = {};
       canv_obj.ctx = canvas.getContext("2d");                  // на context происходит рисование
       canv_obj.w = canvas.width;          // ширина окна в расчетных координатах
       canv_obj.h = canvas.height;         // высота окна в расчетных координатах
       return canv_obj;
   }

} </source> Файл "Wave_Heat_2D.html" <syntaxhighlight lang="html" line start="1" enclose="div"> <!DOCTYPE html> <html> <head>

   <meta charset="UTF-8" />
   <title>Wave 2D</title>
   <script src="Wave_Heat_2D.js"></script>

</head> <body>

<canvas id="Wave_Heat_2D_canvas" width="650" height="650" style="border:1px solid #000000; border-radius:6px; margin-bottom: -5px"></canvas>
Опции

               <input type="radio" id="radio_heat" name="eq_radio" /> Уравнение теплопроводности
<input type="radio" id="radio_wave" name="eq_radio" checked/> Волновое уравнение

Сила затухания:
<input type="range" id="slider_damping_power" style="width: 150px;">

<input type="button" id="button_pause" value="Пауза/Старт"/>
<input type="button" id="button_clear" value="Очистить"/>


               Граничные условия:
<input type="radio" id="radio_BC_mirror" name="BC_radio" checked/>Зеркальные
<input type="radio" id="radio_BC_periodic" name="BC_radio" />Периодические
               Начальные условия:
               for (var i = 0; i < nx; i++) {
    for (var j = 0; j < ny; j++) { <textarea id="IC_text" cols="40" rows="5" wrap=off style="width:95%; height:5.3em; resize: vertical;"></textarea>
    }
}
<input type="button" id="apply_IC_button" value="Применить" style="margin-top: 8px"/> <input type="button" id="clear_IC_window_button" value="Очистить поле для ввода"/>

Примеры:
           <a href="http://tm.spbstu.ru/%D0%A6%D0%B2%D0%B5%D1%82%D0%BA%D0%BE%D0%B2_%D0%94%D0%B5%D0%BD%D0%B8%D1%81_%D0%92%D0%B0%D0%BB%D0%B5%D1%80%D1%8C%D0%B5%D0%B2%D0%B8%D1%87">Denis Tcvetkov</a>
           &
           <a href="http://tm.spbstu.ru/%D0%90.%D0%9C._%D0%9A%D1%80%D0%B8%D0%B2%D1%86%D0%BE%D0%B2">Anton Krivtsov</a>
           &copy 2014

</body> </html> </source> </toggledisplay>