Passa al contenuto principale

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

ProprietaDescrizione
Eseguito suFoodProcessingActivityNode con valori nutrizionali disponibili sui nodi genitori
DipendenzeUnitWeightConversionGapFillingWorker, AddClientNodesGapFillingWorker, MatchProductNameGapFillingWorker, IngredientSplitterGapFillingWorker, NutrientSubdivisionGapFillingWorker, LinkTermToActivityNodeGapFillingWorker
Input ChiaveDichiarazione nutrizionale del prodotto, lista ingredienti con profili nutrizionali
OutputQuantita ingredienti stimate (kg per unita di prodotto genitore)
TriggerIl prodotto ha valori nutrizionali e dichiarazione ingredienti

Quando Viene Eseguito

Il modulo si attiva quando:

  1. Il nodo e un FoodProcessingActivityNode
  2. I nodi genitori hanno valori nutrizionali disponibili (dall'interfaccia di programmazione o dal database)
  3. Il prodotto ha una dichiarazione ingredienti che e stata analizzata dal Gap Filling Module Ingredient Splitter
  4. 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:

NutrienteMediaDeviazione StandardMediana
Energia (kilocalorie)284,84170,00275,0
Grassi (g)12,6112,677,4
Grassi saturi (g)5,486,292,5
Carboidrati (g)27,4724,1916,2
Zucchero/Saccarosio (g)15,3617,057,3
Proteine (g)6,945,765,8
Cloruro di sodio (g)0,770,850,4
Fibre (g)2,882,592,2
Sodio (mg)302,92938,0715,74
Cloro (mg)461,02515,61242,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 cacaoZuccheroBurro di cacaoLatte in polvere
Energia228400884496
Grassi14099,826,7
Grassi sat.8,1060,516,7
Carboidrati11,5100038,4
Zucchero0,5100038,4
Proteine19,60026,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":

  1. Prova fallback senza stima: Se tutte le percentuali sono fisse, usale direttamente
  2. Gestisci suddivisioni: Assegna quantita suddivisioni basate sulla quantita di produzione del genitore
  3. 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

  1. Documentazione CVXPY. https://www.cvxpy.org/

  2. Solutore ECOS. Domahidi, A., Chu, E., & Boyd, S. (2013). ECOS: An SOCP solver for embedded systems. European Control Conference.

  3. Regolamento sull'Etichettatura Alimentare dell'Unione Europea. Regolamento (UE) N. 1169/2011

  4. Distanza di Mahalanobis. https://en.wikipedia.org/wiki/Mahalanobis_distance

  5. Dati di Composizione Alimentare EuroFIR. http://www.eurofir.org/