# -*- coding: utf-8 -*-
"""thevenin_ann_hybrid.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/1HeW03zTKfyvMj87IojBNIYsyhl3XwrkP
"""

import numpy as np
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
import plotly.graph_objects as go
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error, r2_score
import tensorflow as tf
import pickle

def smape(y_true, y_pred):
    return 100 * np.mean(2 * np.abs(y_pred - y_true) / (np.abs(y_pred) + np.abs(y_true)))

data = pd.read_csv('/content/battery_text.csv')
data = data.dropna()

features = ['Voltage', 'Current', 'Time']
target = 'SoC'
X = data[features]
y = data[target]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=16, shuffle=True)

# Normalize features
scaler_X = StandardScaler() # Define scaler_X
X_train_scaled = scaler_X.fit_transform(X_train) # Fit scaler_X to the training data
X_test_scaled = scaler_X.transform(X_test)

with open('scaler_X.pkl', 'wb') as f:
    pickle.dump(scaler_X, f)

model = tf.keras.Sequential([
    tf.keras.layers.InputLayer(input_shape=(X_train_scaled.shape[1],)),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(1)  # Output layer for SoC prediction
])

model.compile(optimizer='adam', loss='mse')
model.fit(X_train_scaled, y_train, epochs=100, batch_size=32, verbose=1)
y_pred_ann = model.predict(X_test_scaled)
y_pred_ann = y_pred_ann.flatten()  # Flatten to 1D array
model.save('hybrid_ann_thevenin_model.keras')  # Add the .keras extension

# Evaluate the ANN model
mae_ann = mean_absolute_error(y_test, y_pred_ann)
r2_ann = r2_score(y_test, y_pred_ann)
smape_ann = smape(y_test, y_pred_ann)

print(f"ANN Model: MAE: {mae_ann:.4f}, R²: {r2_ann:.4f}, SMAPE: {smape_ann:.4f}%")

# Plotly colors
plotly_colors = px.colors.qualitative.Plotly

# 1. Actual vs Predicted SoC (ANN)
fig1 = go.Figure()
fig1.add_trace(go.Scatter(
    x=y_test, y=y_pred_ann.flatten(),
    mode='markers',
    marker=dict(size=7, color=plotly_colors[0]),
    name='Predicted SoC'
))
fig1.add_trace(go.Scatter(
    x=[min(y_test), max(y_test)], y=[min(y_test), max(y_test)],
    mode='lines',
    line=dict(color='black', width=2, dash='dash'),
    name='Perfect Prediction'
))
error_percentage = smape(y_test, y_pred_ann)
fig1.add_annotation(
    x=max(y_test) * 0.6, y=max(y_pred_ann) * 0.9,
    text=f"Error: {error_percentage:.2f}%",
    showarrow=False, font=dict(size=12, color='black')
)
fig1.update_layout(
    title="Actual vs Predicted SoC (ANN)",
    xaxis_title="Actual SoC",
    yaxis_title="Predicted SoC",
    font=dict(size=14),
    plot_bgcolor='white',
    xaxis=dict(showgrid=True, gridcolor='lightgray'),
    yaxis=dict(showgrid=True, gridcolor='lightgray'),
    legend=dict(x=0.8, y=0.1, bgcolor='rgba(0,0,0,0)')
)
fig1.show()

# 2. Distribution of Errors (Histogram)
error_dist = y_test - y_pred_ann.flatten()
fig3 = go.Figure(go.Histogram(
    x=error_dist,
    marker_color=plotly_colors[1],
    opacity=0.75,
))
fig3.update_layout(
    title="Distribution of Errors (ANN)",
    xaxis_title="Error",
    yaxis_title="Frequency",
    font=dict(size=14),
    plot_bgcolor='white',
    xaxis=dict(showgrid=True, gridcolor='lightgray'),
    yaxis=dict(showgrid=True, gridcolor='lightgray'),
)
fig3.show()

# 3. Residuals vs. Predicted Values
fig4 = go.Figure()
fig4.add_trace(go.Scatter(
    x=y_pred_ann.flatten(), y=error_dist,
    mode='markers',
    marker=dict(size=7, color=plotly_colors[2]),
    name='Residuals'
))
fig4.add_trace(go.Scatter(
    x=[min(y_pred_ann), max(y_pred_ann)], y=[0, 0],
    mode='lines',
    line=dict(color='black', width=1, dash='dash'),
    showlegend=False
))
fig4.update_layout(
    title="Residuals vs. Predicted Values (ANN)",
    xaxis_title="Predicted SoC",
    yaxis_title="Residuals",
    font=dict(size=14),
    plot_bgcolor='white',
    xaxis=dict(showgrid=True, gridcolor='lightgray'),
    yaxis=dict(showgrid=True, gridcolor='lightgray'),
)
fig4.show()

