Обновить
266
Андрей Дмитриев@AndreyDmitriev

Пользователь

0,8
Рейтинг
112
Подписчики
Отправить сообщение

С гитхабом тоже надо аккуратно, скажем тот же код LabVIEW (который графический) туда нечасто выкладывают просто потому, что гитхаб под него не слишком заточен и он просядет в рейтинге, опять же учёные не так часто гитхабом пользуются, так что долю того же MATLAB это тоже опустит немного. Тут что-то комбинированно-взвешенное надо.

Делал себе что-то похожее, когда учил французский, на LabVIEW правда. Но у меня были проблемы с восприятием на слух, так что я брал учебные тексты и диалоги с аудио рядом и делал воспроизведение по предложениям или словам с возможностью замедления воспроизведения (есть алгоритмы, позволяющие замедлить, но без изменения высоты тона). Правда с форматом не заморачивался, просто XML с маркерами времени, чтобы звук из wav брать и проигрывать. В принципе похожие программы есть, просто в каждой что-то да не устроило.

Я просто немного этой области "касаюсь" и вижу там всё больше тот же LabVIEW, вот ещё Фортран, и MATLAB/Octave/Maple, ну и Python в последнее время. Хотя возможно, моя выборка нерепрезентативна. Повод посмотреть на R повнимательнее, я никогда его не "трогал", честно говоря, хотя стараюсь быть в курсе и на всех из первой двадцатки что-то делал, кроме Ada и Swift, пожалуй.

Самое главное тут - LabVIEW с 32-го места переехал на 26-е, обогнав Julia и Kotlin, глядишь, так и в двадцатку войдёт! Вообще к этому рейтингу есть некоторые вопросы, я вот сильно не уверен, что R так уж популярен, но в общем топ 5 и так хорошо известен.

Попробовал на мультимониторной конфигурации - тоже работает, но почему-то окна при максимизации залетают под док, ровно тот же исполняемый файл но на одномониторном ноуте работает норм - при макисмизации они разлетаются только до границы и под док не залазят. Может быть проблема винды. Ну и уже понятно, что надо фильтровать окна - скажем у меня на левом мониторе обычно брааузер открыт и он ничем не перекрывается, и его превьюшка не нужна, ну а в режиме работы с LabVIEW хочется видеть только превьюшки от этой среды, а другие не нужны, но это легко можно допилить. В общем стандартные хотелки, которые можно самому доделать под себя.

Да, спасибо, стало лучше. Штука оказалась неожиданно удобная, сегодня заполнял налоговую - на экране дофига всего, и калькулятор, а в превьюшке виден последний результат, и эксель, и там увеличение при наведении мыши удобно. Дома у меня не очень большой экран, а на работе не так актуально, там у меня три монитора, из них два вертикальных, но тоже попробую.

18кб это если собирать из расчёта что в системе есть рантайм. 

Вы невнимательно коммент прочитали, не 18 кб, а "на 18 кб меньше". У автора исполняемый файл 2001К или 2.049.024 байт, а если просто заменить таргет с 11 на 10 в csproj и собрать вот ровно той же командой dotnet publish -c Release -r win-x64, то будет 1967К или 2.013.696 байт, то есть на 34 кб меньше. А 18 кб - это после того, как я скроллбар туда добавил. Кстати, его ещё и upx пожать можно, он не потеряет работспособности, а станет меньше мегабайта - 884224 байт всего.

А вот тут пишут, что в последних версиях и это может не работать https://learn.microsoft.com/en-in/answers/questions/2263244/extendeduihovertime-is-not-working-anymore, хотя там также и метод через gpedit.msc предлагается, но я с планшета коммент пишу, завтра на работе проверю, любопытно.

Ещё есть the Ghidra Book за авторством Chris Eagle и Kara Nance, недавно вышло второе издание. В конце там небольшое приложение для тех, кто знаком с IDA. Основное отличие, что Гидра как бы проектно ориентированная, ну то есть вначале надо создать проект, а потом импортировать туда бинарники для анализа (в четвёртой главе всё детально расписано), а в Иде просто открыли и погнали.

Я эту книгу прочитал в тот момент, когда уже вёл рукописные заметки несколько лет (в большей степени как инбокс для системы GTD Дэвида Аллена), и таким образом получил подтверждение, что всё делаю правильно. После прочтения чуть подкорректировал систему и стало совсем хорошо.

