import os os.system("pip install numpy matplotlib pandas") import numpy as np import matplotlib.pyplot as plt import gradio as gr import pandas as pd # --- Your Helper Functions --- def flatten(img : np.array) -> list[int] : """Converts a 2D numpy array into a 1D list.""" new : list[int] = [] for row in img: for item in row: new.append(int(item)) return new def sgn(x): """Sign function.""" if x < 0: return -1 if x == 0: return 0 return 1 # --- Your Hopfield Class (with one bugfix) --- class Hopfield: def __init__(self,patts): self.E : list[int] = [] self.patts = patts self.size = (4,4) # Fixed size for reshaping self.Px :int = len(patts) self.Py :int = len(patts[0]) # Initialize weights self.W : np.array = np.zeros((self.Py,self.Py),dtype=np.float16) def train(self): """Trains the network on the patterns provided in __init__.""" for i in range(self.Py): for j in range(self.Py): if i == j: self.W[i][j] = 0 continue # Hebbian rule self.W[i][j] = (1 / self.Px) * sum([patt[i] * patt[j] for patt in self.patts]) def Energy(self): """Returns the list of energy values recorded during updates.""" return self.E def update(self,pattern): """ Performs one asynchronous update step on the entire pattern. """ # Flatten the 2D input pattern to 1D pattern_flat = flatten(pattern) # Calculate the new state vector H H : list[int] = [] for i in self.W: # H_i = sgn(sum(W_ij * S_j)) H.append(sgn(sum([w * s for w,s in zip(i, pattern_flat)]))) H = np.array(H) # Calculate the energy of this new state H E = 0 for i in range(self.Py): for j in range(self.Py): E += float(-0.5 * self.W[i][j] * H[i] * H[j]) self.E.append(E) # --- FIX --- # Use reshape, not resize. Resize can add/remove elements. # Reshape will fail if H doesn't have 16 elements, which is safer. return H.reshape(self.size) # --- Default Patterns for the Gradio App --- # Pattern 1: 'X' patt_1_default = [[ 1, -1, -1, 1], [-1, 1, 1, -1], [-1, 1, 1, -1], [ 1, -1, -1, 1]] # Pattern 2: 'C' patt_2_default = [[ 1, 1, 1, -1], [ 1, -1, -1, -1], [ 1, -1, -1, -1], [ 1, 1, 1, -1]] # Pattern 3: 'L' patt_3_default = [[ 1, -1, -1, -1], [ 1, -1, -1, -1], [ 1, -1, -1, -1], [ 1, 1, 1, 1]] # Initial (corrupted) shape to test initial_shape_default = [[ 1, 1, -1, -1], [ 1, -1, -1, -1], [ 1, -1, 1, -1], [ 1, 1, 1, -1]] # --- Gradio Core Logic --- def clean_dataframe(df): """Helper to convert Gradio dataframe to a clean NumPy array.""" # Fill any empty cells (None) with -1 and convert to int return df.fillna(-1).to_numpy(dtype=int) def run_hopfield(patt1_df, patt2_df, patt3_df, initial_shape_df, steps): """ The main function for the Gradio interface. """ # 1. Clean inputs p1 = clean_dataframe(patt1_df) p2 = clean_dataframe(patt2_df) p3 = clean_dataframe(patt3_df) initial_shape = clean_dataframe(initial_shape_df) # 2. Collect patterns to train (ignore empty/all -1 patterns) patterns_to_train = [] if np.any(p1 == 1): patterns_to_train.append(flatten(p1)) if np.any(p2 == 1): patterns_to_train.append(flatten(p2)) if np.any(p3 == 1): patterns_to_train.append(flatten(p3)) # 3. Check if any patterns were provided if not patterns_to_train: fig_shape = plt.figure() plt.title("Error: No patterns provided to train.") plt.axis('off') fig_energy = plt.figure() plt.title("Error: No patterns provided to train.") return fig_shape, fig_energy # 4. Create and train the model patts = np.array(patterns_to_train) model = Hopfield(patts) model.train() # 5. Run the evolution current_shape = initial_shape for _ in range(int(steps)): next_shape = model.update(current_shape) # Check for convergence if np.array_equal(current_shape, next_shape): break current_shape = next_shape # 6. Generate final shape plot fig_shape = plt.figure() plt.imshow(current_shape, cmap='gray', vmin=-1, vmax=1) plt.title("Final Evolved Shape") plt.axis('off') # 7. Generate energy plot fig_energy = plt.figure() energy_data = model.Energy() if energy_data: plt.plot(list(range(len(energy_data))), energy_data, marker='o') plt.title("Energy Evolution") plt.xlabel("Update Step") plt.ylabel("Energy") plt.grid(True) else: plt.title("Energy (No Updates Run)") return fig_shape, fig_energy # --- Gradio Interface --- with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# 🧠 Hopfield Network Simulator") gr.Markdown( "Define up to 3 patterns (1 for 'on', -1 for 'off'). " "The network will learn them. Then, draw an 'Initial Shape' " "and see if the network can evolve it into one of the patterns it learned." ) with gr.Row(): with gr.Column(): gr.Markdown("### 1. Define Patterns to Memorize") # Set headers to be invisible, type to pandas for .fillna patt1_in = gr.Dataframe( value=patt_1_default, label="Pattern 1", headers=None, datatype="number", col_count=4, row_count=4, type="pandas" ) patt2_in = gr.Dataframe( value=patt_2_default, label="Pattern 2", headers=None, datatype="number", col_count=4, row_count=4, type="pandas" ) patt3_in = gr.Dataframe( value=patt_3_default, label="Pattern 3", headers=None, datatype="number", col_count=4, row_count=4, type="pandas" ) with gr.Column(): gr.Markdown("### 2. Set Initial Shape & Run") initial_in = gr.Dataframe( value=initial_shape_default, label="Initial Shape (Test Pattern)", headers=None, datatype="number", col_count=4, row_count=4, type="pandas" ) steps_in = gr.Slider( minimum=1, maximum=10, value=5, step=1, label="Max Evolution Steps" ) run_btn = gr.Button("Run Evolution", variant="primary") with gr.Row(): with gr.Column(): gr.Markdown("### 3. Results") shape_out = gr.Plot(label="Final Evolved Shape") with gr.Column(): gr.Markdown("### 4. Diagnostics") energy_out = gr.Plot(label="Energy Evolution") # Connect the button to the function run_btn.click( fn=run_hopfield, inputs=[patt1_in, patt2_in, patt3_in, initial_in, steps_in], outputs=[shape_out, energy_out] ) if __name__ == "__main__": demo.launch()