633 lines
21 KiB
Plaintext
633 lines
21 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Сети Хопфилда и Хэмминга</title>
|
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%231a1a2e'/%3E%3Cline x1='6' y1='10' x2='16' y2='6' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='6' y1='10' x2='16' y2='16' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='6' y1='10' x2='16' y2='26' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='6' y1='22' x2='16' y2='6' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='6' y1='22' x2='16' y2='16' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='6' y1='22' x2='16' y2='26' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='16' y1='6' x2='26' y2='16' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='16' y1='16' x2='26' y2='16' stroke='%238ab4f8' stroke-width='1'/%3E%3Cline x1='16' y1='26' x2='26' y2='16' stroke='%238ab4f8' stroke-width='1'/%3E%3Ccircle cx='6' cy='10' r='3' fill='%23a0c4ff'/%3E%3Ccircle cx='6' cy='22' r='3' fill='%23a0c4ff'/%3E%3Ccircle cx='16' cy='6' r='3' fill='%23a0c4ff'/%3E%3Ccircle cx='16' cy='16' r='3' fill='%23a0c4ff'/%3E%3Ccircle cx='16' cy='26' r='3' fill='%23a0c4ff'/%3E%3Ccircle cx='26' cy='16' r='3' fill='%23a0c4ff'/%3E%3C/svg%3E">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
|
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: monospace;
|
|
background: #1a1a2e;
|
|
color: #e0e0e0;
|
|
padding: 2rem;
|
|
}
|
|
|
|
h1 { font-size: 1.4rem; margin-bottom: 2rem; color: #a0c4ff; }
|
|
h2 { font-size: 1rem; margin-bottom: 0.75rem; color: #8ab4f8; }
|
|
|
|
.section { margin-bottom: 2.5rem; }
|
|
|
|
.patterns-row {
|
|
display: flex;
|
|
gap: 2rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.pattern-box { text-align: center; }
|
|
.pattern-label { margin-bottom: 0.4rem; font-size: 0.85rem; color: #aaa; }
|
|
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(8, 28px);
|
|
grid-template-rows: repeat(8, 28px);
|
|
gap: 2px;
|
|
}
|
|
|
|
.cell {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 3px;
|
|
background: #2a2a4a;
|
|
transition: background 0.1s;
|
|
}
|
|
|
|
.cell.on { background: #a0c4ff; }
|
|
.cell.off { background: #2a2a4a; }
|
|
|
|
.cell.interactive { cursor: pointer; }
|
|
.cell.interactive:hover { opacity: 0.75; }
|
|
|
|
.controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
margin-bottom: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
button {
|
|
padding: 0.5rem 1.5rem;
|
|
background: #a0c4ff;
|
|
color: #1a1a2e;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-family: monospace;
|
|
font-size: 0.9rem;
|
|
font-weight: bold;
|
|
}
|
|
button:hover { background: #c0d8ff; }
|
|
|
|
button.secondary {
|
|
background: #2a2a4a;
|
|
color: #8ab4f8;
|
|
border: 1px solid #8ab4f8;
|
|
}
|
|
button.secondary:hover { background: #3a3a5a; }
|
|
|
|
.step-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.85rem;
|
|
color: #aaa;
|
|
margin-bottom: 1.5rem;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.step-toggle input { cursor: pointer; accent-color: #8ab4f8; }
|
|
|
|
.step-speed {
|
|
display: none;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-size: 0.82rem;
|
|
color: #aaa;
|
|
margin-left: 1rem;
|
|
}
|
|
.step-speed.visible { display: flex; }
|
|
.step-speed input[type=range] { accent-color: #8ab4f8; }
|
|
|
|
.recall-area {
|
|
display: flex;
|
|
gap: 3rem;
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.result-box { text-align: center; }
|
|
|
|
.net-results {
|
|
display: flex;
|
|
gap: 2.5rem;
|
|
align-items: flex-start;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.net-block {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.net-title {
|
|
font-size: 0.8rem;
|
|
color: #8ab4f8;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.step-counter {
|
|
font-size: 0.78rem;
|
|
color: #666;
|
|
min-height: 1.2em;
|
|
}
|
|
|
|
.hamming-answer {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: #a0c4ff;
|
|
padding: 0.5rem 0;
|
|
}
|
|
|
|
.maxnet-bars {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.bar-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.bar-name { width: 1.5rem; color: #aaa; }
|
|
|
|
.bar-track {
|
|
flex: 1;
|
|
height: 14px;
|
|
background: #2a2a4a;
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bar-fill {
|
|
height: 100%;
|
|
background: #8ab4f8;
|
|
border-radius: 3px;
|
|
transition: width 0.15s;
|
|
}
|
|
|
|
.bar-val { width: 3rem; text-align: right; color: #666; }
|
|
|
|
.status-label {
|
|
margin-top: 0.5rem;
|
|
font-size: 0.82rem;
|
|
color: #aaa;
|
|
}
|
|
.status-label.yes { color: #80ffb0; }
|
|
.status-label.no { color: #ffaaaa; }
|
|
|
|
#results-section { display: none; }
|
|
|
|
#trace-panels {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-top: 1.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.trace-panel {
|
|
flex: 1;
|
|
min-width: 220px;
|
|
background: #1e1e3a;
|
|
border: 1px solid #3a3a5a;
|
|
border-radius: 6px;
|
|
padding: 0.75rem 1rem;
|
|
font-size: 0.88rem;
|
|
color: #b0c4e8;
|
|
line-height: 1.8;
|
|
max-height: 420px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.trace-panel-title {
|
|
font-family: monospace;
|
|
font-size: 0.75rem;
|
|
color: #8ab4f8;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
margin-bottom: 0.5rem;
|
|
border-bottom: 1px solid #3a3a5a;
|
|
padding-bottom: 0.3rem;
|
|
}
|
|
|
|
.trace-line {
|
|
font-family: monospace;
|
|
font-size: 0.82rem;
|
|
color: #888;
|
|
padding-left: 0.5rem;
|
|
}
|
|
|
|
.trace-header {
|
|
font-family: monospace;
|
|
font-size: 0.8rem;
|
|
color: #8ab4f8;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.trace-formula {
|
|
padding: 0.2rem 0;
|
|
}
|
|
|
|
.katex { color: #e8d8ff; }
|
|
|
|
#loading {
|
|
display: none;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
font-size: 0.85rem;
|
|
color: #8ab4f8;
|
|
}
|
|
|
|
.spinner {
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 2px solid #2a2a4a;
|
|
border-top-color: #8ab4f8;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div style="display:flex; align-items:center; gap:1rem; margin-bottom:2rem;">
|
|
<h1 style="margin-bottom:0;">Сети Хопфилда и Хэмминга</h1>
|
|
<a href="https://ada-dev.ru" class="secondary" style="padding:0.5rem 1.5rem; background:#2a2a4a; color:#8ab4f8; border:1px solid #8ab4f8; border-radius:4px; cursor:pointer; font-family:monospace; font-size:0.9rem; font-weight:bold; text-decoration:none;">Домой</a>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Эталонные образы</h2>
|
|
<div class="patterns-row">
|
|
<% @patterns.each do |name, vector| %>
|
|
<div class="pattern-box">
|
|
<div class="pattern-label"><%= name %></div>
|
|
<div class="grid">
|
|
<% vector.each do |v| %>
|
|
<div class="cell <%= v == 1 ? 'on' : 'off' %>"></div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Ввод зашумлённого образа</h2>
|
|
<div id="loading"><div class="spinner"></div>Вычисляю...</div>
|
|
<div class="controls">
|
|
<button onclick="recall()">Распознать</button>
|
|
<button class="secondary" onclick="clearGrid()">Очистить</button>
|
|
<button class="secondary" onclick="randomNoise()">Случайный шум</button>
|
|
</div>
|
|
<label class="step-toggle">
|
|
<input type="checkbox" id="step-mode"> Пошаговый режим
|
|
<span class="step-speed" id="speed-control">
|
|
· скорость:
|
|
<input type="range" id="step-delay" min="50" max="800" value="300" step="50">
|
|
<span id="delay-label">300 мс</span>
|
|
</span>
|
|
</label>
|
|
<label class="step-toggle">
|
|
<input type="checkbox" id="formula-mode"> Показывать формулы вычислений
|
|
</label>
|
|
|
|
<div class="recall-area">
|
|
<div class="pattern-box">
|
|
<div class="pattern-label">Вход</div>
|
|
<div class="grid" id="input-grid">
|
|
<% 64.times do %>
|
|
<div class="cell off interactive" data-val="-1"></div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="trace-panels" style="display:none">
|
|
<div class="trace-panel">
|
|
<div class="trace-panel-title">Хопфилд</div>
|
|
<div id="trace-hopfield"></div>
|
|
</div>
|
|
<div class="trace-panel">
|
|
<div class="trace-panel-title">Хэмминг</div>
|
|
<div id="trace-hamming"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="results-section">
|
|
<div class="net-results">
|
|
<div class="net-block">
|
|
<div class="net-title">Хопфилд — восстановление</div>
|
|
<div class="step-counter" id="hopfield-step"></div>
|
|
<div class="pattern-box">
|
|
<div class="grid" id="output-grid">
|
|
<% 64.times do %>
|
|
<div class="cell off"></div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div class="status-label" id="hopfield-label"></div>
|
|
</div>
|
|
|
|
<div class="net-block">
|
|
<div class="net-title">Хэмминг — MAXNET</div>
|
|
<div class="step-counter" id="hamming-step"></div>
|
|
<div class="maxnet-bars" id="maxnet-bars"></div>
|
|
<div class="hamming-answer" id="hamming-answer">—</div>
|
|
<div class="status-label" id="hamming-label"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const patterns = <%= @patterns.transform_values { |v| v }.to_json %>;
|
|
|
|
document.querySelectorAll('#input-grid .cell').forEach(cell => {
|
|
cell.addEventListener('click', () => {
|
|
const isOn = cell.dataset.val === '1';
|
|
cell.dataset.val = isOn ? '-1' : '1';
|
|
cell.className = 'cell interactive ' + (isOn ? 'off' : 'on');
|
|
});
|
|
});
|
|
|
|
let currentRunId = 0;
|
|
|
|
const stepModeCheckbox = document.getElementById('step-mode');
|
|
const formulaModeCheckbox = document.getElementById('formula-mode');
|
|
const speedControl = document.getElementById('speed-control');
|
|
const stepDelaySlider = document.getElementById('step-delay');
|
|
const delayLabel = document.getElementById('delay-label');
|
|
|
|
stepModeCheckbox.addEventListener('change', () => {
|
|
speedControl.classList.toggle('visible', stepModeCheckbox.checked);
|
|
});
|
|
|
|
function traceEl(panelId, className, content, isLatex = false) {
|
|
const panel = document.getElementById(panelId);
|
|
const el = document.createElement('div');
|
|
el.className = className;
|
|
if (isLatex) {
|
|
el.innerHTML = katex.renderToString(content, { throwOnError: false, displayMode: false });
|
|
} else {
|
|
el.textContent = content;
|
|
}
|
|
panel.appendChild(el);
|
|
panel.scrollTop = panel.scrollHeight;
|
|
}
|
|
|
|
const hfLine = (text) => traceEl('trace-hopfield', 'trace-line', text);
|
|
const hfFormula = (tex) => traceEl('trace-hopfield', 'trace-formula', tex, true);
|
|
const hmLine = (text) => traceEl('trace-hamming', 'trace-line', text);
|
|
const hmFormula = (tex) => traceEl('trace-hamming', 'trace-formula', tex, true);
|
|
|
|
function clearTrace() {
|
|
document.getElementById('trace-hopfield').innerHTML = '';
|
|
document.getElementById('trace-hamming').innerHTML = '';
|
|
document.getElementById('trace-panels').style.display = 'none';
|
|
}
|
|
|
|
function buildTrace(data) {
|
|
document.getElementById('trace-panels').style.display = 'flex';
|
|
const names = data.pattern_names;
|
|
const t = data.hamming_trace;
|
|
|
|
hfFormula(String.raw`s_j(p{+}1) = \sum_{i=0}^{n-1} w_{ij}\,y_i(p)`);
|
|
hfFormula(String.raw`y_j(p{+}1) = f\!\left[s_j(p{+}1)\right], \quad f(s)=\begin{cases}1&s\geq0\\-1&s<0\end{cases}`);
|
|
data.hopfield_nets.forEach((nd, i) => {
|
|
const mark = nd.changed === 0 ? ' ← сошлось' : '';
|
|
hfLine(`p=${i + 1}: изменено ${nd.changed}/64${mark}`);
|
|
});
|
|
|
|
hmFormula(String.raw`w_{ik} = \tfrac{x_i^k}{2}, \quad T_k = \tfrac{n}{2}`);
|
|
hmFormula(String.raw`y_j^{(1)} = \sum_{i=0}^{n-1} w_{ij}\,x_i + T_j`);
|
|
const layer = names.map((n, i) => `${n}\\!=\\!${t.initial_outputs[i]}`).join(',\\;');
|
|
hmFormula(String.raw`y_j^{(2)}(0) = y_j^{(1)}:\;` + layer);
|
|
hmFormula(String.raw`0 < \varepsilon < \tfrac{1}{m},\quad \varepsilon = \tfrac{0.9}{${t.k}} = ${t.eps}`);
|
|
hmFormula(String.raw`s_j^{(2)}(p{+}1) = y_j^{(2)}(p) - \varepsilon\!\sum_{k \neq j} y_k^{(2)}(p)`);
|
|
hmFormula(String.raw`y_j^{(2)}(p{+}1) = f\!\left[s_j^{(2)}(p{+}1)\right]`);
|
|
data.hamming_steps.slice(1).forEach((step, i) => {
|
|
const vals = names.map((n, j) => `${n}\\!=\\!${step[j]}`).join(',\\;');
|
|
hmFormula(`p=${i + 1}:\\; y^{(2)} = [\\;` + vals + `\\;]`);
|
|
});
|
|
hmFormula(String.raw`\Rightarrow \text{победитель: } \mathbf{` + data.hamming_winner + `}`);
|
|
}
|
|
|
|
stepDelaySlider.addEventListener('input', () => {
|
|
delayLabel.textContent = stepDelaySlider.value + ' мс';
|
|
});
|
|
|
|
function getInputVector() {
|
|
return [...document.querySelectorAll('#input-grid .cell')]
|
|
.map(c => parseFloat(c.dataset.val));
|
|
}
|
|
|
|
function hideResults() {
|
|
document.getElementById('results-section').style.display = 'none';
|
|
clearTrace();
|
|
}
|
|
|
|
function clearGrid() {
|
|
document.querySelectorAll('#input-grid .cell').forEach(c => {
|
|
c.dataset.val = '-1';
|
|
c.className = 'cell interactive off';
|
|
});
|
|
hideResults();
|
|
}
|
|
|
|
function randomNoise() {
|
|
document.querySelectorAll('#input-grid .cell').forEach(c => {
|
|
const v = Math.random() > 0.5 ? '1' : '-1';
|
|
c.dataset.val = v;
|
|
c.className = 'cell interactive ' + (v === '1' ? 'on' : 'off');
|
|
});
|
|
hideResults();
|
|
}
|
|
|
|
function vectorsEqual(a, b) {
|
|
return a.every((v, i) => Math.abs(v - b[i]) < 0.01);
|
|
}
|
|
|
|
function sleep(ms) {
|
|
return new Promise(r => setTimeout(r, ms));
|
|
}
|
|
|
|
function renderGrid(cells, vector) {
|
|
cells.forEach((c, i) => {
|
|
c.className = 'cell ' + (vector[i] === 1 ? 'on' : 'off');
|
|
});
|
|
}
|
|
|
|
function renderBars(names, values) {
|
|
const max = Math.max(...values, 1);
|
|
const container = document.getElementById('maxnet-bars');
|
|
if (!container.children.length) {
|
|
container.innerHTML = names.map(n => `
|
|
<div class="bar-row">
|
|
<span class="bar-name">${n}</span>
|
|
<div class="bar-track"><div class="bar-fill" style="width:0%"></div></div>
|
|
<span class="bar-val">0</span>
|
|
</div>`).join('');
|
|
}
|
|
[...container.querySelectorAll('.bar-row')].forEach((row, i) => {
|
|
const pct = (values[i] / max * 100).toFixed(1);
|
|
row.querySelector('.bar-fill').style.width = pct + '%';
|
|
row.querySelector('.bar-val').textContent = values[i].toFixed(1);
|
|
});
|
|
}
|
|
|
|
async function showSteps(data, runId) {
|
|
const delay = parseInt(stepDelaySlider.value);
|
|
const cells = [...document.querySelectorAll('#output-grid .cell')];
|
|
const names = data.pattern_names;
|
|
const showTrace = formulaModeCheckbox.checked && data.hopfield_nets;
|
|
|
|
document.getElementById('results-section').style.display = 'block';
|
|
document.getElementById('hamming-answer').textContent = '—';
|
|
document.getElementById('hamming-label').textContent = '';
|
|
document.getElementById('hopfield-label').textContent = '';
|
|
|
|
if (showTrace) {
|
|
clearTrace();
|
|
document.getElementById('trace-panels').style.display = 'flex';
|
|
hfFormula(String.raw`s_j(p{+}1) = \sum_{i=0}^{n-1} w_{ij}\,y_i(p)`);
|
|
hfFormula(String.raw`y_j(p{+}1) = f\!\left[s_j(p{+}1)\right], \quad f(s)=\begin{cases}1&s\geq0\\-1&s<0\end{cases}`);
|
|
}
|
|
|
|
renderBars(names, data.hamming_steps[0]);
|
|
|
|
const hfSteps = data.hopfield_steps;
|
|
const hmSteps = data.hamming_steps;
|
|
const total = Math.max(hfSteps.length, hmSteps.length);
|
|
|
|
for (let i = 0; i < total; i++) {
|
|
if (i < hfSteps.length) {
|
|
renderGrid(cells, hfSteps[i]);
|
|
document.getElementById('hopfield-step').textContent =
|
|
i === 0 ? 'Шаг 0 — входной вектор' : `Шаг ${i} из ${hfSteps.length - 1}`;
|
|
if (showTrace && i > 0 && data.hopfield_nets[i - 1]) {
|
|
const nd = data.hopfield_nets[i - 1];
|
|
const mark = nd.changed === 0 ? ' ← сошлось' : '';
|
|
hfLine(`p=${i}: изменено ${nd.changed}/64${mark}`);
|
|
}
|
|
}
|
|
if (i < hmSteps.length) {
|
|
renderBars(names, hmSteps[i]);
|
|
document.getElementById('hamming-step').textContent =
|
|
i === 0 ? 'Шаг 0 — слой Хэмминга' : `Итерация MAXNET ${i} из ${hmSteps.length - 1}`;
|
|
if (showTrace) {
|
|
const t = data.hamming_trace;
|
|
if (i === 0) {
|
|
hmFormula(String.raw`w_{ik} = \tfrac{x_i^k}{2}, \quad T_k = \tfrac{n}{2}`);
|
|
hmFormula(String.raw`y_j^{(1)} = \sum_{i=0}^{n-1} w_{ij}\,x_i + T_j`);
|
|
const layer = names.map((n, j) => `${n}\\!=\\!${t.initial_outputs[j]}`).join(',\\;');
|
|
hmFormula(String.raw`y_j^{(2)}(0) = y_j^{(1)}:\;` + layer);
|
|
hmFormula(String.raw`0 < \varepsilon < \tfrac{1}{m},\quad \varepsilon = \tfrac{0.9}{${t.k}} = ${t.eps}`);
|
|
hmFormula(String.raw`s_j^{(2)}(p{+}1) = y_j^{(2)}(p) - \varepsilon\!\sum_{k \neq j} y_k^{(2)}(p)`);
|
|
hmFormula(String.raw`y_j^{(2)}(p{+}1) = f\!\left[s_j^{(2)}(p{+}1)\right]`);
|
|
} else {
|
|
const vals = names.map((n, j) => `${n}\\!=\\!${hmSteps[i][j]}`).join(',\\;');
|
|
hmFormula(`p=${i}:\\; y^{(2)} = [\\;` + vals + `\\;]`);
|
|
}
|
|
}
|
|
}
|
|
await sleep(delay);
|
|
if (runId !== currentRunId) return;
|
|
}
|
|
|
|
const hfFinal = hfSteps[hfSteps.length - 1];
|
|
document.getElementById('hopfield-step').textContent =
|
|
`Завершено за ${hfSteps.length - 1} шаг(ов)`;
|
|
const hfMatch = Object.entries(patterns).find(([_, v]) => vectorsEqual(hfFinal, v))?.[0];
|
|
const hfLabel = document.getElementById('hopfield-label');
|
|
hfLabel.textContent = hfMatch ? `Совпадает с эталоном: ${hfMatch}` : 'Не совпадает ни с одним эталоном';
|
|
hfLabel.className = 'status-label ' + (hfMatch ? 'yes' : 'no');
|
|
|
|
document.getElementById('hamming-step').textContent =
|
|
`Завершено за ${hmSteps.length - 1} итерац(ий)`;
|
|
document.getElementById('hamming-answer').textContent = data.hamming_winner;
|
|
const hmLabel = document.getElementById('hamming-label');
|
|
hmLabel.textContent = `Ближайший эталон: ${data.hamming_winner}`;
|
|
hmLabel.className = 'status-label yes';
|
|
if (showTrace) hmFormula(String.raw`\Rightarrow \text{победитель (}\,y_j^{(2)}\text{ max):}\;\mathbf{` + data.hamming_winner + `}`);
|
|
}
|
|
|
|
function showResult(data) {
|
|
const cells = [...document.querySelectorAll('#output-grid .cell')];
|
|
document.getElementById('hopfield-step').textContent = '';
|
|
document.getElementById('hamming-step').textContent = '';
|
|
|
|
renderGrid(cells, data.hopfield);
|
|
|
|
const hfMatch = Object.entries(patterns).find(([_, v]) => vectorsEqual(data.hopfield, v))?.[0];
|
|
const hfLabel = document.getElementById('hopfield-label');
|
|
hfLabel.textContent = hfMatch ? `Совпадает с эталоном: ${hfMatch}` : 'Не совпадает ни с одним эталоном';
|
|
hfLabel.className = 'status-label ' + (hfMatch ? 'yes' : 'no');
|
|
|
|
document.getElementById('hamming-answer').textContent = data.hamming_winner || data.hamming;
|
|
const hmLabel = document.getElementById('hamming-label');
|
|
hmLabel.textContent = `Ближайший эталон: ${data.hamming_winner || data.hamming}`;
|
|
hmLabel.className = 'status-label yes';
|
|
|
|
document.getElementById('maxnet-bars').innerHTML = '';
|
|
document.getElementById('results-section').style.display = 'block';
|
|
|
|
if (data.hopfield_nets && data.hopfield_nets.length) {
|
|
clearTrace();
|
|
buildTrace(data);
|
|
}
|
|
}
|
|
|
|
async function recall() {
|
|
const runId = ++currentRunId;
|
|
const vector = getInputVector();
|
|
const stepMode = stepModeCheckbox.checked;
|
|
const formulaMode = formulaModeCheckbox.checked;
|
|
const endpoint = formulaMode ? '/recall_trace'
|
|
: stepMode ? '/recall_steps'
|
|
: '/recall';
|
|
|
|
document.getElementById('loading').style.display = 'flex';
|
|
document.getElementById('results-section').style.display = 'none';
|
|
clearTrace();
|
|
|
|
try {
|
|
const resp = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ vector })
|
|
});
|
|
if (runId !== currentRunId) return;
|
|
const data = await resp.json();
|
|
document.getElementById('loading').style.display = 'none';
|
|
|
|
if (stepMode) {
|
|
await showSteps(data, runId);
|
|
} else {
|
|
showResult(data);
|
|
}
|
|
} catch {
|
|
document.getElementById('loading').style.display = 'none';
|
|
alert('Ошибка соединения с сервером');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
<footer style="margin-top:3rem; font-size:0.75rem; color:#666; text-align:center;">
|
|
© Created by Antipenko D.A. for MIAD - 2026
|
|
</html>
|