тут странно, я ваш же код скомпилял под 10 (свежеустановленную) и получилось даже на 18 КБ меньше, что-то там лишнего прицепилось.

В принципе там вроде можно свои плагины прикручивать, хотя я и не пробовал ни разу.

В принципе на .net 10 оно тоже работает, я сделал форк https://github.com/AndrDm/montablab, там понизил версию и явных проблем в общем не заметил.

Хотя стоп, чего я мучаюсь, форкнул, напустил на него копилот, он и добавил:

PR делать не буду, мне не всё там нравится, но если надо, то форк со скроллингом вот тут поживёт пока — https://github.com/AndrDm/montablab

А хорошая штука, спасибо! LabVIEW-программистам может понравиться — у нас код раскидан по блок-диаграммам, плюс для каждого инструмента есть панель, так что десяток окошек бывает, а то и больше. Надо будет в реальный проектах поработать, возможно приживётся. Из хотелок — добавить справа от превьюшек классический скролл бар, сейчас их можно скроллить колёсиком, но на ноуте это чуть менее удобно, и не видно, где мы по вертикали находимся. Можно сделать скрываемый, добавив опцию в список под Dock Left/Dock Right.

Зависит от темы, к примеру я сейчас проективную геометрию изучаю, так нет хороших пособий, я сам себе делаю, причём разными инструментами, к примеру теорему Дезарга в трёхмерке мне проще в OpenSCAD. Сам процесс создания увлекателен местами, ну и если чего-то не хватает, и можно вот так сделать - почему бы и нет?

Эх, а у меня именно что связи-линки между заметками не прижились совершенно. Я веду их в маркдауне, и даже пытался, но с практической точки зрения этот граф мне мало о чём говорит, разве что выглядит красиво. Гораздо важнее (для меня) хороший поиск. Даже если заметки будут лежать сплошным списком всё в кучу, то при наличии поиска всегда можно отыскать нужные. Обсидиан мне не очень нравится редактором, я купил и пользую Typora. Так что сложился примерно такой "воркфлоу" - заметки в маркдауне раскиданные по папкам (папки выбираются по принципу "первое, что придёт в голову"). У каждой есть простой yaml заголовок, в котором можно указать классические категории и теги. Категорий может быть несколько, например "язык программирования", и т.д. Ссылаться с одной заметки на другую тоже не запрещается, само собой. Заметки лежат в гитхабе, и там настроена сборка при помощи hugo, она автоматом запускается, как только я меняю или создаю заметку, так что они потом доступны из браузера. Иногда я напускаю на заметку ИИ, чтобы автоматом заполнить заголовок и причесать текст. Категории - это таксономии в hugo. Важно, что используется pagefind, который это дело индексирует, так что я могу просто искать по тексту (либо вывести списком по категории или тегу). Использование hugo даёт возможность использовать в заметках шорткоды, там картинки в галерею организовать, или pdf прямо в заметку вставить и т.д. Плюс всё это довольно увлекательно "программировать" в качестве активной прокрастинации, тему и стили править время от времени, добавлять dart sass, всякие новые шорткоды, вкорячивать местами JavaScript, и т.д. Вообще у меня три заметочника сейчас — электронный на hugo, затем в планшете reMarkable, в основном для быстрых рукописных, приватных и сканов документов, немножко книжек там до кучи, и совсем бумажный, тут я марки меняю, был Молескин, потом Leuchtturm1917, сейчас Standard Issue Notebook от DesignWorks, ибо ничего лучше хорошей записной книжки и авторучки типа Паркера, Монблана или Дюпона человечество не придумало. Бумажный, кстати, наиболее часто используется, точнее ежедневно, мне руками на бумаге писать приятнее, я веду такие заметки уже почти тридцать лет, практически без перерывов. Там у меня за годы выработалась нехитрая система с оглавлением и маркерами дат, так что с поиском особых проблем тоже нет. Как-то так.

Это так, просто знают не все, а в статье написано "тактах процессора", ну там такты разные, я для себя различаю "тики" и "такты", надо же как-то rdtsc и rdpmc различать.

