Cédric Colas
initial commit
e775f6d
raw
history blame
31.1 kB
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])