An Improved Pelican Optimization Algorithm for Solving Stochastic Optimal Power Flow

the Improved Pelican Optimization Algorithm (IPOA)—a novel metaheuristic approach designed specifically for stochastic OPF (S-OPF) problems under uncertainty.

As the world transitions toward clean and sustainable energy, power systems are increasingly integrating renewable energy resources (RERs) such as solar photovoltaic (PV) and wind power. While these sources reduce carbon emissions and operational costs, their inherent uncertainty and intermittency pose significant challenges to maintaining grid stability and efficiency. One of the most critical tools for managing modern power networks is the Optimal Power Flow (OPF) problem, which determines the optimal operating state of a power system while minimizing costs and satisfying technical constraints.

However, traditional OPF methods struggle when applied to systems with high RER penetration due to unpredictable generation patterns. To address this, researchers have developed advanced optimization techniques—among them, the Improved Pelican Optimization Algorithm (IPOA)—a novel metaheuristic approach designed specifically for stochastic OPF (S-OPF) problems under uncertainty.

In this article, we explore how IPOA enhances the performance of the original Pelican Optimization Algorithm (POA), its application in solving S-OPF with solar and wind integration, and why it outperforms other state-of-the-art algorithms in both accuracy and convergence speed.


What Is Stochastic Optimal Power Flow?

The Optimal Power Flow (OPF) problem aims to minimize an objective function—such as fuel cost or emissions—while ensuring that all physical and operational constraints of the power system are satisfied. These include:

  • Active and reactive power balance
  • Voltage limits at buses
  • Generator output bounds
  • Transformer tap settings
  • Transmission line capacity

When renewable energy sources like wind and solar are introduced, their variable nature introduces stochasticity, making deterministic OPF insufficient. This leads to the Stochastic Optimal Power Flow (S-OPF) framework, which accounts for uncertainties in wind speed, solar irradiance, and load demand using probabilistic models.

Why Traditional Methods Fall Short

Conventional optimization techniques like Newton-Raphson, gradient-based, or interior-point methods often suffer from:

  • Slow convergence
  • Getting trapped in local optima
  • Inability to handle non-convex, non-linear, and multi-modal functions

To overcome these limitations, metaheuristic algorithms inspired by nature—such as Genetic Algorithms, Particle Swarm Optimization (PSO), and Grey Wolf Optimizer (GWO)—have gained popularity. However, even these can stagnate or lack sufficient exploration-exploitation balance.

That’s where the Improved Pelican Optimization Algorithm (IPOA) comes into play.


The Pelican Optimization Algorithm: Nature-Inspired Intelligence

Introduced in 2022, the Pelican Optimization Algorithm (POA) mimics the hunting behavior of pelicans, particularly their two-phase strategy:

  1. Exploration Phase: Pelicans scan large areas to locate prey.
  2. Exploitation Phase: Once near the target, they dive and use wing movements to herd fish into their pouches.

While POA shows promise, it suffers from premature convergence and limited search diversity, especially in complex, high-dimensional problems like S-OPF.

To enhance its performance, the authors of the study proposed three key improvements, forming the Improved Pelican Optimization Algorithm (IPOA).


Key Innovations in the Improved Pelican Optimization Algorithm (IPOA)

The IPOA integrates three novel strategies to boost exploration, avoid local optima, and accelerate convergence:

1. Mutation-Based Strategy

This mechanism introduces genetic variation into the population using two mutation operators:

  • Conservative Mutation:
\[ v_i^G = x_{m1}^G + F \cdot \big(x_{m2}^G – x_{m3}^G\big) \]
  • Current-to-Best Mutation:
\[ v_i^G = x_{\text{best}}^G + F \cdot \big(x_{m1}^G – x_{m2}^G\big) + F \cdot \big(x_{m3}^G – x_{m4}^G\big) \]

Here, F is a scaling factor, and indices m1,m2,m3,m4 are randomly selected individuals different from i . This diversifies the search space and prevents premature convergence.

2. Fitness Distance Balanced (FDB) Selection

The FDB strategy improves selection pressure by balancing fitness value and distance from the best solution. It calculates a score for each individual based on normalized fitness and spatial distance:

\[ DS_i = \sum_{j=1}^{d} \big(x_{i,j} – Best_j \big)^{2} \]

Normalized values:

\[ \text{normDS}_i = \frac{DS_i – \min(DS)}{\max(DS) – \min(DS)}, \qquad \text{normF}_i = \frac{F_i – \min(F)}{\max(F) – \min(F)} \]

Final FDB score:

\[ \text{FDBscore}_i = \alpha \cdot \big(1 – \text{norm}F_i\big) + (1 – \alpha) \cdot \text{norm}DS_i \]

where α = 0.5⋅(1+ t / Tmax​) increases exploitation over time.

This ensures a balanced transition from global exploration to local refinement.

3. Exploitation-Based Gorilla Troops Optimizer (GTO)

Inspired by gorilla social dynamics, the GTO component simulates how silverback leaders guide troop movement. The update equation is:

