Skip to content

Commit 608d2ca

Browse files
committed
Added 2 solution for 0 division problem, and another solution to negative total fitness. Also gave permission to negative prices. Upgraded visualization and plotting a little bit. Added random.seed for last touch to reproducability
1 parent 6e6de4d commit 608d2ca

4 files changed

Lines changed: 63 additions & 63 deletions

File tree

‎geneticAlgorithm.py‎

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@
77
c = 0 # Marginal cost
88
l = 10 # Length of binary string (chromosomes)
99
T = 1000 # Max iterations
10-
11-
# Student ID logic for probabilities
1210
Y = 1 # Second to last digit of ID
1311
Z = 3 # Last digit of ID
1412

15-
p_cross = 0.200 + (0.01 * Y) + (0.001 * Z)
16-
p_mut = 0.00200 + (0.0001 * Y) + (0.00001 * Z)
13+
p_cross_bench = 0.200 + (0.01 * Y) + (0.001 * Z)
14+
p_mut_bench = 0.00200 + (0.0001 * Y) + (0.00001 * Z)
1715

1816
# Theoretical Values
19-
Q_cournot_total = (n / (n + 1)) * (alpha - c)
20-
q_cournot_indiv = Q_cournot_total / n
17+
Q_nash_total = (n / (n + 1)) * (alpha - c)
18+
q_nash_indiv = Q_nash_total / n
2119

2220

2321
def BinaryToDecimal(binary_array):
@@ -29,14 +27,14 @@ def BinaryToDecimal(binary_array):
2927

3028
def BinaryToOutput(binary_array):
3129
x_it = BinaryToDecimal(binary_array)
32-
q_i = (alpha / n) * (x_it / (2**l)) ### I am not sure about this scaling
30+
q_i = alpha * (
31+
x_it / (2**l)
32+
) # Scale the integer [0, 1023] to output (Note: 2^10 = 1024. Max x_it is 1023.)
3333
return q_i
3434

3535

3636
def CalculatePrice(total_Q):
3737
price = alpha - total_Q
38-
if price < 0:
39-
price = 0
4038
return price
4139

4240

@@ -52,9 +50,14 @@ def Variance(values):
5250
return variance
5351

5452

55-
def run_simulation(p_cross, p_mut):
56-
Population = []
53+
def run_simulation(p_cross, p_mut, scenario_name):
54+
print(f"\n--- Running Scenario: {scenario_name} ---")
55+
print(f"p_cross: {p_cross:.5f}, p_mut: {p_mut:.5f}")
5756

57+
random.seed(42)
58+
np.random.seed(42)
59+
60+
Population = []
5861
for i in range(n):
5962
# Generate random binary string of length l
6063
genes = [random.randint(0, 1) for _ in range(l)]
@@ -65,57 +68,51 @@ def run_simulation(p_cross, p_mut):
6568
variance_history = []
6669

6770
for t in range(1, T + 1):
68-
6971
outputs_list = []
7072
total_Q = 0
7173

74+
# Calculate outputs for current population
7275
for firm in Population:
7376
q_i = BinaryToOutput(firm)
7477
outputs_list.append(q_i)
7578
total_Q += q_i
7679

7780
current_price = CalculatePrice(total_Q)
7881

79-
# Check for zero-profit trap: if price is 0, restart population
80-
if current_price <= 0:
81-
Population = []
82-
for i in range(n):
83-
genes = [random.randint(0, 1) for _ in range(l)]
84-
Population.append(genes)
85-
continue
86-
8782
# Store stats
8883
total_Q_history.append(total_Q)
8984
variance_history.append(Variance(outputs_list))
9085

91-
# A. RECOMBINATION
92-
# Calculate fitness scores (profits) for all firms
86+
# A. RECOMBINATION (Roulette Wheel Selection)
9387
fitness_scores = []
9488
for firm in Population:
9589
q_i = BinaryToOutput(firm)
9690
profit = CalculateProfit(q_i, current_price)
9791
fitness_scores.append(profit)
9892

99-
# Check if we can use fitness-based selection
93+
# Handle negative fitness for probability calculation
10094
min_fitness = min(fitness_scores)
101-
total_fitness = sum(fitness_scores)
95+
if min_fitness < 0:
96+
# Shift all fitness values to be positive
97+
offset = -min_fitness
98+
# Add a small epsilon to ensure non-zero probabilities if all are equal
99+
fitness_scores = [f + offset + 1e-6 for f in fitness_scores]
100+
# But this solution can left useless in a situation where all firms have zero fitness score
102101

103-
# If negative fitness or zero total fitness, restart population
104-
if min_fitness < 0 or total_fitness <= 0:
105-
Population = []
106-
for i in range(n):
107-
genes = [random.randint(0, 1) for _ in range(l)]
108-
Population.append(genes)
109-
continue
102+
total_fitness = sum(fitness_scores)
110103

111-
# Normalize to get probabilities
112-
probabilities = [f / total_fitness for f in fitness_scores]
104+
if total_fitness == 0:
105+
probabilities = [
106+
1.0 / n
107+
] * n # If all firms have zero fitness score, each firm has equal probability of being selected
108+
else:
109+
probabilities = [f / total_fitness for f in fitness_scores]
113110

