|
|
import random |
|
|
import numpy as np |
|
|
import streamlit as st |
|
|
|
|
|
|
|
|
default_param_ranges = { |
|
|
'beef': (400, 600), |
|
|
'cornflour': (80, 120), |
|
|
'garlic': (2, 4), |
|
|
'cilantro': (10, 20), |
|
|
'salt': (0.5, 1.5), |
|
|
'pepper': (0.25, 0.75), |
|
|
'baking_powder': (0.25, 0.75) |
|
|
} |
|
|
|
|
|
|
|
|
param_explanations = { |
|
|
'beef': 'Amount of beef in grams.', |
|
|
'cornflour': 'Amount of cornflour in grams.', |
|
|
'garlic': 'Number of garlic cloves.', |
|
|
'cilantro': 'Amount of cilantro in grams.', |
|
|
'salt': 'Amount of salt in teaspoons.', |
|
|
'pepper': 'Amount of pepper in teaspoons.', |
|
|
'baking_powder': 'Amount of baking powder in teaspoons.' |
|
|
} |
|
|
|
|
|
def generate_recipe(param_ranges): |
|
|
return {k: random.uniform(v[0], v[1]) for k, v in param_ranges.items()} |
|
|
|
|
|
def simulate_mixing(recipe, mix_factor): |
|
|
beef = recipe['beef'] |
|
|
cornflour = recipe['cornflour'] |
|
|
|
|
|
|
|
|
|
|
|
if cornflour == 0: |
|
|
return 0 |
|
|
mix_factor = (beef / cornflour) + mix_factor |
|
|
|
|
|
|
|
|
if mix_factor > 1: |
|
|
return 1 |
|
|
|
|
|
return mix_factor |
|
|
|
|
|
def evaluate_squishiness(recipe): |
|
|
beef = recipe['beef'] |
|
|
cornflour = recipe['cornflour'] |
|
|
baking_powder = recipe['baking_powder'] |
|
|
squishiness = (beef / cornflour) + (baking_powder * 10) |
|
|
return squishiness |
|
|
|
|
|
def evaluate_meat_taste(recipe, taste_weights): |
|
|
beef = recipe['beef'] |
|
|
garlic = recipe['garlic'] |
|
|
cilantro = recipe['cilantro'] |
|
|
meat_taste = (beef * taste_weights['beef']) + (garlic * taste_weights['garlic']) + (cilantro * taste_weights['cilantro']) |
|
|
return meat_taste |
|
|
|
|
|
def evaluate_consistency(recipe, mix_factor): |
|
|
beef = recipe['beef'] |
|
|
cornflour = recipe['cornflour'] |
|
|
consistency = (beef / cornflour) * mix_factor |
|
|
return consistency |
|
|
|
|
|
def evaluate_fitness(recipe, weights, mix_factor, taste_weights): |
|
|
squishiness = evaluate_squishiness(recipe) |
|
|
meat_taste = evaluate_meat_taste(recipe, taste_weights) |
|
|
consistency = evaluate_consistency(recipe, mix_factor) |
|
|
fitness = (squishiness * weights['squishiness']) + (meat_taste * weights['meat_taste']) + (consistency * weights['consistency']) |
|
|
return fitness |
|
|
|
|
|
def genetic_algorithm(param_ranges, weights, pop_size=100, generations=50, mutation_rate=0.1, mix_factor=0.1, taste_weights=None): |
|
|
population = [generate_recipe(param_ranges) for _ in range(pop_size)] |
|
|
for gen in range(generations): |
|
|
fitness_scores = [evaluate_fitness(recipe, weights, mix_factor, taste_weights) for recipe in population] |
|
|
parents = select_parents(population, fitness_scores, pop_size // 2) |
|
|
offspring = [] |
|
|
for i in range(0, len(parents), 2): |
|
|
if i + 1 < len(parents): |
|
|
child = crossover(parents[i], parents[i + 1]) |
|
|
child = mutate(child, mutation_rate, param_ranges) |
|
|
offspring.append(child) |
|
|
population = parents + offspring |
|
|
best_recipe = select_parents(population, fitness_scores, 1)[0] |
|
|
return best_recipe |
|
|
|
|
|
def select_parents(population, fitness_scores, num_parents): |
|
|
parents_indices = np.argsort(fitness_scores)[-num_parents:] |
|
|
return [population[i] for i in parents_indices] |
|
|
|
|
|
def crossover(parent1, parent2): |
|
|
child = {} |
|
|
for param in parent1.keys(): |
|
|
child[param] = (parent1[param] + parent2[param]) / 2 |
|
|
return child |
|
|
|
|
|
def mutate(recipe, mutation_rate, param_ranges): |
|
|
for param in recipe.keys(): |
|
|
if random.random() < mutation_rate: |
|
|
recipe[param] += random.uniform(-0.1, 0.1) * recipe[param] |
|
|
recipe[param] = max(param_ranges[param][0], min(recipe[param], param_ranges[param][1])) |
|
|
return recipe |
|
|
|
|
|
|
|
|
st.title('🧆 Meatball Recipe Simulator') |
|
|
|
|
|
|
|
|
st.sidebar.header('Custom Parameter Ranges') |
|
|
param_ranges = {} |
|
|
for param, (low, high) in default_param_ranges.items(): |
|
|
st.sidebar.markdown(f'**{param.capitalize()}**') |
|
|
st.sidebar.write(param_explanations[param]) |
|
|
low_val = st.sidebar.number_input(f'{param.capitalize()} Min', value=low, key=f'{param}_min') |
|
|
high_val = st.sidebar.number_input(f'{param.capitalize()} Max', value=high, key=f'{param}_max') |
|
|
param_ranges[param] = (low_val, high_val) |
|
|
|
|
|
|
|
|
st.sidebar.header('Weights for Fitness Evaluation') |
|
|
weights = { |
|
|
'squishiness': st.sidebar.slider('Weight for Squishiness', 0.0, 1.0, 0.3), |
|
|
'meat_taste': st.sidebar.slider('Weight for Meat Taste', 0.0, 1.0, 0.4), |
|
|
'consistency': st.sidebar.slider('Weight for Consistency', 0.0, 1.0, 0.3) |
|
|
} |
|
|
|
|
|
|
|
|
st.sidebar.header('Weights for Meat Taste Evaluation') |
|
|
taste_weights = { |
|
|
'beef': st.sidebar.slider('Weight for Beef', 0.0, 1.0, 0.5), |
|
|
'garlic': st.sidebar.slider('Weight for Garlic', 0.0, 1.0, 0.3), |
|
|
'cilantro': st.sidebar.slider('Weight for Cilantro', 0.0, 1.0, 0.2) |
|
|
} |
|
|
|
|
|
|
|
|
pop_size = st.sidebar.slider('Population Size', 50, 200, 100) |
|
|
generations = st.sidebar.slider('Generations', 10, 100, 50) |
|
|
mutation_rate = st.sidebar.slider('Mutation Rate', 0.01, 0.2, 0.1) |
|
|
mix_factor = st.sidebar.slider('Mix Factor', 0.0, 1.0, 0.1) |
|
|
|
|
|
if st.button('Run Simulation'): |
|
|
with st.spinner('Running genetic algorithm...'): |
|
|
best_recipe = genetic_algorithm(param_ranges, weights, pop_size, generations, mutation_rate, mix_factor, taste_weights) |
|
|
st.subheader('Best Recipe') |
|
|
for ingredient, amount in best_recipe.items(): |
|
|
st.write(f'{ingredient.capitalize()}: {amount:.2f}') |
|
|
|
|
|
fitness = evaluate_fitness(best_recipe, weights, mix_factor, taste_weights) |
|
|
st.write(f'Fitness Score: {fitness:.2f}') |
|
|
|