Хороший эксперимент, спасибо за наводку и перевод. Ещё надо понимать, что rdtsc возвращает количество тактов на базовой частоте, реальное количество тактов может быть выше и зависит от того, насколько частота турбо бустится выше базовой (либо турбо буст надо отключать). Ещё можно попытаться "добить" его префетчингом по заведомо неправильным адресам, хотя может тут уже и так замедлено до предела. Надо будет попробовать поэкспериментировать на bare metal, я тут выяснил, что на том же Рсте относительно несложно сделать efi консольное приложение, которое вообще не требует ОС, грузится с флешки и даёт экслюзивный доступ ко всем возможностям, в том числе привелигированным, и ОС уже не мешает. В числе незакрытых гештальтов - попробовать использовать кэш первого уровня в качестве "дополнительных регистров" процессора, там если управляющие биты правильно выставить, то его можно адресовать как обычную память (практической пользы почти нет, просто увлекательное упражнение).

Безотносительно презентаций — вы этой статьёй натолкнули меня на мысль, что я могу вставлять JavaScript в Markdown, который использую для онлайн-заметок, собираемых с помощью Hugo. Утащил из презентации функцию сортировки. Кстати, тот же ИИ вполне может ещё и «причесать» код.

Сортировка
---
title: Visualizing Sorting Algorithms in Real Time
date: 2026-07-03
description: Explore how Bubble Sort, Selection Sort, and Insertion Sort work through an interactive live visualization with adjustable speed and operation statistics.
draft: false
---

Sorting algorithms are one of the fundamental building blocks of computer science. While their source code is often only a few lines long, understanding how elements move through a collection can be much easier when the process is visualized.

<!--more-->

This interactive demonstration allows you to watch several classic sorting algorithms operate on a randomized dataset in real time. Use the controls to choose an algorithm, adjust the animation speed, generate a new random sequence, and observe how the algorithm transforms an unsorted array into an ordered one.

During execution, the visualization highlights active comparisons and tracks the number of comparisons and swaps performed. These metrics help illustrate why different algorithms exhibit different performance characteristics and computational complexity.

Available algorithms:

- **Bubble Sort** — repeatedly compares adjacent elements and swaps them when they are out of order.
- **Selection Sort** — repeatedly finds the smallest remaining element and places it in its final position.
- **Insertion Sort** — incrementally builds a sorted section by inserting each new element into the correct location.

Try different speeds, randomize the dataset, and compare how each algorithm behaves as it processes the same input.

<div id="sorting-demo">

<style>
#sorting-demo {
    background: #0d1117;
    color: #e6edf3;
    padding-bottom: 20px;
    font-family: Poppins, system-ui, Segoe UI, sans-serif;
}
#sorting-demo .top {
    display: flex;
    gap: 14px;
    align-items: center;
    padding: 18px 26px;
    background: #161b22;
    flex-wrap: wrap;
}
#sorting-demo h2 {
    margin: 0 18px 0 0;
    font-size: 22px;
    color: #7ee787;
}
#sorting-demo select,
#sorting-demo button {
    font-size: 16px;
    padding: 10px 16px;
    border-radius: 10px;
    border: 1px solid #30363d;
    background: #21262d;
    color: #e6edf3;
    cursor: pointer;
}
#sorting-demo button.go {
    background: #238636;
    border-color: #238636;
    font-weight: 700;
}
#sorting-demo .stat {
    margin-left: auto;
    color: #9fb0c8;
}
#sorting-demo .stat b {
    color: #7ee787;
}
#sorting-demo .bars {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 400px;
    padding: 24px;
    box-sizing: border-box;
}
#sorting-demo .bar {
    flex: 1;
    background: linear-gradient(#3b82f6, #1d4ed8);
    border-radius: 4px 4px 0 0;
    transition: height 0.05s;
}
#sorting-demo .bar.cmp {
    background: linear-gradient(#fbbf24, #d97706);
}
#sorting-demo .bar.swp {
    background: linear-gradient(#f87171, #dc2626);
}
#sorting-demo .bar.done {
    background: linear-gradient(#34d399, #059669);
}
</style>