114-
# Select firms based on their fitness probabilities
111+
# Create mating pool
115112
mating_pool = []
116-
for _ in range(n):
117-
selected_firm_idx = np.random.choice(len(Population), p=probabilities)
118-
mating_pool.append(Population[selected_firm_idx].copy())
113+
indices = np.random.choice(len(Population), size=n, p=probabilities)
114+
for idx in indices:
115+
mating_pool.append(Population[idx].copy())
119116

120117
# B. CROSSOVER
121118
offspring_pool = []
@@ -150,14 +147,14 @@ def run_simulation(p_cross, p_mut):
150147
q_parent = BinaryToOutput(Parent)
151148
q_child = BinaryToOutput(Child)
152149

153-
# Calculate Q_others
150+
# Calculate Q_others (aggregate of all others in current generation)
154151
Q_others = total_Q - q_parent
155152

156153
# Parent Profit (Actual)
157-
# Uses the actual market price that occurred
158154
profit_parent = CalculateProfit(q_parent, current_price)
159155

160-
# Recalculate price if Child were in the market
156+
# Child Profit (Hypothetical)
157+
# Evaluate Child against the *current* behavior of others
161158
new_total_Q = Q_others + q_child
162159
new_price = CalculatePrice(new_total_Q)
163160
profit_child = CalculateProfit(q_child, new_price)
@@ -169,43 +166,46 @@ def run_simulation(p_cross, p_mut):
169166

170167
Population = Next_Generation
171168

172-
# ---------------------------------------------------------
173-
# RESULTS
174-
# ---------------------------------------------------------
175-
print(f"\nSimulation completed!")
176-
print(f"Final Total Q: {total_Q_history[-1]}")
177-
print(f"Target Cournot Total Q: {Q_cournot_total:.2f}")
178-
print(f"Final Variance: {variance_history[-1]}")
169+
# RESULTS & PLOTTING
170+
print(f"Simulation completed!")
171+
print(f"Final Total Q: {total_Q_history[-1]:.2f}")
172+
print(f"Target Nash Total Q: {Q_nash_total:.2f}")
173+
print(f"Final Variance: {variance_history[-1]:.2f}")
179174

175+
# Plotting
180176
plt.figure(figsize=(12, 5))
181177

178+
# Plot 1: Total Quantity
182179
plt.subplot(1, 2, 1)
183-
plt.plot(total_Q_history, label="Actual Total Q")
184-
plt.axhline(y=Q_cournot_total, color="r", linestyle="--", label="Cournot Eq")
180+
plt.plot(total_Q_history, label="Actual Total Q", color="blue")
181+
plt.axhline(y=Q_nash_total, color="r", linestyle="--", label="Nash Equilibrium")
185182
plt.xlabel("Iteration")
186183
plt.ylabel("Total Quantity (Q)")
187-
plt.title("Total Quantity Convergence")
184+
plt.title(f"Industry Output Convergence\n({scenario_name})")
188185
plt.legend()
189-
plt.grid(True)
186+
plt.grid(True, alpha=0.3)
190187

188+
# Plot 2: Variance
191189
plt.subplot(1, 2, 2)
192-
plt.plot(variance_history)
190+
plt.plot(variance_history, color="green")
193191
plt.xlabel("Iteration")
194-
plt.ylabel("Variance of Outputs")
195-
plt.title("Variance Over Time")
196-
plt.grid(True)
192+
plt.ylabel("Variance")
193+
plt.title(f"Population Variance\n({scenario_name})")
194+
plt.grid(True, alpha=0.3)
197195

198196
plt.tight_layout()
199-
plt.savefig("genetic_algorithm_results.png")
200-
plt.show()
197+
filename = f"results_{scenario_name.replace(' ', '_')}.png"
198+
plt.savefig(f"results/{filename}")
199+
print(f"Saved plot to results/{filename}")
200+
plt.close()
201201

202202

203203
if __name__ == "__main__":
204-
# Scenario 1: Benchmark
205-
run_simulation(p_cross, p_mut)
204+
# (ii) Benchmark Scenario
205+
run_simulation(p_cross_bench, p_mut_bench, "Benchmark")
206206

207-
# Scenario 2: High Crossover (+0.250)
208-
run_simulation(p_cross + 0.250, p_mut)
207+
# (iii) High Crossover (+0.250)
208+
run_simulation(p_cross_bench + 0.250, p_mut_bench, "High_Crossover")
209209

210-
# Scenario 3: High Mutation (+0.00250)
211-
run_simulation(p_cross, p_mut + 0.00250)
210+
# (iv) High Mutation (+0.00250)
211+
run_simulation(p_cross_bench, p_mut_bench + 0.00250, "High_Mutation")

‎results/results_Benchmark.png‎

34.5 KB
Loading

‎results/results_High_Crossover.png‎

36.7 KB
Loading

‎results/results_High_Mutation.png‎

34.7 KB
Loading

0 commit comments

Comments
 (0)