Spaces:
Runtime error
Runtime error
from src.cocktails.utilities.ingredients_utilities import get_ingredients_info, format_ingredients, extract_ingredients, ingredients_per_type, bubble_ingredients | |
import numpy as np | |
from src.cocktails.utilities.other_scrubbing_utilities import print_recipe | |
from src.cocktails.utilities.cocktail_utilities import get_cocktail_rep, get_profile, get_bunch_of_rep_keys | |
from src.cocktails.utilities.glass_and_volume_utilities import glass_volume | |
from src.cocktails.representation_learning.run import get_model | |
from src.cocktails.pipeline.get_cocktail2affective_cluster import get_cocktail2affective_cluster | |
from src.cocktails.config import COCKTAILS_CSV_DATA, FULL_COCKTAIL_REP_PATH, REPO_PATH, COCKTAIL_REP_CHKPT_PATH, RECIPE2FEATURES_PATH | |
from src.cocktails.representation_learning.run_without_vae import get_model | |
from src.cocktails.utilities.cocktail_category_detection_utilities import find_cocktail_sub_category | |
import pandas as pd | |
import torch | |
import time | |
device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
density_ingredients = np.loadtxt(COCKTAIL_REP_CHKPT_PATH + 'density_ingredients.txt') | |
max_ingredients, ingredient_list, ind_alcohol = get_ingredients_info() | |
min_ingredients = 2 | |
factor_max = 1.2 # generated recipes can go up to 1.2 times the max quantity of the ingredient found in the dataset | |
prep_model = get_model(RECIPE2FEATURES_PATH + 'multi_predictor/')[0] | |
all_rep_path = FULL_COCKTAIL_REP_PATH | |
all_reps = np.loadtxt(all_rep_path) | |
experiment_dir = REPO_PATH + '/experiments/cocktails/' | |
rep_keys = get_bunch_of_rep_keys()['custom'] | |
dict_weights_mse_computation = {'end volume': .1, 'end sour': 2, 'end sweet': 2, 'end booze': 4, 'end bitter': 2, 'end fruit': 1, 'end herb': 1, | |
'end complex': 1, 'end spicy': 5, 'end oaky': 1, 'end fizzy': 10, 'end colorful': 1, 'end eggy': 10} | |
assert sorted(dict_weights_mse_computation.keys()) == sorted(rep_keys) | |
weights_mse_computation = np.array([dict_weights_mse_computation[k] for k in rep_keys]) | |
weights_mse_computation /= weights_mse_computation.sum() | |
data = pd.read_csv(COCKTAILS_CSV_DATA) | |
preparation_list = sorted(set(data['category'])) | |
glasses_list = sorted(set(data['glass'])) | |
weights_perf_n_ing = {2:0.71, 3:0.81, 4:0.93, 5:1., 6:1.03, 7:1.08, 8:1.05} | |
# weights_perf_n_ing = {2:0.75, 3:0.8, 4:0.95, 5:1.05, 6:1.05, 7:1.05, 8:1.05} | |
min_ingredients_quantities_when_present = np.loadtxt(COCKTAIL_REP_CHKPT_PATH +'ingredients_min_quantities_when_present.txt') | |
min_ingredients_quantities = np.loadtxt(COCKTAIL_REP_CHKPT_PATH +'ingredients_min_quantities.txt') | |
max_ingredients_quantities = np.loadtxt(COCKTAIL_REP_CHKPT_PATH + 'ingredients_max_quantities.txt') | |
min_cocktail_rep, max_cocktail_rep = np.loadtxt(COCKTAIL_REP_CHKPT_PATH +'cocktail_minmax_dim13_customkeys.txt') | |
distrib_nb_ings_2_8 = np.loadtxt(COCKTAIL_REP_CHKPT_PATH + 'distrib_nb_ing.txt')[2:] | |
def normalize_cocktail(cocktail_rep): | |
return ((cocktail_rep - min_cocktail_rep) / (max_cocktail_rep - min_cocktail_rep) - 0.5) * 2 | |
def denormalize_cocktail(cocktail_rep): | |
return (cocktail_rep / 2 + 0.5) * (max_cocktail_rep - min_cocktail_rep) + min_cocktail_rep | |
def normalize_ingredient_q_rep(ingredients_q): | |
return (ingredients_q - min_ingredients_quantities_when_present) / (max_ingredients_quantities * factor_max - min_ingredients_quantities_when_present) | |
COCKTAIL_REPS = normalize_cocktail(np.array([data[k] for k in rep_keys]).transpose()) | |
assert np.abs(COCKTAIL_REPS - all_reps).sum() < 1e-8 | |
cocktail2affective_cluster = get_cocktail2affective_cluster() | |
original_affective_keys = get_bunch_of_rep_keys()['affective'] | |
def sigmoid(x, shift, beta): | |
return (1 / (1 + np.exp(-(x + shift) * beta)) - 0.5) * 2 | |
def get_normalized_affective_cocktail_rep_from_normalized_cocktail_rep(cocktail_rep): | |
indexes = np.array([rep_keys.index(key) for key in original_affective_keys]) | |
cocktail_rep = cocktail_rep[indexes] | |
cocktail_rep[0] = sigmoid(cocktail_rep[0], shift=0.05, beta=4) | |
cocktail_rep[1] = sigmoid(cocktail_rep[1], shift=0.3, beta=5) | |
cocktail_rep[2] = sigmoid(cocktail_rep[2], shift=0.15, beta=3) | |
cocktail_rep[3] = sigmoid(cocktail_rep[3], shift=0.9, beta=20) | |
cocktail_rep[4] = sigmoid(cocktail_rep[4], shift=0, beta=4) | |
cocktail_rep[5] = sigmoid(cocktail_rep[5], shift=0.2, beta=3) | |
cocktail_rep[6] = sigmoid(cocktail_rep[6], shift=0.5, beta=5) | |
cocktail_rep[7] = sigmoid(cocktail_rep[7], shift=0.2, beta=6) | |
return cocktail_rep | |
class IndividualCocktail(): | |
def __init__(self, pop_params, target, target_affective_cluster, genes_presence=None, genes_quantity=None, | |
compute_perf=True, known_target_dict=None, run_hard_check=False): | |
self.pop_params = pop_params | |
self.n_genes = len(ingredient_list) | |
self.max_ingredients = max_ingredients | |
self.min_ingredients = min_ingredients | |
self.mutation_params = pop_params['mutation_params'] | |
self.dist = pop_params['dist'] | |
self.target = target | |
self.is_known = known_target_dict is not None | |
self.known_target_dict = known_target_dict | |
self.perf = None | |
self.cocktail_rep = None | |
self.affective_cluster = None | |
self.target_affective_cluster = target_affective_cluster | |
self.ing_list = np.array(ingredient_list) | |
self.ing_set = set(ingredient_list) | |
self.ing_ids_per_cat = dict(bubbles=set(self.get_ingredients_ids_from_list(bubble_ingredients)), | |
liquor=set(self.get_ingredients_ids_from_list(ingredients_per_type['liquor'])), | |
liqueur=set(self.get_ingredients_ids_from_list(ingredients_per_type['liqueur'])), | |
citrus=set(self.get_ingredients_ids_from_list(ingredients_per_type['acid'] + ['orange juice'])), | |
alcohol=set(ind_alcohol), | |
sweeteners=set(self.get_ingredients_ids_from_list(ingredients_per_type['sweeteners'])), | |
vermouth=set(self.get_ingredients_ids_from_list(ingredients_per_type['vermouth'])), | |
bitters=set(self.get_ingredients_ids_from_list(ingredients_per_type['bitters'])), | |
juice=set(self.get_ingredients_ids_from_list(ingredients_per_type['juice'])), | |
acid=set(self.get_ingredients_ids_from_list(ingredients_per_type['acid'])), | |
egg=set(self.get_ingredients_ids_from_list(['egg'])) | |
) | |
if genes_presence is not None: | |
assert len(genes_presence) == self.n_genes | |
assert len(genes_quantity) == self.n_genes | |
self.genes_presence = genes_presence | |
self.genes_quantity = genes_quantity | |
if compute_perf: | |
self.compute_cocktail_rep() | |
self.compute_perf() | |
else: | |
self.sample_initial_genes() | |
self.compute_cocktail_rep() | |
# self.make_recipe_fit_the_glass() | |
self.compute_perf() | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Sample initial genes with smart rules | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def sample_initial_genes(self): | |
# rules: | |
# - between min_ingredients and max_ingredients | |
# - at most one type of bubbles | |
# - at least one alcohol | |
# - no egg without lime or lemon | |
# - at most two liqueurs | |
# - at most three liquors | |
# - at most two sweetener | |
self.genes_quantity = np.random.uniform(0, 1, size=self.n_genes) # holds quantities for each ingredient | |
n_ingredients = np.random.choice(np.arange(min_ingredients, max_ingredients + 1), p=distrib_nb_ings_2_8) | |
self.genes_presence = np.zeros(self.n_genes) | |
# add one alchohol | |
self.genes_presence[np.random.choice(ind_alcohol)] = 1 | |
while self.get_ing_count() < n_ingredients: | |
candidate_ids = self.get_candidate_ingredients_ids(self.genes_presence) | |
probas = density_ingredients[candidate_ids] / np.sum(density_ingredients[candidate_ids]) | |
self.genes_presence[np.random.choice(candidate_ids, p=probas)] = 1 | |
def get_candidate_ingredients_ids(self, genes_presence): | |
candidates = set(np.argwhere(genes_presence==0).flatten()) | |
present_ids = set(np.argwhere(genes_presence==1).flatten()) | |
if self.count_in_genes(present_ids, 'bubbles') >= 1: # at most one type of bubbles | |
candidates = candidates - self.ing_ids_per_cat['bubbles'] | |
if self.count_in_genes(present_ids, 'liquor') >= 3: # at most three liquors | |
candidates = candidates - self.ing_ids_per_cat['liquor'] | |
if self.count_in_genes(present_ids, 'liqueur') >= 2: # at most two liqueurs | |
candidates = candidates - self.ing_ids_per_cat['liqueur'] | |
if self.count_in_genes(present_ids, 'sweeteners') >= 2: # at most two sweetener | |
candidates = candidates - self.ing_ids_per_cat['sweeteners'] | |
if self.count_in_genes(present_ids, 'citrus') == 0: # no egg without lime or lemon | |
candidates = candidates - self.ing_ids_per_cat['egg'] | |
return np.array(sorted(candidates)) | |
def count_in_genes(self, present_ids, keyword): | |
if keyword == 'citrus': return len(present_ids & self.ing_ids_per_cat['citrus']) | |
elif keyword == 'bubbles': return len(present_ids & self.ing_ids_per_cat['bubbles']) | |
elif keyword == 'liquor': return len(present_ids & self.ing_ids_per_cat['liquor']) | |
elif keyword == 'liqueur': return len(present_ids & self.ing_ids_per_cat['liqueur']) | |
elif keyword == 'alcohol': return len(present_ids & self.ing_ids_per_cat['alcohol']) | |
elif keyword == 'sweeteners': return len(present_ids & self.ing_ids_per_cat['sweeteners']) | |
else: raise ValueError | |
def get_ingredients_ids_from_list(self, ing_list): | |
return [ingredient_list.index(ing) for ing in ing_list] | |
def get_ing_count(self): | |
return np.sum(self.genes_presence) | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Compute cocktail representations | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def get_absent_ing(self): | |
return np.argwhere(self.genes_presence==0).flatten() | |
def get_present_ing(self): | |
return np.argwhere(self.genes_presence==1).flatten() | |
def get_ingredient_quantities(self): | |
# unnormalize quantities to get real ones | |
return (self.genes_quantity * (max_ingredients_quantities * factor_max - min_ingredients_quantities_when_present) + min_ingredients_quantities_when_present) * self.genes_presence | |
def get_ing_and_q_from_genes(self): | |
present_ings = self.get_present_ing() | |
ing_quantities = self.get_ingredient_quantities() | |
ingredients, quantities = [], [] | |
for i_ing in present_ings: | |
ingredients.append(ingredient_list[i_ing]) | |
quantities.append(ing_quantities[i_ing]) | |
return ingredients, quantities, ing_quantities | |
def compute_cocktail_rep(self): | |
# only call when genes have changes | |
init_time = time.time() | |
ingredients, quantities, ing_quantities = self.get_ing_and_q_from_genes() | |
# compute cocktail category | |
self.category = find_cocktail_sub_category(ingredients, quantities)[0] | |
# print(f't1: {time.time() - init_time}') | |
init_time = time.time() | |
self.prep_type = self.get_prep_type(ing_quantities) | |
# print(f't2: {time.time() - init_time}') | |
init_time = time.time() | |
cocktail_rep, self.end_volume, self.end_alcohol = get_cocktail_rep(self.prep_type, ingredients, quantities, keys=rep_keys[1:]) # volume is added later | |
# print(f't3: {time.time() - init_time}') | |
init_time = time.time() | |
self.cocktail_rep = normalize_cocktail(cocktail_rep) | |
# print(f't4: {time.time() - init_time}') | |
init_time = time.time() | |
self.glass = self.get_glass_type(ing_quantities) | |
# print(f't5: {time.time() - init_time}') | |
init_time = time.time() | |
if self.is_known: | |
assert np.abs(self.cocktail_rep - self.target).sum() < 1e-6 | |
return self.cocktail_rep | |
def get_prep_type(self, quantities=None): | |
if self.is_known: return self.known_target_dict['prep_type'] | |
else: | |
if quantities is None: | |
quantities = self.get_ingredient_quantities() | |
if quantities[ingredient_list.index('egg')] > 0: | |
prep_cat = 'egg_shaken' | |
elif self.category in ['spirit_forward', 'simple_sour_with_juice', 'julep', 'duo', 'ancestral', 'complex_sour_with_juice']: | |
# use hard coded rules for most obvious cases determined with the correlations_glass_cat_prep_script | |
if self.category in ['ancestral', 'spirit_forward', 'duo']: | |
prep_cat = 'stirred' | |
elif self.category in ['complex_sour_with_juice', 'julep', 'simple_sour_with_juice']: | |
prep_cat = 'shaken' | |
else: | |
raise ValueError | |
else: | |
output = prep_model(quantities, aux_str='prep_type').flatten() | |
output[preparation_list.index('egg_shaken')] = -np.inf | |
prep_cat = preparation_list[np.argmax(output)] | |
return prep_cat | |
def get_glass_type(self, quantities=None): | |
if self.is_known: return self.known_target_dict['glass'] | |
else: | |
if self.category in ['collins', 'complex_highball', 'simple_highball', 'champagne_cocktail', 'complex_sour']: | |
# use hard coded rules for most obvious cases determined with the correlations_glass_cat_prep_script | |
if self.category in ['collins', 'complex_highball', 'simple_highball']: | |
glass = 'collins' | |
elif self.category in ['champagne_cocktail', 'complex_sour']: | |
glass = 'coupe' | |
else: | |
if quantities is None: | |
quantities = self.get_ingredient_quantities() | |
output = prep_model(quantities, aux_str='glasses').flatten() | |
glass = glasses_list[np.argmax(output)] | |
return glass | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Adapt recipe to fit the glass | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def is_too_large_for_glass(self): | |
return self.end_volume > glass_volume[self.glass] * 0.80 | |
def is_too_small_for_glass(self): | |
return self.end_volume < glass_volume[self.glass] * 0.3 | |
def scale_ing_quantities(self, present_ings, factor): | |
qs = self.get_ingredient_quantities().copy() | |
qs[present_ings] *= factor | |
self.set_genes_from_quantities(present_ings, qs) | |
def set_genes_from_quantities(self, present_ings, quantities): | |
genes_quantity = np.clip((quantities - min_ingredients_quantities_when_present) / | |
(factor_max * max_ingredients_quantities - min_ingredients_quantities_when_present), 0, 1) | |
self.genes_quantity[present_ings] = genes_quantity[present_ings] | |
def make_recipe_fit_the_glass(self): | |
# check if citrus, if not remove egg | |
present_ids = np.argwhere(self.genes_presence == 1).flatten() | |
ing_list = self.ing_list[present_ids] | |
present_ids = set(present_ids) | |
if self.count_in_genes(present_ids, 'citrus') == 0 and 'egg' in ing_list: | |
if self.genes_presence.sum() > 2: | |
i_egg = ingredient_list.index('egg') | |
self.genes_presence[i_egg] = 0. | |
self.compute_cocktail_rep() | |
i_trial = 0 | |
present_ings = self.get_present_ing() | |
while self.is_too_large_for_glass(): | |
i_trial += 1 | |
end_volume = self.end_volume | |
desired_volume = glass_volume[self.glass] * 0.80 | |
ratio = desired_volume / end_volume | |
self.scale_ing_quantities(present_ings, factor=ratio) | |
self.compute_cocktail_rep() | |
if end_volume == self.end_volume: break | |
if i_trial == 10: break | |
while self.is_too_small_for_glass(): | |
i_trial += 1 | |
end_volume = self.end_volume | |
desired_volume = glass_volume[self.glass] * 0.80 | |
ratio = desired_volume / end_volume | |
self.scale_ing_quantities(present_ings, factor=ratio) | |
self.compute_cocktail_rep() | |
if end_volume == self.end_volume: break | |
if i_trial == 10: break | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Compute performance | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def passes_checks(self): | |
present_ids = np.argwhere(self.genes_presence==1).flatten() | |
# ing_list = self.ing_list[present_ids] | |
present_ids = set(present_ids) | |
if len(present_ids) < 2 or len(present_ids) > 8: return False | |
# if self.is_too_large_for_glass(): return False | |
# if self.is_too_small_for_glass(): return False | |
if self.end_alcohol < 0.05 or self.end_alcohol > 0.31: return False | |
if self.count_in_genes(present_ids, 'sweeteners') > 2: return False | |
if self.count_in_genes(present_ids, 'liqueur') > 2: return False | |
if self.count_in_genes(present_ids, 'liquor') > 3: return False | |
# if self.count_in_genes(present_ids, 'citrus') == 0 and 'egg' in ing_list: return False | |
if self.count_in_genes(present_ids, 'bubbles') > 1: return False | |
else: return True | |
def get_affective_cluster(self): | |
cocktail_rep_affective = get_normalized_affective_cocktail_rep_from_normalized_cocktail_rep(self.cocktail_rep) | |
self.affective_cluster = cocktail2affective_cluster(cocktail_rep_affective)[0] | |
return self.affective_cluster | |
def does_affective_cluster_match(self): | |
return True#self.get_affective_cluster() == self.target_affective_cluster | |
def compute_perf(self): | |
if not self.passes_checks(): self.perf = -100 | |
else: | |
if self.dist == 'mse': | |
# self.perf = - np.sqrt(((self.cocktail_rep - self.target)**2).mean()) | |
self.perf = - np.sqrt(np.dot((self.cocktail_rep - self.target)**2, weights_mse_computation)) | |
self.perf *= weights_perf_n_ing[int(self.genes_presence.sum())] | |
if not self.does_affective_cluster_match(): | |
self.perf *= 2 | |
else: raise NotImplemented | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Mutations and crossover | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def get_child(self): | |
time_dict = dict() | |
init_time = time.time() | |
child = IndividualCocktail(pop_params=self.pop_params, target_affective_cluster=self.target_affective_cluster, | |
target=self.target, genes_presence=self.genes_presence.copy(), | |
genes_quantity=self.genes_quantity.copy(), compute_perf=False) | |
time_dict[' asexual child creation'] = [time.time() - init_time] | |
init_time = time.time() | |
this_time_dict = child.mutate() | |
time_dict = self.update_time_dict(time_dict, this_time_dict) | |
time_dict[' asexual child mutation'] = [time.time() - init_time] | |
return child, time_dict | |
def get_child_with(self, other_parent): | |
time_dict = dict() | |
init_time = time.time() | |
new_genes_presence = np.zeros(self.n_genes) | |
present_ing = self.get_present_ing() | |
other_present_ing = other_parent.get_present_ing() | |
new_genes_quantity = np.random.uniform(0, 1, size=self.n_genes) | |
shared_ingredients = sorted(set(present_ing) & set(other_present_ing)) | |
unique_ingredients_one = sorted(set(present_ing) - set(other_present_ing)) | |
unique_ingredients_two = sorted(set(other_present_ing) - set(present_ing)) | |
for i in shared_ingredients: | |
new_genes_presence[i] = 1 | |
new_genes_quantity[i] = (self.genes_quantity[i] + other_parent.genes_quantity[i]) / 2 | |
time_dict[' crossover child creation'] = [time.time() - init_time] | |
init_time = time.time() | |
# add one alcohol if none present | |
if len(set(np.argwhere(new_genes_presence==1).flatten()).intersection(ind_alcohol)) == 0: | |
new_genes_presence[np.random.choice(ind_alcohol)] = 1 | |
# up to here, we respect the constraints (assuming both parents do). | |
candidate_genes = np.array(unique_ingredients_one + unique_ingredients_two) | |
candidate_quantities = np.array([self.genes_quantity[i] for i in unique_ingredients_one] + [other_parent.genes_quantity[i] for i in unique_ingredients_two]) | |
indexes = np.arange(len(candidate_genes)) | |
np.random.shuffle(indexes) | |
candidate_genes = candidate_genes[indexes] | |
candidate_quantities = candidate_quantities[indexes] | |
time_dict[' crossover prepare selection'] = [time.time() - init_time] | |
init_time = time.time() | |
# now let's try to add each of them while respecting the constraints | |
for i in range(len(indexes)): | |
if np.random.rand() < 0.5 or np.sum(new_genes_presence) < self.min_ingredients: # only try to add one every two ingredient | |
ing_id = candidate_genes[i] | |
q = candidate_quantities[i] | |
new_genes_presence[ing_id] = 1 | |
new_genes_quantity[ing_id] = q | |
if np.sum(new_genes_presence) == self.max_ingredients: | |
break | |
time_dict[' crossover do selection'] = [time.time() - init_time] | |
init_time = time.time() | |
# create new child | |
child = IndividualCocktail(pop_params=self.pop_params, target_affective_cluster=self.target_affective_cluster, target=self.target, | |
genes_presence=new_genes_presence.copy(), genes_quantity=new_genes_quantity.copy(), compute_perf=False) | |
time_dict[' crossover create child'] = [time.time() - init_time] | |
init_time = time.time() | |
this_time_dict = child.mutate() | |
time_dict = self.update_time_dict(time_dict, this_time_dict) | |
time_dict[' crossover child mutation'] = [time.time() - init_time] | |
init_time = time.time() | |
return child, time_dict | |
def mutate(self): | |
# self.print_recipe() | |
time_dict = dict() | |
# remove an ingredient | |
init_time = time.time() | |
present_ids = set(np.argwhere(self.genes_presence==1).flatten()) | |
if np.random.rand() < self.mutation_params['p_remove_ing']: | |
if self.get_ing_count() > self.min_ingredients: | |
candidate_ings = self.get_present_ing() | |
if self.count_in_genes(present_ids, 'alcohol') == 1: # make sure we keep at least one liquor | |
candidate_ings = np.array(sorted(set(candidate_ings) - set(ind_alcohol))) | |
index_to_remove = np.random.choice(candidate_ings) | |
self.genes_presence[index_to_remove] = 0 | |
time_dict[' mutation remove ing'] = [time.time() - init_time] | |
init_time = time.time() | |
# add an ingredient | |
if np.random.rand() < self.mutation_params['p_add_ing']: | |
if self.get_ing_count() < self.max_ingredients: | |
candidate_ings = self.get_candidate_ingredients_ids(self.genes_presence.copy()) | |
index_to_add = np.random.choice(candidate_ings, p=density_ingredients[candidate_ings] / np.sum(density_ingredients[candidate_ings])) | |
self.genes_presence[index_to_add] = 1 | |
time_dict[' mutation add ing'] = [time.time() - init_time] | |
init_time = time.time() | |
# replace ings by others from the same family | |
if np.random.rand() < self.mutation_params['p_switch_ing']: | |
i = np.random.choice(self.get_present_ing()) | |
ing_str = ingredient_list[i] | |
if ing_str not in ['sparkling wine', 'orange juice']: | |
if ing_str in bubble_ingredients: | |
candidates_ids = np.array(sorted(self.ing_ids_per_cat['bubbles'] - set([i]))) | |
new_bubble = np.random.choice(candidates_ids, p=density_ingredients[candidates_ids] / np.sum(density_ingredients[candidates_ids])) | |
self.genes_presence[i] = 0 | |
self.genes_presence[new_bubble] = 1 | |
self.genes_quantity[new_bubble] = self.genes_quantity[i] # copy quantity | |
categories = ['acid', 'bitters', 'juice', 'liqueur', 'liquor', 'sweeteners', 'vermouth'] | |
for cat in categories: | |
if ing_str in ingredients_per_type[cat]: | |
present_ings = self.get_present_ing() | |
candidates_ids = np.array(sorted(self.ing_ids_per_cat[cat] - set([i]) - set(present_ings))) | |
if len(candidates_ids) > 0: | |
replacing_ing = np.random.choice(candidates_ids, p=density_ingredients[candidates_ids] / np.sum(density_ingredients[candidates_ids])) | |
self.genes_presence[i] = 0 | |
self.genes_presence[replacing_ing] = 1 | |
self.genes_quantity[replacing_ing] = self.genes_quantity[i] # copy quantity | |
break | |
time_dict[' mutation switch ing'] = [time.time() - init_time] | |
init_time = time.time() | |
# add noise on ing quantity | |
for i in self.get_present_ing(): | |
if np.random.rand() < self.mutation_params['p_change_q']: | |
self.genes_quantity[i] += np.random.randn() * self.mutation_params['delta_change_q'] | |
self.genes_quantity = np.clip(self.genes_quantity, 0, 1) | |
time_dict[' mutation change quantity'] = [time.time() - init_time] | |
init_time = time.time() | |
self.compute_cocktail_rep() | |
time_dict[' mutation compute cocktail rep'] = [time.time() - init_time] | |
init_time = time.time() | |
# self.make_recipe_fit_the_glass() | |
time_dict[' mutation check glass fit'] = [time.time() - init_time] | |
init_time = time.time() | |
self.compute_perf() | |
time_dict[' mutation compute perf'] = [time.time() - init_time] | |
init_time = time.time() | |
stop = 1 | |
return time_dict | |
def update_time_dict(self, main_dict, new_dict): | |
for k in new_dict.keys(): | |
if k in main_dict.keys(): | |
main_dict[k].append(np.sum(new_dict[k])) | |
else: | |
main_dict[k] = [np.sum(new_dict[k])] | |
return main_dict | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
# Get recipe and print | |
# # # # # # # # # # # # # # # # # # # # # # # # | |
def get_recipe(self, unit='mL', name=None): | |
ing_quantities = self.get_ingredient_quantities() | |
ingredients, quantities = [], [] | |
for i_ing, q_ing in enumerate(ing_quantities): | |
if q_ing > 0.8: | |
ingredients.append(ingredient_list[i_ing]) | |
quantities.append(round(q_ing)) | |
recipe_str = format_ingredients(ingredients, quantities) | |
recipe_str_readable = print_recipe(unit=unit, ingredient_str=recipe_str, name=name, to_print=False) | |
return ingredients, quantities, recipe_str, recipe_str_readable | |
def get_instructions(self): | |
ing_quantities = self.get_ingredient_quantities() | |
ingredients, quantities = [], [] | |
for i_ing, q_ing in enumerate(ing_quantities): | |
if q_ing > 0.8: | |
ingredients.append(ingredient_list[i_ing]) | |
quantities.append(round(q_ing)) | |
str_out = 'Instructions:\n ' | |
if 'mint' in ingredients: | |
i_mint = ingredients.index('mint') | |
n_leaves = quantities[i_mint] | |
str_out += f'Add {n_leaves} mint leaves to a shaker, followed by an ice cube.\n Muddle the mint and ice together with a muddler.\n ' | |
bubbles = ['sparkling wine', 'tonic', 'soda', 'ginger beer'] | |
other_ings = [ing for ing in ingredients if ing not in ['egg', 'angostura', 'orange bitters'] + bubbles] | |
if self.prep_type == 'built': | |
str_out += 'Add a large ice cube in the glass.\n ' | |
# add ingredients to pour | |
str_out += 'Pour' | |
for i, ing in enumerate(other_ings): | |
if i == len(other_ings) - 2: | |
str_out += f' {ing} and' | |
elif i == len(other_ings) - 1: | |
str_out += f' {ing}' | |
else: | |
str_out += f' {ing},' | |
if self.prep_type in ['built'] and 'mint' not in ingredients: | |
str_out += ' into the glass.\n ' | |
else: | |
str_out += ' into the shaker.\n ' | |
if self.prep_type == 'egg_shaken' and 'egg' in ingredients: | |
str_out += 'Add the egg white.\n Dry-shake for 15s (without ice), then fill with ice and shake for another 15s.\n Serve into the glass through a strainer.\n ' | |
elif 'shaken' in self.prep_type: | |
str_out += 'Fill with ice and shake for 15s.\n Serve into the glass through a strainer.\n ' | |
elif self.prep_type == 'stirred': | |
str_out += 'Add ice and stir the cocktail with a spoon for 15s.\n Serve into the glass through a strainer.\n ' | |
elif self.prep_type == 'built': | |
str_out += 'Stir two turns with a spoon.\n ' | |
bubble_ing = [ing for ing in ingredients if ing in bubbles] | |
if len(bubble_ing) > 0: | |
str_out += f'Top up with ' | |
for ing in bubble_ing: | |
str_out += f'{ing}, ' | |
str_out = str_out[:-2] + '.\n ' | |
bitter_ing = [ing for ing in ingredients if ing in ['angostura', 'orange bitters']] | |
if len(bitter_ing) > 0: | |
if len(bitter_ing) == 1: | |
q = quantities[ingredients.index(bitter_ing[0])] | |
n_dashes = max(1, int(q / 0.6)) | |
str_out += f'Add {n_dashes} dash' | |
if n_dashes > 1: | |
str_out += 'es' | |
str_out += f' of {bitter_ing[0]}.\n ' | |
elif len(bitter_ing) == 2: | |
q = quantities[ingredients.index(bitter_ing[0])] | |
n_dashes = max(1, int(q / 0.6)) | |
str_out += f'Add {n_dashes} dash' | |
if n_dashes > 1: | |
str_out += 'es' | |
str_out += f' of {bitter_ing[0]} and ' | |
q = quantities[ingredients.index(bitter_ing[1])] | |
n_dashes = max(1, int(q / 0.6)) | |
str_out += f'{n_dashes} dash' | |
if n_dashes > 1: | |
str_out += 'es' | |
str_out += f' of {bitter_ing[1]}.\n ' | |
str_out += 'Enjoy!' | |
return str_out | |
def print_recipe(self, name=None): | |
print(self.get_recipe(name)[3]) |