<!-- No Empty Lines in div section! -->
<div class="top">
    <h2>Sort Algorithms</h2>
    <select id="alg">
        <option value="bubble">Bubble Sort</option>
        <option value="selection">Selection Sort</option>
        <option value="insertion">Insertion Sort</option>
    </select>
    <label style="font-size:15px;color:#9fb0c8">
        Speed
        <input type="range" id="spd" min="1" max="100" value="55" style="vertical-align:middle">
    </label>
    <button id="shuf">🔀 Randomize</button>
    <button class="go" id="run">▶ Sort It!</button>
    <div class="stat">
        Comparisons: <b id="cmp">0</b> · Swaps: <b id="swp">0</b>
    </div>
</div>

<div class="bars" id="bars"></div>

<script>
console.log("sorting demo loaded");

var N = 40;
var arr = [];
var bars = document.getElementById("bars");
var els = [];
var cmp = 0;
var swp = 0;
var running = false;

function build() {
    bars.innerHTML = "";
    els = [];
    for (var i = 0; i < N; i++) {
        var d = document.createElement("div");
        d.className = "bar";
        bars.appendChild(d);
        els.push(d);
    }
}

function paint() {
    for (var i = 0; i < N; i++) {
        els[i].style.height = arr[i] + "%";
    }
}

function updateStats() {
    document.getElementById("cmp").textContent = cmp;
    document.getElementById("swp").textContent = swp;
}

function shuffle() {
    arr = [];
    for (var i = 0; i < N; i++) {
        arr.push(5 + Math.round(Math.random() * 95));
    }
    cmp = 0;
    swp = 0;
    updateStats();
    paint();
}

function delay() {
    var speed = document.getElementById("spd").value;
    return new Promise(function(resolve) {
        setTimeout(resolve, Math.max(2, 105 - speed));
    });
}

async function bubble() {
    for (var i = 0; i < N - 1; i++) {
        for (var j = 0; j < N - 1 - i; j++) {
            cmp++;
            updateStats();
            els[j].className = "bar cmp";
            els[j + 1].className = "bar cmp";
            await delay();
            if (arr[j] > arr[j + 1]) {
                var t = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = t;
                swp++;
                updateStats();
                paint();
            }
            els[j].className = "bar";
            els[j + 1].className = "bar";
        }
        els[N - 1 - i].className = "bar done";
    }
    els[0].className = "bar done";
}

async function selection() {
    for (var i = 0; i < N - 1; i++) {
        var min = i;
        els[i].className = "bar cmp";
        for (var j = i + 1; j < N; j++) {
            els[j].className = "bar cmp";
            cmp++;
            updateStats();
            await delay();
            if (arr[j] < arr[min]) min = j;
            if (j !== min) els[j].className = "bar";
        }
        if (min !== i) {
            var t = arr[i];
            arr[i] = arr[min];
            arr[min] = t;
            swp++;
            updateStats();
            paint();
        }
        els[i].className = "bar done";
    }
    els[N - 1].className = "bar done";
}

async function insertion() {
    els[0].className = "bar done";
    for (var i = 1; i < N; i++) {
        var key = arr[i];
        var j = i - 1;
        els[i].className = "bar swp";
        await delay();
        while (j >= 0 && arr[j] > key) {
            cmp++;
            updateStats();
            els[j].className = "bar cmp";
            arr[j + 1] = arr[j];
            paint();
            await delay();
            els[j].className = "bar";
            j--;
        }
        arr[j + 1] = key;
        swp++;
        updateStats();
        paint();
        for (var k = 0; k <= i; k++) els[k].className = "bar done";
    }
}

async function run() {
    if (running) return;

    running = true;
    document.getElementById("run").disabled = true;
    document.getElementById("shuf").disabled = true;

    try {
        var a = document.getElementById("alg").value;

        if (a === "bubble") {
            await bubble();
        } else if (a === "selection") {
            await selection();
        } else {
            await insertion();
        }

        for (var i = 0; i < N; i++) {
            els[i].className = "bar done";
        }
    } finally {
        running = false;
        document.getElementById("run").disabled = false;
        document.getElementById("shuf").disabled = false;
    }
}

document.getElementById("shuf").onclick = shuffle;
document.getElementById("run").onclick = run;

build();
shuffle();
</script>

</div>

И вот

Ну и не отказал себе в удовольствии навайбкодить десяток маятников, давно хотел это сделать, почти тем же промпотом что в инструкции

Маятники
---
title: "Interactive Pendulum Wave Simulation"
date: 2026-07-03
description: "Explore the fascinating behavior of multiple pendulums with different lengths oscillating from a common suspension point. Observe synchronization, phase shifts, and periodic realignment using an accurate nonlinear pendulum model and RK4 numerical integration."
draft: false
---

