import numpy as np
import matplotlib.pyplot as plt

class SimplePerceptron:
    def __init__(self, lr=0.01, epochs=1000):
        self.lr = lr
        self.epochs = epochs
        self.coefficients = None
        self.intercept = None
        
    def fit(self, features, targets):
        num_samples, num_features = features.shape
        self.coefficients = np.zeros(num_features)
        self.intercept = 0
        
        target_labels = np.where(targets <= 0, -1, 1)
        
        for _ in range(self.epochs):
            for i, x in enumerate(features):
                linear_result = np.dot(x, self.coefficients) + self.intercept
                predicted = self._activation(linear_result)
                
                delta = self.lr * (target_labels[i] - predicted)
                self.coefficients += delta * x
                self.intercept += delta
    
    def predict(self, features):
        linear_result = np.dot(features, self.coefficients) + self.intercept
        return self._activation(linear_result)
    
    def _activation(self, x):
        return np.where(x >= 0, 1, -1)

# Main block for demonstration
if __name__ == "__main__":
    np.random.seed(42)
    input_data = np.random.randn(100, 2)
    output_labels = np.where(input_data[:, 0] + input_data[:, 1] > 0, 1, -1)
    
    perceptron_model = SimplePerceptron(lr=0.1, epochs=100)
    perceptron_model.fit(input_data, output_labels)
    
    plt.figure(figsize=(10, 6))
    plt.scatter(input_data[:, 0], input_data[:, 1], c=output_labels, cmap='winter', marker='o', s=100)
    
    x_lower, x_upper = input_data[:, 0].min() - 1, input_data[:, 0].max() + 1
    y_lower, y_upper = input_data[:, 1].min() - 1, input_data[:, 1].max() + 1
    grid_x, grid_y = np.meshgrid(np.arange(x_lower, x_upper, 0.1),
                                 np.arange(y_lower, y_upper, 0.1))
    
    grid_predictions = perceptron_model.predict(np.c_[grid_x.ravel(), grid_y.ravel()])
    grid_predictions = grid_predictions.reshape(grid_x.shape)
    
    plt.contourf(grid_x, grid_y, grid_predictions, alpha=0.3, cmap='winter')
    plt.title('Perceptron Decision Boundary')
    plt.xlabel('Feature 1')
    plt.ylabel('Feature 2')
    plt.show()
    
    print(f"Final Weights: {perceptron_model.coefficients}")
    print(f"Final Bias: {perceptron_model.intercept}")