# 4. Correlation Matrix (Voltage, Current, Time, SoC)
corr_matrix = data[['Voltage', 'Current', 'Time', 'SoC']].corr()
fig5 = go.Figure(go.Heatmap(
    z=corr_matrix.values,
    x=corr_matrix.columns,
    y=corr_matrix.columns,
    colorscale='Viridis',
    showscale=True,
    colorbar=dict(title="Correlation")
))
fig5.update_layout(
    title="Correlation Matrix",
    font=dict(size=14),
    plot_bgcolor='white',
    xaxis=dict(showgrid=True, gridcolor='lightgray'),
    yaxis=dict(showgrid=True, gridcolor='lightgray'),
)
fig5.update_traces(text=corr_matrix.values.round(2), texttemplate="%{text}")
fig5.show()

# Function to generate a neural network diagram
def plot_ann():
    layer_sizes = [3, 64, 128, 128, 1]  # Input layer (3), Hidden layers (64, 128, 128), Output layer (1)
    fig = go.Figure()

    # Define the layout of each layer
    layer_ys = []
    for i, layer_size in enumerate(layer_sizes):
        layer_ys.append(np.linspace(-1, 1, layer_size))  # Distribute the nodes vertically

    # Define layer positions
    layer_xs = np.linspace(0, 1, len(layer_sizes))

    # Plot nodes and connections between layers
    for i, (x, ys) in enumerate(zip(layer_xs, layer_ys)):
        fig.add_trace(go.Scatter(
            x=[x] * len(ys),
            y=ys,
            mode='markers',
            marker=dict(size=20, color='blue' if i != 0 and i != len(layer_sizes) - 1 else ('red' if i == 0 else 'green')),
            name=f"Layer {i + 1}"
        ))

        # Draw connections between layers
        if i > 0:
            for y_start in layer_ys[i - 1]:
                for y_end in ys:
                    fig.add_trace(go.Scatter(
                        x=[layer_xs[i - 1], x],
                        y=[y_start, y_end],
                        mode='lines',
                        line=dict(color='gray', width=1),
                        showlegend=False
                    ))

    # Update layout
    fig.update_layout(
        title="ANN-Thevenin Model Architecture",
        showlegend=False,
        xaxis=dict(showgrid=False, zeroline=False, visible=False),
        yaxis=dict(showgrid=False, zeroline=False, visible=False),
        width=1000,
        height=600,
        margin=dict(l=0, r=0, t=50, b=0)
    )

    fig.show()

# Call the function to plot the ANN architecture
plot_ann()

import pandas as pd
import numpy as np
import tensorflow as tf
import pickle

# Load the scaler for input features (you must save the scaler during training)
def load_scaler(filename):
    with open(filename, 'rb') as f:
        scaler = pickle.load(f)
    return scaler

# Load the hybrid ANN-Thevenin model from the saved .keras file
def load_hybrid_model(filename):
    hybrid_model = tf.keras.models.load_model(filename)
    return hybrid_model

# Function to make a prediction using predefined Voltage, Current, and Time values
def predict_soc(hybrid_model, scaler, voltage, current, time):
    # Prepare the input array as a DataFrame with feature names
    input_data = pd.DataFrame([[voltage, current, time]], columns=['Voltage', 'Current', 'Time'])

    # Scale the input data using the scaler from training
    input_data_scaled = scaler.transform(input_data)

    # Make prediction using the hybrid model
    predicted_soc = hybrid_model.predict(input_data_scaled)
    return predicted_soc[0][0]  # Extract the prediction from the nested array

# Load the saved hybrid ANN-Thevenin model (.keras file)
model_filename = 'hybrid_ann_thevenin_model.keras'
hybrid_model = load_hybrid_model(model_filename)

# Load the saved scaler used during training
scaler_filename = 'scaler_X.pkl'
scaler = load_scaler(scaler_filename)

# Define your input values here
voltage = 16.9    # Example Voltage in volts
current = 1.0     # Example Current in amps
time = 1.0      # Example Time in seconds

# Make the prediction
predicted_soc = predict_soc(hybrid_model, scaler, voltage, current, time)
print(f"Predicted SoC: {predicted_soc:.4f}")



Convert all the plotly plots to matplotlib