A pendulum wave is a mesmerizing physical phenomenon created by multiple pendulums of different lengths oscillating from the same support. Although all pendulums start in phase, their slightly different natural periods gradually cause them to drift apart, forming beautiful wave-like patterns. After a certain amount of time, the system partially or fully synchronizes again, creating repeating visual structures.

This interactive simulation lets you experiment with pendulum lengths, initial angle, gravity, and time scale while observing how differences in oscillation period influence phase relationships between multiple pendulums.

<!--more-->

## Brief Theory

A mathematical pendulum consists of a point mass suspended from a massless, inextensible string.

For small oscillation angles, the period of a pendulum is approximately:

**T = 2π√(L/g)**

where:

- **T** is the oscillation period,
- **L** is the pendulum length,
- **g** is the gravitational acceleration.

Because the oscillation period depends on the square root of the pendulum length, longer pendulums swing more slowly than shorter ones.

When multiple pendulums of different lengths start from the same initial angle, their motions gradually drift out of phase. This produces evolving geometric patterns known as a **pendulum wave**. At specific moments, the pendulums may align again, creating striking synchronization effects.

This simulation models ten pendulums suspended from a common point. Their lengths are evenly distributed between the selected minimum and maximum values, allowing you to explore how small changes in period lead to complex collective motion.

The motion of each pendulum is calculated using the nonlinear equation:

**θ'' + (g/L)sin(θ) = 0**

Unlike the small-angle approximation, this equation remains accurate for larger oscillation amplitudes.

Numerical integration is performed using the **fourth-order Runge–Kutta (RK4)** method, providing stable and accurate results throughout the simulation.

<div id="m-demo">

<style>
#m-demo *{
    box-sizing:border-box;
}

#m-demo .wrapper{
    display:flex;
    flex-wrap:wrap;
    gap:12px;
    width:100%;
    max-width:100%;
    align-items:flex-start;
}

#m-demo .canvas-wrap{
    position:relative;
    width:100%;
    max-width:400px;
    height:720px;
    background:#ffffff;
    border:1px solid #d1d5db;
    overflow:hidden;
    flex:0 0 400px;
}

#m-demo canvas{
    display:block;
    width:100%;
    height:100%;
}

#m-demo .panel{
    flex:1 1 320px;
    min-width:280px;
    max-width:100%;
    background:#ffffff;
    padding:12px;
    overflow:auto;
    border-left:1px solid #d1d5db;
}

#m-demo h1{
    margin:0 0 8px;
    font-size:1.5rem;
}

#m-demo h2{
    margin:0 0 8px;
    font-size:1.1rem;
}

#m-demo .card{
    background:#ffffff;
    padding:10px;
    border-radius:12px;
    margin-bottom:10px;
}

#m-demo label{
    display:block;
    margin:6px 0 2px;
    font-size:.9rem;
}

#m-demo input[type=range]{
    width:100%;
}

#m-demo .value{
    color:#0284c7;
    font-weight:bold;
}

#m-demo .controls{
    display:flex;
    gap:6px;
    flex-wrap:wrap;
    margin-top:10px;
}

#m-demo button{
    border:none;
    border-radius:8px;
    padding:8px 12px;
    cursor:pointer;
    font-weight:600;
    font-size:.9rem;
}

#m-demo .start{
    background:#16a34a;
    color:#fff;
}

#m-demo .pause{
    background:#ea580c;
    color:#fff;
}

#m-demo .reset{
    background:#2563eb;
    color:#fff;
}

#m-demo .stats{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:6px;
}

#m-demo .stat{
    background:#0f172a;
    color:#fff;
    padding:8px;
    border-radius:8px;
    font-size:.9rem;
}

#m-demo .small{
    font-size:.85rem;
    line-height:1.4;
}

@media (max-width:1000px){

    #m-demo .wrapper{
        flex-direction:column;
    }

    #m-demo .canvas-wrap{
        width:100%;
        max-width:400px;
        height:720px;
        margin:0 auto;
        flex:none;
    }

    #m-demo .panel{
        width:100%;
        min-width:auto;
        border-left:none;
    }
}
</style>

