Ingredient Amount Estimator GFM
Il Gap Filling Module Ingredient Amount Estimator stima la quantita di ogni ingrediente in un prodotto alimentare basandosi sui dati nutrizionali. Utilizza l'ottimizzazione convessa (CVXPY) per minimizzare la differenza tra i valori nutrizionali dichiarati sulla confezione del prodotto e i valori calcolati derivati dai nutrienti dei singoli ingredienti.
Riferimento Rapido
| Proprieta | Descrizione |
|---|---|
| Eseguito su | FoodProcessingActivityNode con valori nutrizionali disponibili sui nodi genitori |
| Dipendenze | UnitWeightConversionGapFillingWorker, AddClientNodesGapFillingWorker, MatchProductNameGapFillingWorker, IngredientSplitterGapFillingWorker, NutrientSubdivisionGapFillingWorker, LinkTermToActivityNodeGapFillingWorker |
| Input Chiave | Dichiarazione nutrizionale del prodotto, lista ingredienti con profili nutrizionali |
| Output | Quantita ingredienti stimate (kg per unita di prodotto genitore) |
| Trigger | Il prodotto ha valori nutrizionali e dichiarazione ingredienti |
Quando Viene Eseguito
Il modulo si attiva quando:
- Il nodo e un
FoodProcessingActivityNode - I nodi genitori hanno valori nutrizionali disponibili (dall'interfaccia di programmazione o dal database)
- Il prodotto ha una dichiarazione ingredienti che e stata analizzata dal Gap Filling Module Ingredient Splitter
- Tutti i Gap Filling Module dipendenti richiesti sono completati
Output Chiave
Il modulo produce:
- Quantita ingredienti: Peso stimato di ogni ingrediente in kg per unita di prodotto genitore
- Stato soluzione: Stato del solutore di ottimizzazione (ottimale, non realizzabile, illimitato)
- Errori quadratici: Errori quadratici per nutriente per la valutazione della qualita
- Nutrienti stimati: Valori nutrizionali calcolati basati sulle quantita stimate
Metodologia Scientifica
Lo stimatore delle quantita degli ingredienti risolve un problema di ottimizzazione vincolata per trovare le percentuali degli ingredienti che meglio corrispondono ai valori nutrizionali dichiarati rispettando i vincoli legali e fisici.
Formulazione del Problema
L'algoritmo minimizza la differenza normalizzata tra nutrienti calcolati e dichiarati:
minimize ||A_norm * x - b_norm||
Dove:
- A: Matrice nutrienti M x N (M nutrienti, N ingredienti)
- x: Vettore N x 1 delle quantita ingredienti (frazioni del totale, che sommano a 1)
- b: Vettore M x 1 dei valori nutrizionali dichiarati
- A_norm, b_norm: Versioni normalizzate usando la deviazione standard
Normalizzazione
Ogni nutriente e normalizzato per la sua deviazione standard della popolazione per garantire una ponderazione uniforme:
A_norm = (A.T / norm_vector).T
b_norm = b / norm_vector
Questa normalizzazione utilizza statistiche derivate da migliaia di dichiarazioni di prodotti:
| Nutriente | Media | Deviazione Standard | Mediana |
|---|---|---|---|
| Energia (kilocalorie) | 284,84 | 170,00 | 275,0 |
| Grassi (g) | 12,61 | 12,67 | 7,4 |
| Grassi saturi (g) | 5,48 | 6,29 | 2,5 |
| Carboidrati (g) | 27,47 | 24,19 | 16,2 |
| Zucchero/Saccarosio (g) | 15,36 | 17,05 | 7,3 |
| Proteine (g) | 6,94 | 5,76 | 5,8 |
| Cloruro di sodio (g) | 0,77 | 0,85 | 0,4 |
| Fibre (g) | 2,88 | 2,59 | 2,2 |
| Sodio (mg) | 302,92 | 938,07 | 15,74 |
| Cloro (mg) | 461,02 | 515,61 | 242,64 |
Nutrienti Accettati
Solo i nutrienti con dati completi nel Database Eaternity vengono utilizzati per l'ottimizzazione:
ACCEPTED_NUTRIENTS = {
"energy",
"fat",
"saturated_fat",
"carbohydrates",
"water",
"sucrose",
"protein",
"sodium",
"chlorine",
"fibers",
}
Sistema di Vincoli
L'ottimizzazione include diversi tipi di vincoli:
1. Vincolo Somma-a-Uno
Tutte le frazioni degli ingredienti a ogni livello gerarchico devono sommare a 1 (100%):
F @ x == g
Dove F e la matrice di vincolo di uguaglianza che garantisce che gli ingredienti a ogni livello sommino correttamente.
2. Vincolo di Ordine Decrescente
Secondo le normative sull'etichettatura alimentare dell'Unione Europea, gli ingredienti devono essere elencati in ordine decrescente per peso:
C @ x <= d
Dove C e una matrice di vincolo che impone:
ingrediente[i] >= ingrediente[i+1]per ingredienti allo stesso livello
Eccezione: Questo vincolo non si applica alle suddivisioni (varianti nutrizionali dello stesso ingrediente).
3. Vincoli di Percentuale Fissa
Quando le percentuali sono dichiarate sulla confezione:
# Vincolo percentuale esatta
x[ingredient_idx] == fixed_percentage * x[parent_idx]
# Oppure per ingredienti di primo livello
x[ingredient_idx] == fixed_percentage
4. Vincoli di Percentuale Minima/Massima
Quando sono specificati solo i limiti:
# Percentuale minima: ingrediente >= min_percentage * genitore
x[col_idx] - min_pct * x[parent_col_idx] >= 0
# Percentuale massima: ingrediente <= max_percentage * genitore
x[col_idx] - max_pct * x[parent_col_idx] <= 0
5. Non-negativita
Tutte le quantita degli ingredienti devono essere non negative:
x >= 0
Dettagli di Implementazione
Configurazione del Solutore
Il modulo utilizza il solutore ECOS tramite CVXPY:
problem.solve(solver="ECOS")
ECOS (Embedded Conic Solver) e scelto per la sua efficienza con programmi conici di secondo ordine e la sua capacita di gestire la funzione obiettivo dei minimi quadrati.
Gestione della Gerarchia degli Ingredienti
L'algoritmo gestisce le dichiarazioni di ingredienti annidati utilizzando un sistema di tuple di livello:
# Esempio di gerarchia:
# "Cioccolato (cacao 30%, zucchero), Latte in polvere (latte, lattosio)"
# Tuple di livello:
# (0,) -> Cioccolato
# (0, 0) -> cacao (30% del Cioccolato)
# (0, 1) -> zucchero
# (1,) -> Latte in polvere
# (1, 0) -> latte
# (1, 1) -> lattosio
La matrice di vincolo garantisce:
- I sotto-ingredienti sommano alla quantita dell'ingrediente genitore
- I vincoli di ordine si applicano all'interno di ogni livello
Gestione Speciale
Regolazione per Fermentazione
Per i prodotti alcolici, l'algoritmo regola i nutrienti per tenere conto della fermentazione:
# Conversione alcol (equazione di Gay-Lussac)
# 180g zucchero -> 92g etanolo + 88g CO2
alc_as_sugar = alc.value / 0.47
# Regola zucchero e carboidrati
sugar.value += alc_as_sugar
carbs.value += alc_as_sugar
# Regola energia (alcol: 7 kcal/g, zucchero: 4 kcal/g)
energy.value += alc_as_sugar * 4.0 # Aggiungi calorie zucchero
energy.value -= alc.value * 7.0 # Rimuovi calorie alcol
Suddivisione del Cloruro di Sodio
Quando e dichiarato il cloruro di sodio (sale), viene suddiviso in sodio e cloro per l'ottimizzazione:
NA_PERCENT_IN_NACL = 39.34 # 39,34% sodio
CL_PERCENT_IN_NACL = 60.66 # 60,66% cloro
new_sodium = old_sodium + (sodium_chloride * 39.34 / 100)
new_chlorine = old_chlorine + (sodium_chloride * 60.66 / 100)
Rimozione dell'Acqua
Il contenuto di acqua e escluso dall'ottimizzazione diretta poiche e gestito implicitamente attraverso la suddivisione dei nutrienti:
incoming_nutrients.quantities = {
k: v for k, v in incoming_nutrients.quantities.items()
if k != get_nutrient_term("water").uid
}
Ingredienti Non Alimentari
Agli ingredienti non alimentari (additivi, conservanti, numeri E) viene assegnata quantita zero:
for uid in non_food_ingredient_uids:
calc_graph.apply_mutation(
PropMutation(
node_uid=uid,
prop_name="amount",
prop=QuantityProp(
value=0.0,
unit_term_uid=kilogram_term.uid,
),
)
)
Esempio di Calcolo
Scenario: Barretta di cioccolato con dichiarazione:
- "Massa di cacao (45%), Zucchero, Burro di cacao, Latte in polvere (5%)"
- Nutrienti dichiarati: 530 kcal, 32g grassi, 20g grassi saturi, 52g carboidrati, 48g zucchero, 6g proteine
Passo 1: Costruire la Gerarchia degli Ingredienti
Tuple di livello:
(0,) -> Massa di cacao [45% fisso]
(1,) -> Zucchero
(2,) -> Burro di cacao
(3,) -> Latte in polvere [5% fisso]
Passo 2: Costruire le Matrici
Matrice nutrienti A (per 100g di ogni ingrediente):
| Massa di cacao | Zucchero | Burro di cacao | Latte in polvere | |
|---|---|---|---|---|
| Energia | 228 | 400 | 884 | 496 |
| Grassi | 14 | 0 | 99,8 | 26,7 |
| Grassi sat. | 8,1 | 0 | 60,5 | 16,7 |
| Carboidrati | 11,5 | 100 | 0 | 38,4 |
| Zucchero | 0,5 | 100 | 0 | 38,4 |
| Proteine | 19,6 | 0 | 0 | 26,3 |
Matrice vincoli C (vincoli ordine):
[[-1, 1, 0, 0], # x[0] >= x[1]
[0, -1, 1, 0], # x[1] >= x[2]
[0, 0, -1, 1], # x[2] >= x[3]
[0, 0, 0, -1]]
Passo 3: Risolvere l'Ottimizzazione
Con vincoli fissi x[0] = 0,45 e x[3] = 0,05:
minimize ||A_norm @ x - b_norm||
subject to:
x[0] + x[1] + x[2] + x[3] = 1
x[0] = 0.45
x[3] = 0.05
x[0] >= x[1] >= x[2] >= x[3]
x >= 0
Passo 4: Soluzione
Massa di cacao: 45,0% (fisso)
Zucchero: 30,2% (stimato)
Burro di cacao: 19,8% (stimato)
Latte in polvere: 5,0% (fisso)
Passo 5: Output nel Grafo
Ogni nodo ingrediente riceve una quantita in kg:
# Per 1kg di prodotto:
massa_cacao.amount = 0.450 kg
zucchero.amount = 0.302 kg
burro_cacao.amount = 0.198 kg
latte_polvere.amount = 0.050 kg
Meccanismi di Fallback
Quando l'Ottimizzazione Fallisce
Se l'ottimizzazione restituisce "infeasible" o "unbounded":
- Prova fallback senza stima: Se tutte le percentuali sono fisse, usale direttamente
- Gestisci suddivisioni: Assegna quantita suddivisioni basate sulla quantita di produzione del genitore
- Registra errore dati: Registra il fallimento per revisione manuale
if len(flat_nutrients) == len(fixed_percentages):
if set(flat_nutrients.keys()) == set(fixed_percentages.keys()):
self.handle_fixed_percentages(flat_nutrients, fixed_percentages, calc_graph)
return
Nutrienti Mancanti
Quando gli ingredienti foglia non hanno dati nutrizionali:
- Se tutte le percentuali sono note, usa percentuali fisse
- Altrimenti, solleva un errore richiedendo dati dal team scientifico
Metriche di Qualita
Errori Quadratici
Il modulo calcola errori quadratici per nutriente per la valutazione della qualita:
error_squares[nutrient] = ((stimato - dichiarato) / STD)^2
Questo errore normalizzato permette confronti tra prodotti indipendentemente dalla scala.
Stato della Soluzione
Il solutore restituisce uno stato che indica la qualita della soluzione:
- optimal: Soluzione valida trovata
- optimal_inaccurate: Soluzione trovata ma potrebbe avere problemi numerici
- infeasible: Nessuna soluzione valida esiste (vincoli in conflitto)
- unbounded: Problema non correttamente vincolato
Limitazioni Note
Copertura Dati
- Richiede profili nutrizionali per tutti gli ingredienti nel Database Eaternity
- Solo 10 nutrienti usati per l'ottimizzazione (non tutti i nutrienti dichiarati)
- Statistiche nutrizionali basate su dati di prodotti europei
Ipotesi del Modello
- Ingredienti elencati in ordine decrescente (regolamento dell'Unione Europea)
- Percentuali sotto-ingredienti relative al genitore (puo variare nella pratica)
- Relazione lineare tra quantita ingredienti e nutrienti
- Nessun cambiamento nutrizionale correlato alla trasformazione modellato direttamente
Considerazioni Numeriche
- Soluzioni negative molto piccole (> -1e-5) sono fissate a 1e-10
- Soluzioni NaN attivano errori
- Il solutore puo fallire su problemi mal condizionati
Riferimenti
-
Documentazione CVXPY. https://www.cvxpy.org/
-
Solutore ECOS. Domahidi, A., Chu, E., & Boyd, S. (2013). ECOS: An SOCP solver for embedded systems. European Control Conference.
-
Regolamento sull'Etichettatura Alimentare dell'Unione Europea. Regolamento (UE) N. 1169/2011
-
Distanza di Mahalanobis. https://en.wikipedia.org/wiki/Mahalanobis_distance
-
Dati di Composizione Alimentare EuroFIR. http://www.eurofir.org/