Commit
·
ff9b6c0
1
Parent(s):
1d3caec
Update src/backend/optimization_algo.py
Browse files- src/backend/optimization_algo.py +60 -46
src/backend/optimization_algo.py
CHANGED
|
@@ -5,7 +5,8 @@ import streamlit as st
|
|
| 5 |
# import all functions from src.backend.chatbot
|
| 6 |
from src.backend.chatbot import *
|
| 7 |
|
| 8 |
-
|
|
|
|
| 9 |
# Define the compatibility matrix
|
| 10 |
compatibility_matrix = st.session_state.full_mat
|
| 11 |
# Define the list of plants
|
|
@@ -16,31 +17,30 @@ def genetic_algorithm_plants():
|
|
| 16 |
num_plant_beds = st.session_state.n_plant_beds
|
| 17 |
# 1 <= min_species_per_bed <= max_species_per_bed <= len(user_plants)
|
| 18 |
min_species_per_bed = st.session_state.min_species
|
| 19 |
-
# max_species_per_bed >= floor(length(user_plants)-(min_species_per_bed*num_plant_beds-1) & max_species_per_bed <= len(user_plants)
|
| 20 |
max_species_per_bed = st.session_state.max_species
|
| 21 |
|
| 22 |
-
|
| 23 |
# Genetic Algorithm parameters
|
| 24 |
population_size = st.session_state.population_size
|
| 25 |
num_generations = st.session_state.num_generations
|
| 26 |
tournament_size = st.session_state.tournament_size
|
| 27 |
crossover_rate = st.session_state.crossover_rate
|
| 28 |
mutation_rate = st.session_state.mutation_rate
|
| 29 |
-
seed_population_rate = st.session_state.seed_population_rate
|
| 30 |
-
|
| 31 |
|
| 32 |
-
def generate_initial_population():
|
| 33 |
population = []
|
| 34 |
|
| 35 |
# Add seed groupings to the population, validated and replaced as necessary
|
| 36 |
-
num_seeds = int(
|
|
|
|
|
|
|
| 37 |
# we generate just one seed grouping for this beta language model suggestion feature
|
| 38 |
-
seed_grouping = get_language_model_suggestions()
|
| 39 |
if seed_grouping != "no response yet":
|
| 40 |
valid_seed_grouping = validate_and_replace(seed_grouping)
|
| 41 |
population.append(valid_seed_grouping)
|
| 42 |
|
| 43 |
-
|
| 44 |
# Fill the rest of the population with random groupings, also validated and replaced
|
| 45 |
while len(population) < population_size:
|
| 46 |
random_grouping = generate_random_grouping()
|
|
@@ -49,7 +49,6 @@ def genetic_algorithm_plants():
|
|
| 49 |
|
| 50 |
return population
|
| 51 |
|
| 52 |
-
|
| 53 |
def generate_random_grouping():
|
| 54 |
random.shuffle(user_plants)
|
| 55 |
remaining_plants = user_plants.copy()
|
|
@@ -67,7 +66,9 @@ def genetic_algorithm_plants():
|
|
| 67 |
num_species_in_bed = plants_per_bed
|
| 68 |
|
| 69 |
# Ensure the bed size is within the min and max constraints
|
| 70 |
-
num_species_in_bed = max(
|
|
|
|
|
|
|
| 71 |
|
| 72 |
bed = remaining_plants[:num_species_in_bed]
|
| 73 |
remaining_plants = remaining_plants[num_species_in_bed:]
|
|
@@ -75,8 +76,6 @@ def genetic_algorithm_plants():
|
|
| 75 |
|
| 76 |
return grouping
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
# Perform crossover between two parents, preserving at least one occurrence of each plant
|
| 81 |
def crossover(parent1, parent2):
|
| 82 |
if random.random() < crossover_rate:
|
|
@@ -88,17 +87,21 @@ def genetic_algorithm_plants():
|
|
| 88 |
for plant in user_plants:
|
| 89 |
if all(plant not in bed for bed in child1):
|
| 90 |
# Find a bed with fewer species and add the missing plant
|
| 91 |
-
min_bed_index = min(
|
|
|
|
|
|
|
| 92 |
child1[min_bed_index].append(plant)
|
| 93 |
if all(plant not in bed for bed in child2):
|
| 94 |
# Find a bed with fewer species and add the missing plant
|
| 95 |
-
min_bed_index = min(
|
|
|
|
|
|
|
| 96 |
child2[min_bed_index].append(plant)
|
| 97 |
|
| 98 |
return child1, child2
|
| 99 |
else:
|
| 100 |
return parent1, parent2
|
| 101 |
-
|
| 102 |
# Perform mutation on an individual, ensuring no bed exceeds the maximum species constraint
|
| 103 |
def mutate(individual):
|
| 104 |
if random.random() < mutation_rate:
|
|
@@ -110,8 +113,12 @@ def genetic_algorithm_plants():
|
|
| 110 |
species_in_bed = random.sample(species_in_bed, max_species_per_bed)
|
| 111 |
|
| 112 |
# Add missing plants by performing swaps between current species and missing plants
|
| 113 |
-
missing_plants = [
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
for _ in range(num_missing_plants):
|
| 116 |
swap_species = random.choice(missing_plants)
|
| 117 |
missing_plants.remove(swap_species)
|
|
@@ -124,8 +131,12 @@ def genetic_algorithm_plants():
|
|
| 124 |
|
| 125 |
# Calculate the fitness score of the grouping
|
| 126 |
def calculate_fitness(grouping):
|
| 127 |
-
positive_reward_factor =
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
# Define penalties for not meeting constraints
|
| 131 |
penalty_for_exceeding_max = 500 # Adjust as needed
|
|
@@ -144,15 +155,16 @@ def genetic_algorithm_plants():
|
|
| 144 |
species2_index = plant_list.index(species2_name)
|
| 145 |
|
| 146 |
# Compatibility score between two species in the same bed
|
| 147 |
-
compatibility_score = compatibility_matrix[species1_index][
|
| 148 |
-
|
|
|
|
|
|
|
| 149 |
if compatibility_score > 0:
|
| 150 |
# Positive reward for compatible species
|
| 151 |
-
score += compatibility_score*positive_reward_factor
|
| 152 |
elif compatibility_score < 0:
|
| 153 |
# Negative penalty for incompatible species
|
| 154 |
-
score += compatibility_score*negative_penalty_factor
|
| 155 |
-
|
| 156 |
|
| 157 |
# Apply penalties for not meeting constraints
|
| 158 |
if len(bed) > max_species_per_bed:
|
|
@@ -164,7 +176,6 @@ def genetic_algorithm_plants():
|
|
| 164 |
|
| 165 |
return score
|
| 166 |
|
| 167 |
-
|
| 168 |
# Perform tournament selection
|
| 169 |
def tournament_selection(population):
|
| 170 |
selected = []
|
|
@@ -189,11 +200,14 @@ def genetic_algorithm_plants():
|
|
| 189 |
individual[bed_idx] = species_in_bed
|
| 190 |
adjusted_offspring.append(individual)
|
| 191 |
|
| 192 |
-
return
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
# Genetic Algorithm main function
|
| 195 |
-
def genetic_algorithm():
|
| 196 |
-
population = generate_initial_population()
|
| 197 |
|
| 198 |
for generation in range(num_generations):
|
| 199 |
print(f"Generation {generation + 1}")
|
|
@@ -213,7 +227,6 @@ def genetic_algorithm_plants():
|
|
| 213 |
# Validate and replace any missing plants in the new population
|
| 214 |
population = [validate_and_replace(grouping) for grouping in population]
|
| 215 |
|
| 216 |
-
|
| 217 |
best_grouping = max(population, key=calculate_fitness)
|
| 218 |
best_grouping = validate_and_replace(best_grouping)
|
| 219 |
best_fitness = calculate_fitness(best_grouping)
|
|
@@ -224,7 +237,7 @@ def genetic_algorithm_plants():
|
|
| 224 |
# st.write(f"Best Grouping: {best_grouping}")
|
| 225 |
# st.write(f"Fitness Score: {best_fitness}")
|
| 226 |
return best_grouping
|
| 227 |
-
|
| 228 |
# def validate_and_replace(grouping):
|
| 229 |
# print("Grouping structure before validation:", grouping)
|
| 230 |
# all_plants = set(user_plants)
|
|
@@ -250,18 +263,20 @@ def genetic_algorithm_plants():
|
|
| 250 |
# random_bed[random.randint(0, len(random_bed) - 1)] = missing_plant
|
| 251 |
|
| 252 |
# return grouping
|
| 253 |
-
|
| 254 |
############
|
| 255 |
############ experimental
|
| 256 |
|
| 257 |
def adjust_grouping(grouping):
|
| 258 |
-
|
| 259 |
plants_in_grouping = set(plant for bed in grouping for plant in bed)
|
| 260 |
missing_plants = set(user_plants) - plants_in_grouping
|
| 261 |
|
| 262 |
for missing_plant in missing_plants:
|
| 263 |
# Find a bed that can accommodate the missing plant without exceeding max_species_per_bed
|
| 264 |
-
suitable_bed = next(
|
|
|
|
|
|
|
| 265 |
if suitable_bed is not None:
|
| 266 |
suitable_bed.append(missing_plant)
|
| 267 |
else:
|
|
@@ -272,16 +287,18 @@ def genetic_algorithm_plants():
|
|
| 272 |
# Ensure min_species_per_bed and max_species_per_bed constraints
|
| 273 |
for bed in grouping:
|
| 274 |
while len(bed) < min_species_per_bed:
|
| 275 |
-
additional_plant = random.choice(
|
|
|
|
|
|
|
| 276 |
bed.append(additional_plant)
|
| 277 |
while len(bed) > max_species_per_bed:
|
| 278 |
bed.remove(random.choice(bed))
|
| 279 |
|
| 280 |
return grouping
|
| 281 |
-
|
| 282 |
def validate_and_replace(grouping):
|
| 283 |
best_grouping = None
|
| 284 |
-
best_fitness = float(
|
| 285 |
|
| 286 |
for _ in range(5): # Generate 5 different configurations
|
| 287 |
temp_grouping = [bed.copy() for bed in grouping]
|
|
@@ -294,16 +311,13 @@ def genetic_algorithm_plants():
|
|
| 294 |
|
| 295 |
return best_grouping
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
############
|
| 300 |
-
def get_language_model_suggestions():
|
| 301 |
-
#
|
| 302 |
-
|
| 303 |
-
st.session_state.seed_groupings = get_seed_groupings_from_LLM()
|
| 304 |
return st.session_state.seed_groupings
|
| 305 |
|
|
|
|
| 306 |
|
| 307 |
-
|
| 308 |
-
best_grouping
|
| 309 |
-
return best_grouping
|
|
|
|
| 5 |
# import all functions from src.backend.chatbot
|
| 6 |
from src.backend.chatbot import *
|
| 7 |
|
| 8 |
+
|
| 9 |
+
def genetic_algorithm_plants(model, demo_lite):
|
| 10 |
# Define the compatibility matrix
|
| 11 |
compatibility_matrix = st.session_state.full_mat
|
| 12 |
# Define the list of plants
|
|
|
|
| 17 |
num_plant_beds = st.session_state.n_plant_beds
|
| 18 |
# 1 <= min_species_per_bed <= max_species_per_bed <= len(user_plants)
|
| 19 |
min_species_per_bed = st.session_state.min_species
|
| 20 |
+
# max_species_per_bed >= floor(length(user_plants)-(min_species_per_bed*num_plant_beds-1) & max_species_per_bed <= len(user_plants)
|
| 21 |
max_species_per_bed = st.session_state.max_species
|
| 22 |
|
|
|
|
| 23 |
# Genetic Algorithm parameters
|
| 24 |
population_size = st.session_state.population_size
|
| 25 |
num_generations = st.session_state.num_generations
|
| 26 |
tournament_size = st.session_state.tournament_size
|
| 27 |
crossover_rate = st.session_state.crossover_rate
|
| 28 |
mutation_rate = st.session_state.mutation_rate
|
| 29 |
+
seed_population_rate = st.session_state.seed_population_rate
|
|
|
|
| 30 |
|
| 31 |
+
def generate_initial_population(model, demo_lite):
|
| 32 |
population = []
|
| 33 |
|
| 34 |
# Add seed groupings to the population, validated and replaced as necessary
|
| 35 |
+
num_seeds = int(
|
| 36 |
+
population_size * st.session_state.seed_population_rate
|
| 37 |
+
) # 10% of the population as seeds
|
| 38 |
# we generate just one seed grouping for this beta language model suggestion feature
|
| 39 |
+
seed_grouping = get_language_model_suggestions(model, demo_lite)
|
| 40 |
if seed_grouping != "no response yet":
|
| 41 |
valid_seed_grouping = validate_and_replace(seed_grouping)
|
| 42 |
population.append(valid_seed_grouping)
|
| 43 |
|
|
|
|
| 44 |
# Fill the rest of the population with random groupings, also validated and replaced
|
| 45 |
while len(population) < population_size:
|
| 46 |
random_grouping = generate_random_grouping()
|
|
|
|
| 49 |
|
| 50 |
return population
|
| 51 |
|
|
|
|
| 52 |
def generate_random_grouping():
|
| 53 |
random.shuffle(user_plants)
|
| 54 |
remaining_plants = user_plants.copy()
|
|
|
|
| 66 |
num_species_in_bed = plants_per_bed
|
| 67 |
|
| 68 |
# Ensure the bed size is within the min and max constraints
|
| 69 |
+
num_species_in_bed = max(
|
| 70 |
+
min_species_per_bed, min(num_species_in_bed, max_species_per_bed)
|
| 71 |
+
)
|
| 72 |
|
| 73 |
bed = remaining_plants[:num_species_in_bed]
|
| 74 |
remaining_plants = remaining_plants[num_species_in_bed:]
|
|
|
|
| 76 |
|
| 77 |
return grouping
|
| 78 |
|
|
|
|
|
|
|
| 79 |
# Perform crossover between two parents, preserving at least one occurrence of each plant
|
| 80 |
def crossover(parent1, parent2):
|
| 81 |
if random.random() < crossover_rate:
|
|
|
|
| 87 |
for plant in user_plants:
|
| 88 |
if all(plant not in bed for bed in child1):
|
| 89 |
# Find a bed with fewer species and add the missing plant
|
| 90 |
+
min_bed_index = min(
|
| 91 |
+
range(len(child1)), key=lambda i: len(child1[i])
|
| 92 |
+
)
|
| 93 |
child1[min_bed_index].append(plant)
|
| 94 |
if all(plant not in bed for bed in child2):
|
| 95 |
# Find a bed with fewer species and add the missing plant
|
| 96 |
+
min_bed_index = min(
|
| 97 |
+
range(len(child2)), key=lambda i: len(child2[i])
|
| 98 |
+
)
|
| 99 |
child2[min_bed_index].append(plant)
|
| 100 |
|
| 101 |
return child1, child2
|
| 102 |
else:
|
| 103 |
return parent1, parent2
|
| 104 |
+
|
| 105 |
# Perform mutation on an individual, ensuring no bed exceeds the maximum species constraint
|
| 106 |
def mutate(individual):
|
| 107 |
if random.random() < mutation_rate:
|
|
|
|
| 113 |
species_in_bed = random.sample(species_in_bed, max_species_per_bed)
|
| 114 |
|
| 115 |
# Add missing plants by performing swaps between current species and missing plants
|
| 116 |
+
missing_plants = [
|
| 117 |
+
plant for plant in user_plants if plant not in species_in_bed
|
| 118 |
+
]
|
| 119 |
+
num_missing_plants = min(
|
| 120 |
+
len(missing_plants), max_species_per_bed - len(species_in_bed)
|
| 121 |
+
)
|
| 122 |
for _ in range(num_missing_plants):
|
| 123 |
swap_species = random.choice(missing_plants)
|
| 124 |
missing_plants.remove(swap_species)
|
|
|
|
| 131 |
|
| 132 |
# Calculate the fitness score of the grouping
|
| 133 |
def calculate_fitness(grouping):
|
| 134 |
+
positive_reward_factor = (
|
| 135 |
+
1000 # Adjust this to increase the reward for compatible species
|
| 136 |
+
)
|
| 137 |
+
negative_penalty_factor = (
|
| 138 |
+
2000 # Adjust this to increase the penalty for incompatible species
|
| 139 |
+
)
|
| 140 |
|
| 141 |
# Define penalties for not meeting constraints
|
| 142 |
penalty_for_exceeding_max = 500 # Adjust as needed
|
|
|
|
| 155 |
species2_index = plant_list.index(species2_name)
|
| 156 |
|
| 157 |
# Compatibility score between two species in the same bed
|
| 158 |
+
compatibility_score = compatibility_matrix[species1_index][
|
| 159 |
+
species2_index
|
| 160 |
+
]
|
| 161 |
+
|
| 162 |
if compatibility_score > 0:
|
| 163 |
# Positive reward for compatible species
|
| 164 |
+
score += compatibility_score * positive_reward_factor
|
| 165 |
elif compatibility_score < 0:
|
| 166 |
# Negative penalty for incompatible species
|
| 167 |
+
score += compatibility_score * negative_penalty_factor
|
|
|
|
| 168 |
|
| 169 |
# Apply penalties for not meeting constraints
|
| 170 |
if len(bed) > max_species_per_bed:
|
|
|
|
| 176 |
|
| 177 |
return score
|
| 178 |
|
|
|
|
| 179 |
# Perform tournament selection
|
| 180 |
def tournament_selection(population):
|
| 181 |
selected = []
|
|
|
|
| 200 |
individual[bed_idx] = species_in_bed
|
| 201 |
adjusted_offspring.append(individual)
|
| 202 |
|
| 203 |
+
return (
|
| 204 |
+
sorted_population[: population_size - len(adjusted_offspring)]
|
| 205 |
+
+ adjusted_offspring
|
| 206 |
+
)
|
| 207 |
|
| 208 |
# Genetic Algorithm main function
|
| 209 |
+
def genetic_algorithm(model, demo_lite):
|
| 210 |
+
population = generate_initial_population(model, demo_lite)
|
| 211 |
|
| 212 |
for generation in range(num_generations):
|
| 213 |
print(f"Generation {generation + 1}")
|
|
|
|
| 227 |
# Validate and replace any missing plants in the new population
|
| 228 |
population = [validate_and_replace(grouping) for grouping in population]
|
| 229 |
|
|
|
|
| 230 |
best_grouping = max(population, key=calculate_fitness)
|
| 231 |
best_grouping = validate_and_replace(best_grouping)
|
| 232 |
best_fitness = calculate_fitness(best_grouping)
|
|
|
|
| 237 |
# st.write(f"Best Grouping: {best_grouping}")
|
| 238 |
# st.write(f"Fitness Score: {best_fitness}")
|
| 239 |
return best_grouping
|
| 240 |
+
|
| 241 |
# def validate_and_replace(grouping):
|
| 242 |
# print("Grouping structure before validation:", grouping)
|
| 243 |
# all_plants = set(user_plants)
|
|
|
|
| 263 |
# random_bed[random.randint(0, len(random_bed) - 1)] = missing_plant
|
| 264 |
|
| 265 |
# return grouping
|
| 266 |
+
|
| 267 |
############
|
| 268 |
############ experimental
|
| 269 |
|
| 270 |
def adjust_grouping(grouping):
|
| 271 |
+
# Determine the plants that are missing in the grouping
|
| 272 |
plants_in_grouping = set(plant for bed in grouping for plant in bed)
|
| 273 |
missing_plants = set(user_plants) - plants_in_grouping
|
| 274 |
|
| 275 |
for missing_plant in missing_plants:
|
| 276 |
# Find a bed that can accommodate the missing plant without exceeding max_species_per_bed
|
| 277 |
+
suitable_bed = next(
|
| 278 |
+
(bed for bed in grouping if len(bed) < max_species_per_bed), None
|
| 279 |
+
)
|
| 280 |
if suitable_bed is not None:
|
| 281 |
suitable_bed.append(missing_plant)
|
| 282 |
else:
|
|
|
|
| 287 |
# Ensure min_species_per_bed and max_species_per_bed constraints
|
| 288 |
for bed in grouping:
|
| 289 |
while len(bed) < min_species_per_bed:
|
| 290 |
+
additional_plant = random.choice(
|
| 291 |
+
[plant for plant in user_plants if plant not in bed]
|
| 292 |
+
)
|
| 293 |
bed.append(additional_plant)
|
| 294 |
while len(bed) > max_species_per_bed:
|
| 295 |
bed.remove(random.choice(bed))
|
| 296 |
|
| 297 |
return grouping
|
| 298 |
+
|
| 299 |
def validate_and_replace(grouping):
|
| 300 |
best_grouping = None
|
| 301 |
+
best_fitness = float("-inf")
|
| 302 |
|
| 303 |
for _ in range(5): # Generate 5 different configurations
|
| 304 |
temp_grouping = [bed.copy() for bed in grouping]
|
|
|
|
| 311 |
|
| 312 |
return best_grouping
|
| 313 |
|
|
|
|
|
|
|
| 314 |
############
|
| 315 |
+
def get_language_model_suggestions(model, demo_lite):
|
| 316 |
+
# This returns a list of seed groupings based on the compatibility matrix
|
| 317 |
+
st.session_state.seed_groupings = get_seed_groupings_from_LLM(model, demo_lite)
|
|
|
|
| 318 |
return st.session_state.seed_groupings
|
| 319 |
|
| 320 |
+
# Run the genetic algorithm
|
| 321 |
|
| 322 |
+
best_grouping = genetic_algorithm(model, demo_lite)
|
| 323 |
+
return best_grouping
|
|
|