\[ GX(t+1) = L \cdot M \cdot \big( X(t) – X_{\text{silverback}} \big) + X(t) \]

where:

\[ M = \left( \frac{\sum_{i=1}^{N} X_i(t)}{2L} \right)^{\frac{1}{2L}} \] \[ L = C \cdot l, \quad C = \big(\cos(2r^5) + 1\big)\cdot\left(1 – \frac{t}{T_{\max}}\right) \] \[ l \in [-1, 1] \quad \text{is random} \]

    This enhances local search capability and fine-tunes solutions during later iterations.

    ✅ Together, these enhancements make IPOA more robust, accurate, and adaptable to complex engineering problems like S-OPF.


    Modeling Uncertainty in Wind and Solar Generation

    For S-OPF to be effective, realistic models of RER uncertainty must be incorporated.

    Wind Power Uncertainty

    Wind speed follows a Weibull probability density function (PDF):

    \[ f_v(v) = (c k)\,(c v)^{k-1} e^{-(c v)^k}, \quad v > 0 \]

    Where:

    • k : shape parameter
    • c : scale parameter
    • Mean wind speed: Mwbl = c⋅Γ (1+k−1)

    Wind turbine output depends on cut-in (vin ), rated (vr ), and cut-out (vout​ ) speeds:

    \[ P_w(v) = \begin{cases} 0, & v < v_{\text{in}} \ \text{or} \ v > v_{\text{out}} \\[6pt] \dfrac{P_w \cdot v_r}{v_r – v_{\text{in}}}(v – v_{\text{in}}), & v_{\text{in}} \leq v \leq v_r \\[10pt] P_w, & v_r < v \leq v_{\text{out}} \end{cases} \]

    ​​

    Assumed values: vin = 3 , vr ​= 16 , vout = 25 m/s.

    Solar PV Uncertainty

    Solar irradiance is modeled using a lognormal PDF:

    \[ f_I(I) = \frac{1}{I \sigma \sqrt{2\pi}} \exp\!\left(-\frac{(\ln I – \mu)^2}{2\sigma^2}\right), \quad I > 0 \]

    PV output:

    \[ P_{pv}(I) = \begin{cases} \dfrac{P_{pvr} \cdot I_{std}}{I}, & I \geq I_c \\[8pt] \dfrac{P_{pvr} \cdot I_{std} \cdot I_c}{I^2}, & 0 < I < I_c \end{cases} \]

    ​​

    Where Ic​ is the threshold irradiance and Istd​ is standard test condition irradiance.

    These models were integrated into a modified IEEE 30-bus system with wind farms at buses 5 and 11 (75 MW and 60 MW), and solar PV units also connected at those locations.


    Objective Functions: Minimizing Cost and Emissions

    The S-OPF problem considers two main objectives.

    Objective 1: Total Generation Cost Minimization

    Includes thermal, wind, and solar costs, including penalties for over/underestimation.

    Thermal Fuel Cost (with valve-point effect):

    \[ \varsigma_T(P^{TG}) = \sum_{i=1}^{N_{tg}} \Big( a_i P_{gi}^2 + b_i P_{gi} + c_i + \big| d_i \sin\big(e_i (P_{Gi}^{\min} – P_{Gi})\big) \big| \Big) \]

    Wind and Solar Direct Costs:

    \[ \lambda_{ws,j}(P_{ws,j}) = g_j \, P_{ws,j}, \qquad \lambda_{pvs,k}(P_{pvs,k}) = h_k \, P_{pvs,k} \]

    Reserve and Penalty Costs:

    For wind:

    \[ \xi_{w,j}(P_{w,j}) = \lambda_{ws,j} + \lambda_{wr,j}\big(P_{ws,j} – P_{wav,j}\big) + \lambda_{wp,j}\big(P_{wav,j} – P_{ws,j}\big) \]

    For solar:

    \[ \xi_{pv,k}(P_{pv,k}) = \lambda_{pvs,k} \;+\; \lambda_{pvr,k}\,(P_{pvs,k} – P_{pva,k}) \;+\; \lambda_{pvp,k}\,(P_{pva,k} – P_{pvs,k}) \]

    Full Objective Function:

    \[ F_{1} = \varsigma T(P_{TG}) \;+\; \sum_{j} \xi_{w,j} \;+\; \sum_{k} \xi_{pv,k} \]


    Objective 2: Emission-Aware Cost Minimization

    Carbon emissions from thermal units:

    \[ E = \sum_{i=1}^{N_{TG}} \Big[ (\alpha_i + \beta_i P_{TG,i} + \gamma_i P_{TG,i}^2)\times 0.01 \;+\; \omega_i \cdot e^{\mu_i P_{TG,i}} \Big] \] \[\text{With carbon tax} ( C_{\text{tax}} = $20/\text{ton} )\]: \[ F_2 = F_1 + C_{\text{tax}} \cdot E \]

    Performance Evaluation: IPOA vs. State-of-the-Art Algorithms

    The IPOA was tested against five powerful optimizers:

    • Sand Cat Swarm Optimization (SCSO)
    • Grey Wolf Optimizer (GWO)
    • Zebra Optimization Algorithm (ZOA)
    • Dandelion Optimizer (DO)
    • Original Pelican Optimization Algorithm (POA)

    Benchmark Testing: CEC 2019 Functions

    FUNCTIONBEST SOLVERIPOA RANK
    G1IPOA1st
    G2IPOA1st
    G3TieTop
    G4DO2nd
    G5IPOA1st
    G6DO (best), IPOA (mean)1st (avg)
    G7IPOA1st
    G8–G10IPOA1st

    Statistical tests confirmed significant superiority:

    • Wilcoxon rank-sum test: p-values < 0.05 for most cases
    • Friedman test: IPOA ranked first overall

    Case Studies: Application to Modified IEEE 30-Bus System

    Two scenarios were evaluated:

    Case 1: Generation Cost Reduction (No Emissions)

    ALGORITHMBEST COST ($)AVG.COST)STD DEV
    SCSO862.00862.600.36
    GWO862.06862.460.25
    DO861.92862.290.26
    IPOA861.89861.930.06

    ✅ IPOA achieved the lowest cost and highest stability.

    Case 2: Emission-Constrained Cost Minimization

    ALGORITHMBEST COST ($)AVG. COST ($)STD DEV
    DO892.1585892.22610.122
    IPOA892.1538892.18030.120

    Despite tighter constraints, IPOA delivered the best solution, reducing emissions to 0.885 ton/h and power losses to 5.4486 MW.

    📈 Convergence plots show IPOA reaches optimal solutions faster than competitors, often within 30–40 iterations.


    🏆 Why IPOA Outperforms Other Algorithms

    FEATURESIPOA ADVANTAGES
    ExplorationMutation + FDB ensures diverse initial search
    ExploitationGTO-based refinement improves local search
    BalanceDynamic α in FDB balances exploration/exploitation
    RobustnessHandles non-convex, noisy, multi-modal landscapes
    SpeedFaster convergence due to guided updates

    Moreover, IPOA maintains all system variables within feasible limits (voltage, reactive power, transformer taps), proving its practical applicability.


    🔮 Future Applications and Research Directions

    The success of IPOA opens doors for:

    • Large-scale grid integration with multiple RERs
    • Electric vehicle (EV) charging coordination
    • Microgrid energy management
    • Real-time dispatch in smart grids

    Future work may involve hybridizing IPOA with machine learning models for predictive optimization or applying it to multi-objective OPF with environmental and economic trade-offs.


    ✅ Conclusion: A Leap Forward in Power System Optimization

    The Improved Pelican Optimization Algorithm (IPOA) represents a major advancement in solving stochastic optimal power flow problems in modern power systems. By integrating mutation strategies, fitness-distance balance, and gorilla troops exploitation, IPOA achieves superior performance in terms of:

    • Solution quality
    • Convergence speed
    • Stability
    • Constraint handling

    Validated through rigorous testing on CEC 2019 benchmarks and real-world IEEE 30-bus case studies, IPOA consistently outperforms leading metaheuristics like GWO, DO, and POA in minimizing generation costs and emissions under renewable uncertainty.

    As power systems grow more complex and decentralized, intelligent algorithms like IPOA will become essential tools for engineers, planners, and policymakers striving for affordable, reliable, and sustainable electricity.


    Call to Action: Join the Energy Optimization Revolution!

    Are you working on power system optimization, renewable integration, or smart grid technologies? Download the full research paper to dive deeper into the IPOA methodology, equations, and simulation data.

    👉 Click here to access the complete study

    💡 Want to implement IPOA in your project?
    Contact the corresponding authors:

    🔗 Follow us for more insights on AI-driven energy solutions, optimization algorithms, and sustainable grid technologies.

    Let’s build a smarter, cleaner energy future—together.

    I’ll provide a complete Python implementation of the Improved Pelican Optimization Algorithm (IPOA) for solving the stochastic optimal power flow problem as described in the paper. This implementation includes the uncertainty modeling of renewable energy resources and the three improvement strategies.

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.stats import weibull_min, lognorm
    from scipy.special import gamma
    import pandas as pd
    import time
    
    class IPOA:
        def __init__(self, n_pop, n_iter, problem_dim, lb, ub, objective_func):
            self.n_pop = n_pop
            self.n_iter = n_iter
            self.problem_dim = problem_dim
            self.lb = lb
            self.ub = ub
            self.objective_func = objective_func
            
            # Initialize population
            self.population = np.random.uniform(lb, ub, (n_pop, problem_dim))
            self.fitness = np.zeros(n_pop)
            self.best_solution = None
            self.best_fitness = float('inf')
            self.fitness_history = []
            
            # Parameters for improvements
            self.F = 0.5  # Scaling factor for mutation
            self.a = 0.5  # Initial weight for FDB
            self.betamin = 0.2
            self.betamax = 0.8
            self.p = 0.03
            self.w = 0.8
            
        def initialize_population(self):
            self.population = np.random.uniform(self.lb, self.ub, (self.n_pop, self.problem_dim))
            for i in range(self.n_pop):
                self.fitness[i] = self.objective_func(self.population[i])
                
                if self.fitness[i] < self.best_fitness:
                    self.best_fitness = self.fitness[i]
                    self.best_solution = self.population[i].copy()
                    
            self.fitness_history.append(self.best_fitness)
        
        def mutation_strategy(self, current_iter):
            mutated_population = np.zeros_like(self.population)
            
            for i in range(self.n_pop):
                # Select three random individuals
                r1, r2, r3 = np.random.choice(range(self.n_pop), 3, replace=False)
                
                # Conventional mutation
                if np.random.rand() < 0.5:
                    mutated_population[i] = self.population[r1] + self.F * (self.population[r2] - self.population[r3])
                # Current-to-best mutation
                else:
                    r4 = np.random.randint(0, self.n_pop)
                    mutated_population[i] = self.best_solution + self.F * (self.population[r1] - self.population[r2]) + \
                                           self.F * (self.population[r3] - self.population[r4])
                
                # Apply bounds
                mutated_population[i] = np.clip(mutated_population[i], self.lb, self.ub)
                
            return mutated_population
        
        def FDB_selection(self, current_iter):
            # Calculate distance matrix
            DS = np.zeros(self.n_pop)
            for i in range(self.n_pop):
                DS[i] = np.sqrt(np.sum((self.population[i] - self.best_solution)**2))
            
            # Normalize distance and fitness
            normDS = (DS - np.min(DS)) / (np.max(DS) - np.min(DS) + 1e-10)
            normF = (self.fitness - np.min(self.fitness)) / (np.max(self.fitness) - np.min(self.fitness) + 1e-10)
            
            # Update weight parameter
            self.a = 0.5 * (1 + current_iter / self.n_iter)
            
            # Calculate FDB score
            FDBscore = self.a * (1 - normF) + (1 - self.a) * normDS
            
            return FDBscore
        
        def GTO_exploitation(self, current_iter):
            # Calculate M parameter
            M = (np.abs(np.sum(self.population, axis=0) / self.n_pop) ** (2 ** np.random.rand())) ** (1 / (2 ** np.random.rand()))
            
            # Calculate C parameter
            C = (np.cos(2 * np.random.rand()) + 1) * (1 - current_iter / self.n_iter)
            
            # Calculate L parameter
            L = C * np.random.uniform(-1, 1, self.problem_dim)
            
            # Apply GTO mechanism
            GX = L * M * (self.population - self.best_solution) + self.population
            
            # Apply bounds
            GX = np.clip(GX, self.lb, self.ub)
            
            return GX
        
        def optimize(self):
            self.initialize_population()
            
            for iter in range(self.n_iter):
                # Phase 1: Exploration (moving towards prey)
                new_population_1 = np.zeros_like(self.population)
                for i in range(self.n_pop):
                    if self.fitness[i] > self.best_fitness:
                        new_population_1[i] = self.population[i] + np.random.rand() * (self.best_solution - self.population[i])
                    else:
                        new_population_1[i] = self.population[i] + np.random.rand() * (self.population[i] - self.best_solution)
                
                # Apply bounds
                new_population_1 = np.clip(new_population_1, self.lb, self.ub)
                
                # Evaluate new population
                new_fitness_1 = np.zeros(self.n_pop)
                for i in range(self.n_pop):
                    new_fitness_1[i] = self.objective_func(new_population_1[i])
                    
                    # Update if better
                    if new_fitness_1[i] < self.fitness[i]:
                        self.population[i] = new_population_1[i]
                        self.fitness[i] = new_fitness_1[i]
                        
                        if new_fitness_1[i] < self.best_fitness:
                            self.best_fitness = new_fitness_1[i]
                            self.best_solution = new_population_1[i].copy()
                
                # Phase 2: Exploitation (whizzing on water surface)
                R = 0.2
                new_population_2 = np.zeros_like(self.population)
                for i in range(self.n_pop):
                    new_population_2[i] = self.population[i] + R * (1 - iter/self.n_iter) * (2*np.random.rand() - 1) * self.population[i]
                
                # Apply bounds
                new_population_2 = np.clip(new_population_2, self.lb, self.ub)
                
                # Evaluate new population
                new_fitness_2 = np.zeros(self.n_pop)
                for i in range(self.n_pop):
                    new_fitness_2[i] = self.objective_func(new_population_2[i])
                    
                    # Update if better
                    if new_fitness_2[i] < self.fitness[i]:
                        self.population[i] = new_population_2[i]
                        self.fitness[i] = new_fitness_2[i]
                        
                        if new_fitness_2[i] < self.best_fitness:
                            self.best_fitness = new_fitness_2[i]
                            self.best_solution = new_population_2[i].copy()
                
                # Apply improvement strategies
                # 1. Mutation strategy
                mutated_pop = self.mutation_strategy(iter)
                for i in range(self.n_pop):
                    mut_fitness = self.objective_func(mutated_pop[i])
                    if mut_fitness < self.fitness[i]:
                        self.population[i] = mutated_pop[i]
                        self.fitness[i] = mut_fitness
                        
                        if mut_fitness < self.best_fitness:
                            self.best_fitness = mut_fitness
                            self.best_solution = mutated_pop[i].copy()
                
                # 2. FDB selection
                FDBscore = self.FDB_selection(iter)
                # Sort by FDBscore (lower is better)
                sorted_indices = np.argsort(FDBscore)
                self.population = self.population[sorted_indices]
                self.fitness = self.fitness[sorted_indices]
                
                # 3. GTO exploitation
                GTO_pop = self.GTO_exploitation(iter)
                for i in range(self.n_pop):
                    GTO_fitness = self.objective_func(GTO_pop[i])
                    if GTO_fitness < self.fitness[i]:
                        self.population[i] = GTO_pop[i]
                        self.fitness[i] = GTO_fitness
                        
                        if GTO_fitness < self.best_fitness:
                            self.best_fitness = GTO_fitness
                            self.best_solution = GTO_pop[i].copy()
                
                # Record best fitness
                self.fitness_history.append(self.best_fitness)
                
                # Print progress
                if iter % 10 == 0:
                    print(f"Iteration {iter}, Best Fitness: {self.best_fitness}")
            
            return self.best_solution, self.best_fitness, self.fitness_history
    
    # Stochastic OPF problem formulation
    class StochasticOPF:
        def __init__(self):
            # System parameters (based on IEEE 30-bus system)
            self.n_buses = 30
            self.n_gen = 6  # including renewable generators
            self.n_wind = 2
            self.n_solar = 2
            
            # Cost coefficients for thermal generators
            self.a_i = np.array([0.00375, 0.0175, 0.0625, 0.00834, 0.025, 0.025])
            self.b_i = np.array([2.00, 1.75, 1.00, 3.25, 3.00, 3.00])
            self.c_i = np.array([0, 0, 0, 0, 0, 0])
            
            # Valve-point loading effect coefficients
            self.d_i = np.array([0.063, 0.098, 0.082, 0.056, 0.052, 0.052])
            self.e_i = np.array([0.041, 0.036, 0.028, 0.024, 0.022, 0.022])
            
            # Wind cost coefficients
            self.g_i = np.array([1.5, 1.5])  # $/MWh
            self.K_wrj = np.array([3.0, 3.0])  # reserve cost coefficient
            self.K_wpj = np.array([1.5, 1.5])  # penalty cost coefficient
            
            # Solar cost coefficients
            self.h_k = np.array([1.8, 1.8])  # $/MWh
            self.K_pnrk = np.array([2.5, 2.5])  # reserve cost coefficient
            self.K_ppk = np.array([1.2, 1.2])  # penalty cost coefficient
            
            # Emission coefficients
            self.alpha_i = np.array([4.091, 2.543, 4.258, 5.326, 4.258, 6.131])
            self.beta_i = np.array([-5.554, -6.047, -5.094, -3.55, -5.094, -5.555])
            self.gamma_i = np.array([6.49, 5.638, 4.586, 3.38, 4.586, 5.151])
            self.eta_i = np.array([2.0e-4, 5.0e-4, 1.0e-6, 2.0e-3, 1.0e-6, 1.0e-5])
            self.delta_i = np.array([2.857, 3.333, 8.000, 2.000, 8.000, 6.667])
            
            # Carbon tax
            self.C_tax = 20  # $/ton
            
            # Wind parameters
            self.v_in = 3  # m/s, cut-in speed
            self.v_r = 16  # m/s, rated speed
            self.v_out = 25  # m/s, cut-out speed
            self.P_wr = np.array([75, 60])  # MW, rated power of wind farms
            
            # Solar parameters
            self.I_c = 120  # W/m², certain irradiance
            self.I_std = 1000  # W/m², standard irradiance
            self.P_prr = np.array([35, 50])  # MW, rated power of solar PV units
            
            # Weibull parameters for wind (k, c)
            self.wind_params = np.array([[2.0, 8.0], [2.0, 8.0]])
            
            # Lognormal parameters for solar (mu, sigma)
            self.solar_params = np.array([[5.5, 0.5], [5.5, 0.5]])
            
            # Power limits
            self.P_min = np.array([50, 20, 15, 10, 10, 12])  # MW
            self.P_max = np.array([140, 80, 50, 35, 30, 40])  # MW
            self.Q_min = np.array([-20, -20, -15, -15, -10, -12])  # MVAR
            self.Q_max = np.array([150, 60, 50, 40, 30, 40])  # MVAR
            self.V_min = 0.95  # p.u.
            self.V_max = 1.1  # p.u.
            
            # Load data (simplified)
            self.P_load = 283.4  # MW
            self.Q_load = 126.2  # MVAR
            
            # Number of scenarios for Monte Carlo simulation
            self.n_scenarios = 1000
            
        def weibull_pdf(self, v, k, c):
            """Weibull probability density function"""
            return (k/c) * (v/c)**(k-1) * np.exp(-(v/c)**k)
        
        def lognormal_pdf(self, I, mu, sigma):
            """Lognormal probability density function"""
            return (1/(I * sigma * np.sqrt(2*np.pi))) * np.exp(-(np.log(I)-mu)**2/(2*sigma**2))
        
        def wind_power(self, v):
            """Wind power output as function of wind speed"""
            P_w = np.zeros_like(v)
            for i in range(len(v)):
                if v[i] < self.v_in or v[i] > self.v_out:
                    P_w[i] = 0
                elif v[i] < self.v_r:
                    P_w[i] = self.P_wr * ((v[i] - self.v_in) / (self.v_r - self.v_in))
                else:
                    P_w[i] = self.P_wr
            return P_w
        
        def solar_power(self, I):
            """Solar power output as function of irradiance"""
            P_p = np.zeros_like(I)
            for i in range(len(I)):
                if I[i] <= 0:
                    P_p[i] = 0
                elif I[i] < self.I_c:
                    P_p[i] = self.P_prr * (I[i]**2 / (self.I_std * self.I_c))
                else:
                    P_p[i] = self.P_prr * (I[i] / self.I_std)
            return P_p
        
        def thermal_cost(self, P_g):
            """Quadratic fuel cost with valve-point loading effect"""
            cost = 0
            for i in range(len(P_g)):
                cost += self.a_i[i] * P_g[i]**2 + self.b_i[i] * P_g[i] + self.c_i[i] + \
                       self.d_i[i] * np.abs(np.sin(self.e_i[i] * (self.P_min[i] - P_g[i])))
            return cost
        
        def wind_direct_cost(self, P_ws):
            """Direct cost of scheduled wind power"""
            return np.sum(self.g_i * P_ws)
        
        def wind_reserve_cost(self, P_ws, P_wav):
            """Reserve cost for wind power"""
            cost = 0
            for j in range(self.n_wind):
                if P_ws[j] > P_wav[j]:
                    cost += self.K_wrj[j] * (P_ws[j] - P_wav[j])
            return cost
        
        def wind_penalty_cost(self, P_ws, P_wav):
            """Penalty cost for wind power"""
            cost = 0
            for j in range(self.n_wind):
                if P_ws[j] < P_wav[j]:
                    cost += self.K_wpj[j] * (P_wav[j] - P_ws[j])
            return cost
        
        def solar_direct_cost(self, P_ps):
            """Direct cost of scheduled solar power"""
            return np.sum(self.h_k * P_ps)
        
        def solar_reserve_cost(self, P_ps, P_pav):
            """Reserve cost for solar power"""
            cost = 0
            for k in range(self.n_solar):
                if P_ps[k] > P_pav[k]:
                    cost += self.K_pnrk[k] * (P_ps[k] - P_pav[k])
            return cost
        
        def solar_penalty_cost(self, P_ps, P_pav):
            """Penalty cost for solar power"""
            cost = 0
            for k in range(self.n_solar):
                if P_ps[k] < P_pav[k]:
                    cost += self.K_ppk[k] * (P_pav[k] - P_ps[k])
            return cost
        
        def emission_cost(self, P_g):
            """Emission cost from thermal generators"""
            E = 0
            for i in range(len(P_g)):
                E += (self.alpha_i[i] + self.beta_i[i] * P_g[i] + self.gamma_i[i] * P_g[i]**2) * 0.01 + \
                     self.eta_i[i] * np.exp(self.delta_i[i] * P_g[i])
            return E * self.C_tax
        
        def generate_scenarios(self):
            """Generate scenarios for wind and solar power"""
            # Generate wind scenarios
            wind_scenarios = np.zeros((self.n_scenarios, self.n_wind))
            for j in range(self.n_wind):
                k, c = self.wind_params[j]
                v_scenarios = weibull_min.rvs(k, scale=c, size=self.n_scenarios)
                wind_scenarios[:, j] = self.wind_power(v_scenarios)
            
            # Generate solar scenarios
            solar_scenarios = np.zeros((self.n_scenarios, self.n_solar))
            for k in range(self.n_solar):
                mu, sigma = self.solar_params[k]
                I_scenarios = lognorm.rvs(sigma, scale=np.exp(mu), size=self.n_scenarios)
                solar_scenarios[:, k] = self.solar_power(I_scenarios)
            
            return wind_scenarios, solar_scenarios
        
        def calculate_expected_costs(self, P_ws, P_ps):
            """Calculate expected costs using Monte Carlo simulation"""
            wind_scenarios, solar_scenarios = self.generate_scenarios()
            
            # Calculate expected available power
            E_P_wav = np.mean(wind_scenarios, axis=0)
            E_P_pav = np.mean(solar_scenarios, axis=0)
            
            # Calculate direct costs
            direct_cost_thermal = 0  # Will be calculated in the main objective function
            direct_cost_wind = self.wind_direct_cost(P_ws)
            direct_cost_solar = self.solar_direct_cost(P_ps)
            
            # Calculate expected reserve and penalty costs
            reserve_cost_wind = 0
            penalty_cost_wind = 0
            reserve_cost_solar = 0
            penalty_cost_solar = 0
            
            for s in range(self.n_scenarios):
                reserve_cost_wind += self.wind_reserve_cost(P_ws, wind_scenarios[s])
                penalty_cost_wind += self.wind_penalty_cost(P_ws, wind_scenarios[s])
                reserve_cost_solar += self.solar_reserve_cost(P_ps, solar_scenarios[s])
                penalty_cost_solar += self.solar_penalty_cost(P_ps, solar_scenarios[s])
            
            reserve_cost_wind /= self.n_scenarios
            penalty_cost_wind /= self.n_scenarios
            reserve_cost_solar /= self.n_scenarios
            penalty_cost_solar /= self.n_scenarios
            
            return (direct_cost_wind, reserve_cost_wind, penalty_cost_wind,
                    direct_cost_solar, reserve_cost_solar, penalty_cost_solar,
                    E_P_wav, E_P_pav)
        
        def power_balance_constraint(self, P_g, P_ws, P_ps, E_P_wav, E_P_pav):
            """Power balance constraint"""
            # Calculate total generation
            total_gen = np.sum(P_g) + np.sum(P_ws) + np.sum(P_ps)
            
            # Calculate expected available renewable power
            total_avail_gen = np.sum(P_g) + np.sum(E_P_wav) + np.sum(E_P_pav)
            
            # Power balance penalty
            balance_penalty = abs(total_avail_gen - self.P_load - 5.8)  # 5.8 MW is approximate loss
            
            return balance_penalty
        
        def constraint_violation(self, x):
            """Calculate constraint violation penalty"""
            # Extract decision variables
            P_g = x[0:6]  # Thermal generation
            P_ws = x[6:8]  # Wind scheduled power
            P_ps = x[8:10]  # Solar scheduled power
            V_g = x[10:16]  # Generator voltages
            
            penalty = 0
            
            # Thermal generation limits
            for i in range(6):
                if P_g[i] < self.P_min[i]:
                    penalty += 1000 * (self.P_min[i] - P_g[i])**2
                if P_g[i] > self.P_max[i]:
                    penalty += 1000 * (P_g[i] - self.P_max[i])**2
            
            # Wind scheduled power limits
            for j in range(2):
                if P_ws[j] < 0:
                    penalty += 1000 * (0 - P_ws[j])**2
                if P_ws[j] > self.P_wr[j]:
                    penalty += 1000 * (P_ws[j] - self.P_wr[j])**2
            
            # Solar scheduled power limits
            for k in range(2):
                if P_ps[k] < 0:
                    penalty += 1000 * (0 - P_ps[k])**2
                if P_ps[k] > self.P_prr[k]:
                    penalty += 1000 * (P_ps[k] - self.P_prr[k])**2
            
            # Voltage limits
            for i in range(6):
                if V_g[i] < self.V_min:
                    penalty += 1000 * (self.V_min - V_g[i])**2
                if V_g[i] > self.V_max:
                    penalty += 1000 * (V_g[i] - self.V_max])**2
            
            return penalty
        
        def objective_function_case1(self, x):
            """Objective function for Case 1: Minimize total cost without carbon tax"""
            # Extract decision variables
            P_g = x[0:6]  # Thermal generation
            P_ws = x[6:8]  # Wind scheduled power
            P_ps = x[8:10]  # Solar scheduled power
            
            # Calculate thermal cost
            thermal_cost = self.thermal_cost(P_g)
            
            # Calculate expected costs for wind and solar
            (direct_cost_wind, reserve_cost_wind, penalty_cost_wind,
             direct_cost_solar, reserve_cost_solar, penalty_cost_solar,
             E_P_wav, E_P_pav) = self.calculate_expected_costs(P_ws, P_ps)
            
            # Total cost
            total_cost = (thermal_cost + direct_cost_wind + reserve_cost_wind + penalty_cost_wind +
                         direct_cost_solar + reserve_cost_solar + penalty_cost_solar)
            
            # Add constraint violation penalty
            penalty = self.constraint_violation(x)
            
            # Add power balance penalty
            balance_penalty = self.power_balance_constraint(P_g, P_ws, P_ps, E_P_wav, E_P_pav)
            
            return total_cost + 100 * penalty + 100 * balance_penalty
        
        def objective_function_case2(self, x):
            """Objective function for Case 2: Minimize total cost with carbon tax"""
            # Extract decision variables
            P_g = x[0:6]  # Thermal generation
            P_ws = x[6:8]  # Wind scheduled power
            P_ps = x[8:10]  # Solar scheduled power
            
            # Calculate thermal cost
            thermal_cost = self.thermal_cost(P_g)
            
            # Calculate emission cost
            emission_cost = self.emission_cost(P_g)
            
            # Calculate expected costs for wind and solar
            (direct_cost_wind, reserve_cost_wind, penalty_cost_wind,
             direct_cost_solar, reserve_cost_solar, penalty_cost_solar,
             E_P_wav, E_P_pav) = self.calculate_expected_costs(P_ws, P_ps)
            
            # Total cost
            total_cost = (thermal_cost + emission_cost + direct_cost_wind + reserve_cost_wind + 
                         penalty_cost_wind + direct_cost_solar + reserve_cost_solar + penalty_cost_solar)
            
            # Add constraint violation penalty
            penalty = self.constraint_violation(x)
            
            # Add power balance penalty
            balance_penalty = self.power_balance_constraint(P_g, P_ws, P_ps, E_P_wav, E_P_pav)
            
            return total_cost + 100 * penalty + 100 * balance_penalty
    
    # Main execution
    if __name__ == "__main__":
        # Problem setup
        problem_dim = 16  # 6 thermal, 2 wind, 2 solar, 6 generator voltages
        lb = np.array([50, 20, 15, 10, 10, 12,  # Thermal min
                       0, 0,  # Wind min
                       0, 0,  # Solar min
                       0.95, 0.95, 0.95, 0.95, 0.95, 0.95])  # Voltage min
        
        ub = np.array([140, 80, 50, 35, 30, 40,  # Thermal max
                       75, 60,  # Wind max
                       35, 50,  # Solar max
                       1.1, 1.1, 1.1, 1.1, 1.1, 1.1])  # Voltage max
        
        # Initialize OPF problem
        opf = StochasticOPF()
        
        # Run Case 1: Without carbon tax
        print("Running Case 1: Minimize total cost without carbon tax")
        ipoa_case1 = IPOA(n_pop=30, n_iter=100, problem_dim=problem_dim, 
                          lb=lb, ub=ub, objective_func=opf.objective_function_case1)
        
        start_time = time.time()
        best_solution_case1, best_fitness_case1, history_case1 = ipoa_case1.optimize()
        end_time = time.time()
        
        print(f"Case 1 completed in {end_time - start_time:.2f} seconds")
        print(f"Best solution: {best_solution_case1}")
        print(f"Best fitness: {best_fitness_case1}")
        
        # Run Case 2: With carbon tax
        print("\nRunning Case 2: Minimize total cost with carbon tax")
        ipoa_case2 = IPOA(n_pop=30, n_iter=100, problem_dim=problem_dim, 
                          lb=lb, ub=ub, objective_func=opf.objective_function_case2)
        
        start_time = time.time()
        best_solution_case2, best_fitness_case2, history_case2 = ipoa_case2.optimize()
        end_time = time.time()
        
        print(f"Case 2 completed in {end_time - start_time:.2f} seconds")
        print(f"Best solution: {best_solution_case2}")
        print(f"Best fitness: {best_fitness_case2}")
        
        # Plot convergence
        plt.figure(figsize=(10, 6))
        plt.plot(history_case1, label='Case 1: Without Carbon Tax')
        plt.plot(history_case2, label='Case 2: With Carbon Tax')
        plt.xlabel('Iteration')
        plt.ylabel('Best Fitness ($/h)')
        plt.title('Convergence of IPOA for Stochastic OPF')
        plt.legend()
        plt.grid(True)
        plt.savefig('ipoa_convergence.png')
        plt.show()
        
        # Print detailed results
        print("\n=== Detailed Results ===")
        print("Case 1 (Without Carbon Tax):")
        print(f"  Thermal generation: {best_solution_case1[0:6]}")
        print(f"  Wind scheduled: {best_solution_case1[6:8]}")
        print(f"  Solar scheduled: {best_solution_case1[8:10]}")
        print(f"  Generator voltages: {best_solution_case1[10:16]}")
        print(f"  Total cost: {best_fitness_case1} $/h")
        
        print("\nCase 2 (With Carbon Tax):")
        print(f"  Thermal generation: {best_solution_case2[0:6]}")
        print(f"  Wind scheduled: {best_solution_case2[6:8]}")
        print(f"  Solar scheduled: {best_solution_case2[8:10]}")
        print(f"  Generator voltages: {best_solution_case2[10:16]}")
        print(f"  Total cost: {best_fitness_case2} $/h")

    Related posts, You May like to read

    1. 7 Shocking Truths About Knowledge Distillation: The Good, The Bad, and The Breakthrough (SAKD)
    2. 7 Revolutionary Breakthroughs in Medical Image Translation (And 1 Fatal Flaw That Could Derail Your AI Model)
    3. DeepSPV: Revolutionizing 3D Spleen Volume Estimation from 2D Ultrasound with AI
    4. ACAM-KD: Adaptive and Cooperative Attention Masking for Knowledge Distillation
    5. GeoSAM2 3D Part Segmentation — Prompt-Controllable, Geometry-Aware Masks for Precision 3D Editing
    6. Probabilistic Smooth Attention for Deep Multiple Instance Learning in Medical Imaging
    7. A Knowledge Distillation-Based Approach to Enhance Transparency of Classifier Models
    8. Towards Trustworthy Breast Tumor Segmentation in Ultrasound Using AI Uncertainty
    9. Discrete Migratory Bird Optimizer with Deep Transfer Learning for Multi-Retinal Disease Detection

    Leave a Comment

    Your email address will not be published. Required fields are marked *

    Follow by Email
    Tiktok