Двумерное уравнение теплопроводности + волновое уравнение
Численное решение двумерного уравнения теплопроводности и двумерного волнового уравнения.
Управление:
- Левая кнопка мыши - нагреть область
- Средняя кнопка мыши - придать области нормальную температуру
- Правая кнопка мыши - охладить область
Скачать 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" /> Уравнение теплопроводности Граничные условия: |
|
Начальные условия: for (var i = 0; i < nx; i++) { |
||
<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> © 2014 |
</body> </html> </source> </toggledisplay>