<!-- No Empty Lines in div section! -->
<div class="wrapper">
    <div class="canvas-wrap">
        <canvas id="canvas"></canvas>
    </div>
    <div class="panel">
        <h1>Mathematical Pendulums</h1>
        <div class="card">
			<label>Minimum Length: <span class="value" id="minLenVal">2.0</span> m</label>
			<input id="minLength" type="range" min="0.5" max="5" step="0.1" value="2">
			<label>Maximum Length: <span class="value" id="lenVal">5.0</span> m</label>
			<input id="length" type="range" min="0.5" max="5" step="0.1" value="5">
            <label>Initial Angle: <span class="value" id="angVal">20</span>°</label>
            <input id="angle" type="range" min="1" max="80" step="1" value="20">
            <label>Gravitational Acceleration: <span class="value" id="gVal">9.81</span> m/s²</label>
            <input id="gravity" type="range" min="1.6" max="24" step="0.01" value="9.81">
            <label>Time Scale: <span class="value" id="speedVal">1.0</span>x</label>
            <input id="speed" type="range" min="0.2" max="4" step="0.1" value="1">
            <div class="controls">
                <button class="start" id="startBtn">Start</button>
                <button class="pause" id="pauseBtn">Pause</button>
                <button class="reset" id="resetBtn">Reset</button>
            </div>
        </div>
        <div class="card">
            <h2>Current Values</h2>
            <div class="stats">
                <div class="stat">Angle<br><b id="curAngle">0°</b></div>
                <div class="stat">Lengths<br><b id="curLength">2.0–5.0 m</b></div>
                <div class="stat">Period<br><b id="period">0 s</b></div>
                <div class="stat">Frequency<br><b id="freq">0 Hz</b></div>
                <div class="stat">Time<br><b id="simTime">0 s</b></div>
                <div class="stat">g<br><b id="curG">9.81</b></div>
            </div>
        </div>
    </div>
</div>

<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

const lengthSlider = document.getElementById("length");
const minLengthSlider = document.getElementById("minLength");
const angleSlider = document.getElementById("angle");
const gravitySlider = document.getElementById("gravity");
const speedSlider = document.getElementById("speed");

const lenVal = document.getElementById("lenVal");
const angVal = document.getElementById("angVal");
const gVal = document.getElementById("gVal");
const speedVal = document.getElementById("speedVal");

let running = false;
let time = 0;

const COUNT = 10;

const COLORS = [
    "#ef4444",
    "#f97316",
    "#f59e0b",
    "#84cc16",
    "#22c55e",
    "#10b981",
    "#06b6d4",
    "#3b82f6",
    "#8b5cf6",
    "#ec4899"
];

let pendulums = [];

function hexToRgba(hex, alpha){

    const r = parseInt(hex.slice(1,3),16);
    const g = parseInt(hex.slice(3,5),16);
    const b = parseInt(hex.slice(5,7),16);

    return `rgba(${r},${g},${b},${alpha})`;
}

function buildPendulums(){

    pendulums = [];

	const minL = Math.min(
    Number(minLengthSlider.value),
    Number(lengthSlider.value) - 0.1
);

const maxL = Number(lengthSlider.value);

    for(let i = 0; i < COUNT; i++){

        const length =
            minL +
            (maxL - minL) * i / (COUNT - 1);

        pendulums.push({
            L: length,
            theta: Number(angleSlider.value) * Math.PI / 180,
            omega: 0,
            color: COLORS[i]
        });
    }
}

function resize(){
    canvas.width = canvas.clientWidth;
    canvas.height = canvas.clientHeight;
}

window.addEventListener("resize", resize);
resize();

function updateLabels(){
    lenVal.textContent = lengthSlider.value;
    angVal.textContent = angleSlider.value;
    gVal.textContent = gravitySlider.value;
    speedVal.textContent = Number(speedSlider.value).toFixed(1);

document.getElementById("curLength").textContent =
Number(minLengthSlider.value).toFixed(1) +
"–" +
Number(lengthSlider.value).toFixed(1) +
" m";

    document.getElementById("curG").textContent = gravitySlider.value;

    const L = Number(lengthSlider.value);
    const g = Number(gravitySlider.value);
    const T = 2 * Math.PI * Math.sqrt(L / g);

    document.getElementById("period").textContent = T.toFixed(2) + " s";
    document.getElementById("freq").textContent = (1 / T).toFixed(2) + " Hz";
}

