@@ -3,8 +3,6 @@
< head >
< meta charset = "UTF-8" >
< title > Сети Хопфилда и Хэмминга</ title >
< 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 ; }
@@ -186,60 +184,6 @@
# results-section { display : none ; }
# trace-panels {
display : flex ;
gap : 1 rem ;
margin-top : 1.5 rem ;
flex-wrap : wrap ;
}
. trace-panel {
flex : 1 ;
min-width : 220 px ;
background : #1e1e3a ;
border : 1 px solid #3a3a5a ;
border-radius : 6 px ;
padding : 0.75 rem 1 rem ;
font-size : 0.88 rem ;
color : #b0c4e8 ;
line-height : 1.8 ;
max-height : 420 px ;
overflow-y : auto ;
}
. trace-panel-title {
font-family : monospace ;
font-size : 0.75 rem ;
color : #8ab4f8 ;
text-transform : uppercase ;
letter-spacing : 0.08 em ;
margin-bottom : 0.5 rem ;
border-bottom : 1 px solid #3a3a5a ;
padding-bottom : 0.3 rem ;
}
. trace-line {
font-family : monospace ;
font-size : 0.82 rem ;
color : #888 ;
padding-left : 0.5 rem ;
}
. trace-header {
font-family : monospace ;
font-size : 0.8 rem ;
color : #8ab4f8 ;
text-transform : uppercase ;
letter-spacing : 0.05 em ;
margin-top : 0.5 rem ;
}
. trace-formula {
padding : 0.2 rem 0 ;
}
. katex { color : #e8d8ff ; }
# loading {
display : none ;
align-items : center ;
@@ -297,9 +241,6 @@
< 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" >
@@ -311,17 +252,6 @@
</ 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" >
@@ -360,68 +290,15 @@
});
});
let currentRunId = 0 ;
const stepModeCheckbox = document . getElementById ( 'step-mo de' );
const formulaModeCheckbox = document . getElementById ( 'formula-mode ' );
const speedControl = document . getElementById ( 'speed-control' );
const stepDelaySlider = document . getElementById ( 'step-delay' );
const delayLabel = document . getElementById ( 'delay-label' );
const stepModeCheckbox = document . getElementById ( 'step-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 + ' мс' ;
});
@@ -431,17 +308,12 @@
. 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 () ;
document . getElementById ( 'results-section' ). style . display = 'none' ;
}
function randomNoise () {
@@ -450,7 +322,7 @@
c . dataset . val = v ;
c . className = 'cell interactive ' + ( v === '1' ? 'on' : 'off' );
});
hideResults () ;
document . getElementById ( 'results-section' ). style . display = 'none' ;
}
function vectorsEqual ( a , b ) {
@@ -485,24 +357,17 @@
});
}
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 ;
async function showSteps ( data ) {
const delay = parseInt ( stepDelaySlider . value );
const cells = [... document . querySelectorAll ( '#output-grid .cell' )];
const names = data . pattern_names ;
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 ;
@@ -514,36 +379,16 @@
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 } шаг(ов)` ;
@@ -552,13 +397,13 @@
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 ) {
@@ -573,32 +418,24 @@
hfLabel . textContent = hfMatch ? `Совпадает с эталоном: ${ hfMatch } ` : 'Не совпадает ни с одним эталоном' ;
hfLabel . className = 'status-label ' + ( hfMatch ? 'yes' : 'no' );
document . getElementById ( 'hamming-answer' ). textContent = data . hamming_winner || data . hamming ;
document . getElementById ( 'hamming-answer' ). textContent = data . hamming ;
const hmLabel = document . getElementById ( 'hamming-label' );
hmLabel . textContent = `Ближайший эталон: ${ data . hamming_winner || data . hamming } ` ;
hmLabel . textContent = `Ближайший эталон: ${ 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 );
}
document . getElementById ( 'results-section' ). style . display = 'block' ;
}
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' ;
const vector = getInputVector () ;
const stepMode = stepModeCheckbox . checked ;
const endpoint = stepMode ? '/recall_steps' : '/recall' ;
document . getElementById ( 'loading' ). style . display = 'flex' ;
document . getElementById ( 'results-section' ). style . display = 'none' ;
clearTrace ();
try {
const resp = await fetch ( endpoint , {
@@ -606,12 +443,11 @@
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 );
await showSteps ( data );
} else {
showResult ( data );
}
@@ -623,6 +459,6 @@
</ script >
</ body >
< footer style = "margin-top:3rem; font-size:0.75 rem; color:#666 ; text-align:center;" >
© Created by Antipenko D.A. for MIAD - 2026
< footer style = "margin-top: 3rem; font-size: 0.8 rem; color: #555 ; text-align: center;" >
© Визуализация работы сетей Хопфилда и Хэмминга by Антипенко Д.А . - 2026. Ruby/Sinatra
</ html >