Saltar al contenido principal

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

PropiedadDescripción
Se ejecuta enFoodProcessingActivityNode con valores de nutrientes disponibles en nodos padre
DependenciasUnitWeightConversionGapFillingWorker, AddClientNodesGapFillingWorker, MatchProductNameGapFillingWorker, IngredientSplitterGapFillingWorker, NutrientSubdivisionGapFillingWorker, LinkTermToActivityNodeGapFillingWorker
Entrada claveDeclaración de nutrientes del producto, lista de ingredientes con perfiles de nutrientes
SalidaCantidades de ingredientes estimadas (kg por unidad de producto padre)
ActivadorEl producto tiene valores de nutrientes y declaración de ingredientes

Cuándo se Ejecuta

El módulo se activa cuando:

  1. El nodo es un FoodProcessingActivityNode
  2. Los nodos padre tienen valores de nutrientes disponibles (de la Interfaz de Programación de Aplicaciones o base de datos)
  3. El producto tiene una declaración de ingredientes que ha sido parseada por el Gap Filling Module de División de Ingredientes
  4. 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:

NutrientePromedioDesviación EstándarMediana
Energía (kilocaloría)284,84170,00275,0
Grasa (g)12,6112,677,4
Grasa saturada (g)5,486,292,5
Carbohidratos (g)27,4724,1916,2
Azúcar/Sacarosa (g)15,3617,057,3
Proteína (g)6,945,765,8
Cloruro de sodio (g)0,770,850,4
Fibras (g)2,882,592,2
Sodio (mg)302,92938,0715,74
Cloro (mg)461,02515,61242,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 cacaoAzúcarManteca de cacaoLeche en polvo
Energía228400884496
Grasa14099,826,7
Grasa sat.8,1060,516,7
Carbohidratos11,5100038,4
Azúcar0,5100038,4
Proteína19,60026,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":

  1. Intentar respaldo sin estimación: Si todos los porcentajes están fijos, usar esos directamente
  2. Manejar subdivisiones: Asignar cantidades de subdivisión basadas en cantidad de producción padre
  3. 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óduloRelación
Ingredient SplitterParsea declaraciones de ingredientes en jerarquía estructurada
Nutrient SubdivisionCrea variantes secas/modificadas para modelado de pérdida de agua
Match Product NameEnlaza ingredientes a entradas de base de datos con perfiles de nutrientes
Unit Weight ConversionConvierte cantidades entre unidades

Referencias

  1. Documentación de CVXPY. https://www.cvxpy.org/

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

  3. Regulación de Etiquetado de Alimentos de la Unión Europea. Reglamento (UE) No 1169/2011

  4. Distancia de Mahalanobis. https://en.wikipedia.org/wiki/Mahalanobis_distance

  5. Datos de Composición de Alimentos EuroFIR. http://www.eurofir.org/