| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- import numpy as np
- import matplotlib.pyplot as plt
- # -------------------------------
- # Define the battery model functions
- # -------------------------------
- def ocv_model(soc, temp):
- """
- Open-circuit voltage model as a function of SOC and temperature.
- For demonstration, we use a simple linear relationship.
- In a real application, this should be replaced with an empirically derived model.
-
- Parameters:
- soc: state-of-charge (0 to 1)
- temp: temperature (°C) [not used in this simple model]
- Returns:
- OCV (V)
- """
- # Example: voltage ranges from 3.0V to 4.2V
- return 3.0 + 1.2 * soc
- def battery_model(x, current, dt):
- """
- State propagation model.
- State vector x = [SOC, capacity]
- - SOC: state-of-charge (0 to 1)
- - capacity: battery capacity in Ah
- The SOC decreases with discharge current.
-
- Parameters:
- x: state vector [soc, capacity]
- current: battery current (A) (positive for discharge)
- dt: time interval (seconds)
- Returns:
- Updated state vector.
- """
- soc, capacity = x
- # Note: capacity is in Ah and dt is in seconds, so convert capacity to Coulombs (Ah * 3600)
- soc_new = soc - (current * dt) / (capacity * 3600)
- # For simplicity, assume capacity changes very slowly; we let it remain nearly constant.
- return np.array([soc_new, capacity])
- def jacobian_f(x, current, dt):
- """
- Computes the Jacobian of the battery_model function with respect to state x.
- """
- soc, capacity = x
- # f1 = soc - (current*dt)/(capacity*3600)
- df1_dsoc = 1.0
- df1_dcap = (current * dt) / (capacity**2 * 3600)
- # f2 = capacity (assumed constant), so its derivatives are:
- df2_dsoc = 0.0
- df2_dcap = 1.0
- return np.array([[df1_dsoc, df1_dcap],
- [df2_dsoc, df2_dcap]])
- def measurement_model(x, current, temp, R=0.05):
- """
- Measurement model: the terminal voltage is given by the open-circuit voltage
- minus the voltage drop across the internal resistance.
-
- Parameters:
- x: state vector [soc, capacity]
- current: battery current (A)
- temp: temperature (°C)
- R: internal resistance (Ohm)
- Returns:
- Terminal voltage (V)
- """
- soc, _ = x
- return ocv_model(soc, temp) - current * R
- def jacobian_h(x, current, temp, R=0.05):
- """
- Computes the Jacobian of the measurement_model with respect to the state x.
- Here we assume the OCV model derivative with respect to SOC is constant (1.2 V per unit SOC).
- """
- # dV/dsoc: derivative of ocv_model with respect to soc (in our simple model, it's constant)
- dV_dsoc = 1.2
- dV_dcap = 0.0 # Voltage is not directly affected by capacity in this simple model.
- return np.array([dV_dsoc, dV_dcap]).reshape(1, -1)
- # -------------------------------
- # Extended Kalman Filter functions
- # -------------------------------
- def ekf_predict(x, P, current, dt, Q):
- """
- EKF prediction step.
-
- Parameters:
- x: current state estimate
- P: current state covariance matrix
- current: current battery current (A)
- dt: time step (s)
- Q: process noise covariance matrix
- Returns:
- x_pred: predicted state
- P_pred: predicted covariance
- """
- x_pred = battery_model(x, current, dt)
- F = jacobian_f(x, current, dt)
- P_pred = F @ P @ F.T + Q
- return x_pred, P_pred
- def ekf_update(x_pred, P_pred, z, current, temp, R_internal=0.05, R_measure=0.01):
- """
- EKF update step.
-
- Parameters:
- x_pred: predicted state
- P_pred: predicted covariance matrix
- z: measured voltage (V)
- current: battery current (A)
- temp: temperature (°C)
- R_internal: internal resistance used in the measurement model
- R_measure: measurement noise variance
- Returns:
- x_updated: updated state estimate
- P_updated: updated covariance matrix
- """
- H = jacobian_h(x_pred, current, temp, R_internal)
- z_pred = measurement_model(x_pred, current, temp, R_internal)
- y = z - z_pred # innovation or measurement residual
- S = H @ P_pred @ H.T + R_measure # innovation covariance
- K = P_pred @ H.T @ np.linalg.inv(S) # Kalman gain
- x_updated = x_pred + (K * y).flatten() # update state (flatten since K*y is a vector)
- P_updated = (np.eye(len(x_pred)) - K @ H) @ P_pred
- return x_updated, P_updated
- # -------------------------------
- # Simulation / Filtering Example
- # -------------------------------
- # Simulation parameters
- dt = 0.25 # time step in seconds
- num_steps = 100 # number of simulation steps
- # Initialize state:
- # Assume starting at 100% SOC and a capacity of 3 Ah (e.g., 3000 mAh)
- x_est = np.array([1.0, 3.0])
- # Initial state covariance
- P_est = np.diag([0.01, 0.01])
- # Process noise covariance matrix
- Q = np.diag([1e-5, 1e-6])
- # Measurement noise variance (for voltage measurement)
- R_measure = 0.01
- # Internal resistance (Ohm)
- R_internal = 0.05
- # Generate dummy measurement data
- # For example, assume a constant discharge current of 0.5 A and constant temperature (25°C)
- current_array = np.full(num_steps, 0.5)
- temp_array = np.full(num_steps, 25)
- voltage_meas_array = np.zeros(num_steps)
- voltage_true_array = np.zeros(num_steps)
- # For simulation purposes, we generate a "true" state trajectory.
- x_true = np.array([1.0, 3.0]) # initial true state
- # Lists to store estimates for plotting
- soc_estimates = []
- cap_estimates = []
- for k in range(num_steps):
- # --- Simulation: generate true state and corresponding measurement ---
- x_true = battery_model(x_true, current_array[k], dt)
- voltage_true = measurement_model(x_true, current_array[k], temp_array[k], R_internal)
- # Simulate measurement noise
- noise = np.random.normal(0, np.sqrt(R_measure))
- voltage_meas = voltage_true + noise
- voltage_true_array[k] = voltage_true
- voltage_meas_array[k] = voltage_meas
- # --- EKF Predict Step ---
- x_pred, P_pred = ekf_predict(x_est, P_est, current_array[k], dt, Q)
- # --- EKF Update Step ---
- x_est, P_est = ekf_update(x_pred, P_pred, voltage_meas, current_array[k], temp_array[k],
- R_internal, R_measure)
- soc_estimates.append(x_est[0])
- cap_estimates.append(x_est[1])
-
- # Optional: print the step results
- print(f"Step {k:03d}: Measured Voltage = {voltage_meas:.3f} V, Estimated SOC = {x_est[0]:.4f}, Capacity = {x_est[1]:.4f} Ah")
- # -------------------------------
- # Plotting results for visualization
- # -------------------------------
- plt.figure(figsize=(12, 5))
- plt.subplot(1, 2, 1)
- plt.plot(soc_estimates, label='Estimated SOC')
- plt.xlabel('Time Step')
- plt.ylabel('State of Charge')
- plt.title('SOC Estimation')
- plt.legend()
- plt.subplot(1, 2, 2)
- plt.plot(cap_estimates, label='Estimated Capacity (Ah)')
- plt.xlabel('Time Step')
- plt.ylabel('Capacity (Ah)')
- plt.title('Capacity (SOH) Estimation')
- plt.legend()
- plt.tight_layout()
- plt.show()
|