updateLabels();

[
	minLengthSlider,
    lengthSlider,
    angleSlider,
    gravitySlider,
    speedSlider
].forEach(el => {
    el.addEventListener("input", () => {
        updateLabels();
        buildPendulums();
    });
});

function resetSimulation(){
    time = 0;
    buildPendulums();
}

resetSimulation();

document.getElementById("startBtn").onclick = () => running = true;
document.getElementById("pauseBtn").onclick = () => running = false;

document.getElementById("resetBtn").onclick = () => {
    running = false;
    resetSimulation();
};

function derivatives(theta, omega, length){

    const g = Number(gravitySlider.value);

    return {
        dtheta: omega,
        domega: -(g / length) * Math.sin(theta)
    };
}

function rk4(p, dt){

    const k1 = derivatives(
        p.theta,
        p.omega,
        p.L
    );

    const k2 = derivatives(
        p.theta + k1.dtheta * dt / 2,
        p.omega + k1.domega * dt / 2,
        p.L
    );

    const k3 = derivatives(
        p.theta + k2.dtheta * dt / 2,
        p.omega + k2.domega * dt / 2,
        p.L
    );

    const k4 = derivatives(
        p.theta + k3.dtheta * dt,
        p.omega + k3.domega * dt,
        p.L
    );

    p.theta += dt *
        (k1.dtheta + 2*k2.dtheta + 2*k3.dtheta + k4.dtheta) / 6;

    p.omega += dt *
        (k1.domega + 2*k2.domega + 2*k3.domega + k4.domega) / 6;
}

function drawGrid(){
    ctx.strokeStyle = "#d1d5db";
    ctx.lineWidth = 1;

    for(let x = 0; x < canvas.width; x += 40){
        ctx.beginPath();
        ctx.moveTo(x,0);
        ctx.lineTo(x,canvas.height);
        ctx.stroke();
    }

    for(let y = 0; y < canvas.height; y += 40){
        ctx.beginPath();
        ctx.moveTo(0,y);
        ctx.lineTo(canvas.width,y);
        ctx.stroke();
    }
}

let last = performance.now();

function frame(now){

    const realDt = (now - last) / 1000;
    last = now;

    const dt = realDt * Number(speedSlider.value);

    if(running){
        rk4(dt);
        time += dt;
    }

    ctx.clearRect(0,0,canvas.width,canvas.height);
    drawGrid();

	const originX = canvas.width / 2;
	const originY = 40;

	for(let i = 0; i < pendulums.length; i++){

    	const p = pendulums[i];

   		if(running){
        	rk4(p, dt);
    	}

    	const visualLength = p.L * 95;

    	const bobX =
        	originX +
        	visualLength * Math.sin(p.theta);

    	const bobY =
        	originY +
        	visualLength * Math.cos(p.theta);

    	ctx.beginPath();
    	ctx.arc(
        	originX,
        	originY,
        	3,
        	0,
        	Math.PI * 2
    	);
    	ctx.fillStyle = "#64748b";
    	ctx.fill();

    	ctx.beginPath();
    	ctx.moveTo(originX, originY);
    	ctx.lineTo(bobX, bobY);
    	ctx.strokeStyle = hexToRgba(p.color, 0.45);
    	ctx.lineWidth = 1.5;
    	ctx.stroke();

    	ctx.beginPath();
    	ctx.arc(
        	bobX,
        	bobY,
        	8,
        	0,
        	Math.PI * 2
    	);
    	ctx.fillStyle = p.color;
    	ctx.fill();
	}
	document.getElementById("curAngle").textContent =
	COUNT + " pendulums";

    document.getElementById("simTime").textContent =
        time.toFixed(2) + " s";

	requestAnimationFrame(frame);
}

requestAnimationFrame(frame);
</script>

</div>

Я Рунге-Кутта на численных методах программировал на Фортране лет тридцать пять назад и убил неделю, если не больше, а теперь это в обеденный перерыв делается. В принципе на малых углах он не нужен, но копилот его использовал, хуже не будет, можно сопротивление воздуха добавить. Круто, спасибо.

1
23 ...

Информация

В рейтинге
2 407-й
Откуда
Ahrensburg, Schleswig-Holstein, Германия
Дата рождения
Зарегистрирован
Активность