Ingredient Amount Estimator GFM
El Gap Filling Module Estimador de Cantidades de Ingredientes estima la cantidad de cada ingrediente en un producto alimentario basándose en datos nutricionales. Utiliza optimización convexa (CVXPY) para minimizar la diferencia entre los valores nutricionales declarados en el envase del producto y los valores calculados derivados de los nutrientes de ingredientes individuales.
Referencia Rápida
| Propiedad | Descripción |
|---|---|
| Se ejecuta en | FoodProcessingActivityNode con valores de nutrientes disponibles en nodos padre |
| Dependencias | UnitWeightConversionGapFillingWorker, AddClientNodesGapFillingWorker, MatchProductNameGapFillingWorker, IngredientSplitterGapFillingWorker, NutrientSubdivisionGapFillingWorker, LinkTermToActivityNodeGapFillingWorker |
| Entrada clave | Declaración de nutrientes del producto, lista de ingredientes con perfiles de nutrientes |
| Salida | Cantidades de ingredientes estimadas (kg por unidad de producto padre) |
| Activador | El producto tiene valores de nutrientes y declaración de ingredientes |
Cuándo se Ejecuta
El módulo se activa cuando:
- El nodo es un
FoodProcessingActivityNode - Los nodos padre tienen valores de nutrientes disponibles (de la Interfaz de Programación de Aplicaciones o base de datos)
- El producto tiene una declaración de ingredientes que ha sido parseada por el Gap Filling Module de División de Ingredientes
- Todos los Gap Filling Modules de dependencia requeridos han completado
Salida Clave
El módulo produce:
- Cantidades de ingredientes: Peso estimado de cada ingrediente en kg por unidad de producto padre
- Estado de solución: Estado del solucionador de optimización (óptimo, infactible, no acotado)
- Cuadrados de error: Errores cuadrados por nutriente para evaluación de calidad
- Nutrientes estimados: Valores de nutrientes calculados basados en cantidades estimadas
Metodología Científica
El estimador de cantidades de ingredientes resuelve un problema de optimización con restricciones para encontrar porcentajes de ingredientes que mejor coincidan con los valores nutricionales declarados mientras respetan restricciones legales y físicas.
Formulación del Problema
El algoritmo minimiza la diferencia normalizada entre nutrientes calculados y declarados:
minimizar ||A_norm * x - b_norm||
Donde:
- A: Matriz de nutrientes M x N (M nutrientes, N ingredientes)
- x: Vector N x 1 de cantidades de ingredientes (fracciones del total, sumando 1)
- b: Vector M x 1 de valores de nutrientes declarados
- A_norm, b_norm: Versiones normalizadas usando desviación estándar
Normalización
Cada nutriente se normaliza por su desviación estándar poblacional para asegurar ponderación igual:
A_norm = (A.T / norm_vector).T
b_norm = b / norm_vector
Esta normalización usa estadísticas derivadas de miles de declaraciones de productos:
| Nutriente | Promedio | Desviación Estándar | Mediana |
|---|---|---|---|
| Energía (kilocaloría) | 284,84 | 170,00 | 275,0 |
| Grasa (g) | 12,61 | 12,67 | 7,4 |
| Grasa saturada (g) | 5,48 | 6,29 | 2,5 |
| Carbohidratos (g) | 27,47 | 24,19 | 16,2 |
| Azúcar/Sacarosa (g) | 15,36 | 17,05 | 7,3 |
| Proteína (g) | 6,94 | 5,76 | 5,8 |
| Cloruro de sodio (g) | 0,77 | 0,85 | 0,4 |
| Fibras (g) | 2,88 | 2,59 | 2,2 |
| Sodio (mg) | 302,92 | 938,07 | 15,74 |
| Cloro (mg) | 461,02 | 515,61 | 242,64 |
Nutrientes Aceptados
Solo los nutrientes con datos completos en la Base de Datos de Eaternity se usan para optimización:
ACCEPTED_NUTRIENTS = {
"energy",
"fat",
"saturated_fat",
"carbohydrates",
"water",
"sucrose",
"protein",
"sodium",
"chlorine",
"fibers",
}
Sistema de Restricciones
La optimización incluye varios tipos de restricciones:
1. Restricción de Suma a Uno
Todas las fracciones de ingredientes en cada nivel de jerarquía deben sumar 1 (100%):
F @ x == g
Donde F es la matriz de restricción de igualdad que asegura que los ingredientes en cada nivel sumen correctamente.
2. Restricción de Orden Decreciente
Según las regulaciones de etiquetado de alimentos de la Unión Europea, los ingredientes deben listarse en orden decreciente por peso:
C @ x <= d
Donde C es una matriz de restricción que impone:
ingrediente[i] >= ingrediente[i+1]para ingredientes del mismo nivel
Excepción: Esta restricción no aplica a subdivisiones (variantes de nutrientes del mismo ingrediente).
3. Restricciones de Porcentaje Fijo
Cuando se declaran porcentajes en el envase:
# Restricción de porcentaje exacto
x[ingredient_idx] == fixed_percentage * x[parent_idx]
# O para ingredientes de nivel raíz
x[ingredient_idx] == fixed_percentage
4. Restricciones de Porcentaje Mínimo/Máximo
Cuando solo se especifican límites:
# Porcentaje mínimo: ingrediente >= min_percentage * padre
x[col_idx] - min_pct * x[parent_col_idx] >= 0
# Porcentaje máximo: ingrediente <= max_percentage * padre
x[col_idx] - max_pct * x[parent_col_idx] <= 0
5. No Negatividad
Todas las cantidades de ingredientes deben ser no negativas:
x >= 0
Detalles de Implementación
Configuración del Solucionador
El módulo usa el solucionador ECOS a través de CVXPY:
problem.solve(solver="ECOS")
ECOS (Embedded Conic Solver) se elige por su eficiencia con programas de cono de segundo orden y su capacidad para manejar la función objetivo de mínimos cuadrados.
Manejo de Jerarquía de Ingredientes
El algoritmo maneja declaraciones de ingredientes anidados usando un sistema de tuplas de nivel:
# Ejemplo de jerarquía:
# "Chocolate (cacao 30%, azúcar), Leche en polvo (leche, lactosa)"
# Tuplas de nivel:
# (0,) -> Chocolate
# (0, 0) -> cacao (30% de Chocolate)
# (0, 1) -> azúcar
# (1,) -> Leche en polvo
# (1, 0) -> leche
# (1, 1) -> lactosa
La matriz de restricciones asegura:
- Los subingredientes suman la cantidad de su ingrediente padre
- Las restricciones de orden aplican dentro de cada nivel
Manejo Especial
Ajuste de Fermentación
Para productos alcohólicos, el algoritmo ajusta nutrientes para tener en cuenta la fermentación:
# Conversión de alcohol (ecuación de Gay-Lussac)
# 180g azúcar -> 92g etanol + 88g CO2
alc_as_sugar = alc.value / 0.47
# Ajustar azúcar y carbohidratos
sugar.value += alc_as_sugar
carbs.value += alc_as_sugar
# Ajustar energía (alcohol: 7 kcal/g, azúcar: 4 kcal/g)
energy.value += alc_as_sugar * 4.0 # Añadir calorías de azúcar
energy.value -= alc.value * 7.0 # Eliminar calorías de alcohol
División de Cloruro de Sodio
Cuando se declara cloruro de sodio (sal), se divide en sodio y cloro para optimización:
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)
Eliminación de Agua
El contenido de agua se excluye de la optimización directa ya que se maneja implícitamente a través de la subdivisión de nutrientes:
incoming_nutrients.quantities = {
k: v for k, v in incoming_nutrients.quantities.items()
if k != get_nutrient_term("water").uid
}
Ingredientes No Alimentarios
Los ingredientes no alimentarios (aditivos, conservantes, números E) se asignan cantidad cero:
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,
),
)
)
Construcción de Matriz de Restricciones
Matriz de Nutrientes (A)
La matriz de nutrientes M x N mapea perfiles de nutrientes de ingredientes a la optimización:
def make_nutrient_matrix(flat_nutrients, product_order, nutrient_order, M, N):
matrix = np.zeros((M, N))
for iy, product_key in enumerate(product_order):
if flat_nutrients[product_key] is None:
continue # Dejar columna en cero
for ix, nutrient_key in enumerate(nutrient_order):
if nutrient_key in flat_nutrients[product_key].quantities:
matrix[ix, iy] = flat_nutrients[product_key].quantities[nutrient_key].value
matrix[np.isnan(matrix)] = 0.0
return matrix
Matriz de Restricción de Orden (C)
Impone orden decreciente de ingredientes:
# Ejemplo para [(0,), (1,), (1,0), (1,1), (2,)]
# Estructura de matriz:
# [[-1, 1, 0, 0, 0], # ingrediente[0] >= ingrediente[1]
# [ 0, -1, 0, 0, 1], # ingrediente[1] >= ingrediente[4]
# [ 0, 0, -1, 1, 0], # sub[0] >= sub[1]
# [ 0, 0, 0, -1, 0],
# [ 0, 0, 0, 0, -1]]
Matriz de Igualdad (F)
Asegura que los ingredientes sumen correctamente en cada nivel:
# Ejemplo: F @ x = g
# [[1, 1, 0, 0, 1], # Nivel superior suma 1
# [0, -1, 1, 1, 0]] # Subingredientes suman padre
# g = [1, 0]
Ejemplo de Cálculo
Escenario: Barra de chocolate con declaración:
- "Pasta de cacao (45%), Azúcar, Manteca de cacao, Leche en polvo (5%)"
- Nutrientes declarados: 530 kcal, 32g grasa, 20g grasa saturada, 52g carbohidratos, 48g azúcar, 6g proteína
Paso 1: Construir Jerarquía de Ingredientes
Tuplas de nivel:
(0,) -> Pasta de cacao [45% fijo]
(1,) -> Azúcar
(2,) -> Manteca de cacao
(3,) -> Leche en polvo [5% fijo]
Paso 2: Construir Matrices
Matriz de nutrientes A (por 100g de cada ingrediente):
| Pasta de cacao | Azúcar | Manteca de cacao | Leche en polvo | |
|---|---|---|---|---|
| Energía | 228 | 400 | 884 | 496 |
| Grasa | 14 | 0 | 99,8 | 26,7 |
| Grasa sat. | 8,1 | 0 | 60,5 | 16,7 |
| Carbohidratos | 11,5 | 100 | 0 | 38,4 |
| Azúcar | 0,5 | 100 | 0 | 38,4 |
| Proteína | 19,6 | 0 | 0 | 26,3 |
Matriz de restricciones C (restricciones de orden):
[[-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]]
Paso 3: Resolver Optimización
Con restricciones fijas x[0] = 0,45 y x[3] = 0,05:
minimizar ||A_norm @ x - b_norm||
sujeto a:
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
Paso 4: Solución
Pasta de cacao: 45,0% (fijo)
Azúcar: 30,2% (estimado)
Manteca de cacao: 19,8% (estimado)
Leche en polvo: 5,0% (fijo)
Paso 5: Salida al Grafo
Cada nodo de ingrediente recibe una cantidad en kg:
# Para producto de 1kg:
cocoa_mass.amount = 0,450 kg
sugar.amount = 0,302 kg
cocoa_butter.amount = 0,198 kg
milk_powder.amount = 0,050 kg
Mecanismos de Respaldo
Cuando la Optimización Falla
Si la optimización devuelve "infactible" o "no acotado":
- Intentar respaldo sin estimación: Si todos los porcentajes están fijos, usar esos directamente
- Manejar subdivisiones: Asignar cantidades de subdivisión basadas en cantidad de producción padre
- Registrar error de datos: Registrar el fallo para revisión manual
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
Nutrientes Faltantes
Cuando los ingredientes hoja carecen de datos de nutrientes:
- Si todos los porcentajes son conocidos, usar porcentajes fijos
- De lo contrario, elevar un error solicitando datos del equipo científico
Métricas de Calidad
Cuadrados de Error
El módulo calcula errores cuadrados por nutriente para evaluación de calidad:
error_squares[nutrient] = ((estimated - declared) / STD)^2
Este error normalizado permite comparación entre productos independientemente de la escala.
Estado de Solución
El solucionador devuelve estado indicando calidad de solución:
- optimal: Solución válida encontrada
- optimal_inaccurate: Solución encontrada pero puede tener problemas numéricos
- infeasible: No existe solución válida (restricciones conflictivas)
- unbounded: Problema no correctamente restringido
Limitaciones Conocidas
Cobertura de Datos
- Requiere perfiles de nutrientes para todos los ingredientes en la Base de Datos de Eaternity
- Solo 10 nutrientes usados para optimización (no todos los nutrientes declarados)
- Estadísticas de nutrientes basadas en datos de productos europeos
Suposiciones del Modelo
- Ingredientes listados en orden decreciente (regulación de la Unión Europea)
- Porcentajes de subingredientes relativos al padre (puede variar en la práctica)
- Relación lineal entre cantidades de ingredientes y nutrientes
- Cambios de nutrientes relacionados con procesamiento no modelados directamente
Consideraciones Numéricas
- Soluciones negativas muy pequeñas (> -1e-5) se ajustan a 1e-10
- Soluciones NaN activan errores
- El solucionador puede fallar en problemas mal condicionados
Gap Filling Modules Relacionados
| Módulo | Relación |
|---|---|
| Ingredient Splitter | Parsea declaraciones de ingredientes en jerarquía estructurada |
| Nutrient Subdivision | Crea variantes secas/modificadas para modelado de pérdida de agua |
| Match Product Name | Enlaza ingredientes a entradas de base de datos con perfiles de nutrientes |
| Unit Weight Conversion | Convierte cantidades entre unidades |
Referencias
-
Documentación de CVXPY. https://www.cvxpy.org/
-
Solucionador ECOS. Domahidi, A., Chu, E., & Boyd, S. (2013). ECOS: An SOCP solver for embedded systems. European Control Conference.
-
Regulación de Etiquetado de Alimentos de la Unión Europea. Reglamento (UE) No 1169/2011
-
Distancia de Mahalanobis. https://en.wikipedia.org/wiki/Mahalanobis_distance
-
Datos de Composición de Alimentos EuroFIR. http://www.eurofir.org/