Computational Egg-cellence
Demonstrating Nutritional Analysis, PDE Modeling and LLM Recipe Tools Using Eggs

Gay Wilson and Ricardo Lopez, Wolfram Research

(RowDefault[, (Spacer1), , (Spacer1), ])  #WolframTechConf

ABSTRACT

This talk demonstrates the versatility of the Wolfram Language through food and nutrition analysis, transforming egg recipes with LLM tools and using the heat equation to model boiling an egg. Through this partial differential equation (PDE), we analyze the temperature evolution inside the egg over time.

Food and Nutrition Analysis with Wolfram Language

Food and Nutrition Analysis Resources

Wolfram Function Repository

Wolfram Language Example Repository

Wolfram Community (Gay Wilson)

Recipe Analysis with LLM and Wolfram Language

RecipeGraph

Works with the LLM to return a directed graph of the ingredients and instructions for a recipe:

@LLMSynthesize["Write a recipe for a vegetable frittata. Use abbreviations for Tbsp and tsp."]

RecipeRiskAnalysis

Works with the LLM to identify and highlight potential food safety hazards in a recipe:

["Ingredients:
2 eggs
1 tablespoon butter
Salt and pepper (to taste)
Instructions:
Crack the eggs into a bowl and whisk them with a fork until fully blended.
Heat the butter in a non-stick pan over medium heat until melted and bubbly.
Pour the eggs into the pan.
Let them sit for a few seconds without stirring.
Gently stir with a spatula, pushing the eggs from the edges towards the center.
Continue cooking while stirring occasionally until the eggs are set but still soft and slightly creamy.
Season with salt and pepper, and serve immediately."]

RecipeAllergenFinder

Works with the LLM to identify and list potential food allergens in a recipe:

["Ingredients:
12 oz wide egg noodles
1 lb ribeye steak, trimmed and sliced into thin strips
2 tbsp olive oil
1 medium onion, chopped
2 cloves garlic, minced
8 oz cremini mushrooms, sliced
1/2 cup dry white wine
1 cup beef broth
3/4 cup sour cream
2 tbsp all-purpose flour
1 tbsp Dijon mustard
1 tbsp Worcestershire sauce
Salt and pepper to taste
1/4 cup fresh parsley, chopped"]

Writing a recipe analysis prompt

A look behind the scenes at the prompt for   :

prompt=LLMFunction["You are the Food Safety Manager at a high-profile restaurant.\n
Your job is to make sure the restaurant staff follow the FDA's Hazard Analysis and Critical Control Point (HACCP) guidelines.\n
You are reviewing the Executive Chef's new recipes before the staff begin training to prepare these recipes.\n
Review the recipe `1`.\n
Edit the recipe so that any instruction steps that represent a Critical Control Point in the HACCP process begin with the word RISK and a colon.\n
Remember, a Critical Control Point is any process step where control can be applied for the prevention or elimination of any potential food safety hazard.\n
Edit ONLY the instructions that could be a potential food safety hazard. You can change the wording of the instruction if needed, such as 'wash, peel and slice the apples', instead of 'peel and slice the apples'.\n
ALSO give a brief explanation after the instruction step (8 words or less enclosed in parentheses) WHY that step could be a food safety hazard.\n
Here is an example: (Potential hazard: Contamination from unwashed produce)\n
KEEP the ingredients list at the start of the output, before the revised instructions, as in the original recipe.\n
Also INCLUDE the ingredients list in the recipe when LLMSynthesize is used to create the recipe.\n"];

Building a Recipe Timeline

Recipe timelines can be a great tool for helping manage a busy kitchen, by organizing the preparation and cooking process.

We wrote an LLM function instructing the LLM how to retrieve and format the recipe output. Next, we used the TimelinePlot function to create a colorful, easy-to-follow timeline.

detailedRecipeFunc=LLMFunction["Write a recipe for `1`. INCLUDE the amounts of each ingredient IN THE INSTRUCTIONS. Use the abbreviations Tbsp and tsp. With each instruction, include how many minutes the step will take. The description of each step absolutely MUST be less than 12 words! Give the result as a valid JSON array, using only standard letter and number characters. For example:
[
[\"Preheat oven to 350 F\", 10],
[\"Chop 1 medium onion\", 5],
[\"Stir in 2 Tbsp olive oil\", 1],
...
]
"];

pastaAllaCarbonaraSteps=ImportString[detailedRecipeFunc["pasta alla carbonara"],"RawJSON"]

numSteps=Length[pastaAllaCarbonaraSteps];
gradientColors=Table[ColorData["Rainbow"][i/numSteps],{i,numSteps}];

timelineData=MapThread[
    Labeled[
        Style[DateInterval[{TimeObject[{0,#1-#2,0},"Second"],TimeObject[{0,#1,0},"Second"]}],#3],
        #4
    ]&,
    {
        Accumulate[pastaAllaCarbonaraSteps[[All,2]]],
        pastaAllaCarbonaraSteps[[All,2]],
        PadRight[gradientColors,Length[pastaAllaCarbonaraSteps],gradientColors],
        pastaAllaCarbonaraSteps[[All,1]]
    }
];

TimelinePlot[timelineData,
    FrameLabel->{"Minutes",None},Filling->Below,PlotLabel->"Pasta alla Carbonara",
    FrameTicks->{{None,None},{{TimeObject[{0,#,0},"Second"],#}&/@Range[0,40,5],None}}
]

EggBoilingTime

This formula in FormulaData uses the physical properties of an egg and the initial and final temperatures to calculate the time required to boil the egg:

FormulaData[{"EggBoilingTime","Normal","CommonValues"}]

FormulaData[{"EggBoilingTime","Normal","CommonValues"},"QuantityVariableTable"]

variables={
	"r"->0.76,
	QuantityVariable[Subscript["T","egg"],"Temperature"]->Quantity[22,"DegreesCelsius"],
	QuantityVariable[Subscript["T","water"],"Temperature"]->Quantity[100,"DegreesCelsius"],
	QuantityVariable[Subscript["T","yolk"],"Temperature"]->Quantity[77,"DegreesCelsius"],
          "c"->Quantity[3.7,("Joules"/"Grams")/"Kelvins"],
	"k"->Quantity[0.0054, "Watts"]/Quantity[1, "Centimeters"*"KelvinsDifference"],
	"w"->Quantity[50,"Grams"],
	"ρ"->Quantity[1.038,"Grams"/"Centimeters"^3]
};
FormulaData[{"EggBoilingTime","Normal","CommonValues"},variables]

UnitConvert[(Quantity[347.597237881765`, "s", seconds, "Seconds"]),"Minutes"]

Boiling an egg at high altitude (1600m)

At higher elevations, the atmospheric pressure is lower, which means water boils at a lower temperature. Because the boiling point of water is lower, it takes longer for an egg to cook fully:

variablesAlt={
	"r"->0.76,
	QuantityVariable[Subscript["T","egg"],"Temperature"]->Quantity[22,"DegreesCelsius"],
	QuantityVariable[Subscript["T","yolk"],"Temperature"]->Quantity[77,"DegreesCelsius"],
          "c"->Quantity[3.7,("Joules"/"Grams")/"Kelvins"],
	"k"->Quantity[0.0054, "Watts"]/Quantity[1, "Centimeters"*"KelvinsDifference"],
	"w"->Quantity[50,"Grams"],
	"ρ"->Quantity[1.038,"Grams"/"Centimeters"^3],
	"h"->Quantity[1600,"Meters"]
};
res=FormulaData[{"EggBoilingTime","Altitude","CommonValues"},variablesAlt];
vals=Cases[res,HoldPattern[Equal[QuantityVariable["P","Pressure"],v_]]:> v,Infinity];
res/. QuantityVariable["P","Pressure"]->vals[[1]]

UnitConvert[(Quantity[417.5610370054755`, "s", seconds, "Seconds"]),"Minutes"]

UnitConvert[(Quantity[367.8733279217219`, "K", kelvins, "Kelvins"]),"Celsius"]

UnitConvert[(Quantity[367.8733279217219`, "K", kelvins, "Kelvins"]),"Fahrenheit"]

New Formulas Coming in Wolfram|Alpha

Calculating energy use and carbon footprint:

Boiling in water

Cold storage (refrigerator and freezer)

Cooking in an oven

Frying in a frying pan

Heating of water in electric kettle

Microwave cooking

Caloric content by food mass

Boiling a 50-gram egg in 650mL water for 6 minutes

Energy use

Boiling a 50-gram egg in 650mL water for 6 minutes

Carbon footprint

Boiling Egg PDE Model

Analyze the temperature evolution inside a boiling egg using the heat equation:

Needs["NDSolve`FEM`"]

eggShapePoints[L_, B_,w_] := Table[Evaluate[{Re[0.01 B (Sqrt[((z (L-z))/((L^2)+4 w (L-2 z)+4 (w^2)))])/. z-> z + L/2], 0.01 z}], 
      {z, -(L/2), (L/2),(L/200)}]

shellCoords=eggShapePoints[5, 4,-0.7];
shellSpline = BSplineCurve[shellCoords];

Compute the egg’s center of mass using the shell’s coordinates:

volumes=Table[π shellCoords[[i,1]]^2(shellCoords[[i+1,2]]-shellCoords[[i,2]]),{i,1,Length[shellCoords]-1}];
centerOfMassZ=Total[Table[shellCoords[[i,2]]*volumes[[i]],{i,1,Length[volumes]}]]/Total[volumes];

Define the domain for an axisymmetric model of the egg. The yolk region is modeled through a circle centered at the egg’s center of mass:

lineEdge = Line[{shellCoords[[1]], shellCoords[[-1]]}];
circleEdge = Circle[{0, centerOfMassZ}, 0.01,{-(π/2),(π/2)}];
regions=DiscretizeGraphics[Graphics[{shellSpline,lineEdge,circleEdge}]];

bmesh = ToBoundaryMesh[regions];

bmesh["Wireframe"["MeshElement"->"PointElements","MeshElementStyle"->PointSize[0.015],"MeshElementMarkerStyle"->Orange]];

Define a Dirichlet condition such that the outside of the egg is at the boiling temperature of water:

bcs=DirichletCondition[T[t,r,z]==N@QuantityMagnitude[(Quantity[100, "°C", degrees Celsius, "DegreesCelsius"]), "Kelvins"],ElementMarker==1];

materialRegions=<|"EggYolk"->1,"EggWhite"->2|>;

mesh = ToElementMesh[bmesh, "RegionMarker" -> {{{0.005, 0}, materialRegions["EggYolk"]}, {{0.015, 0}, materialRegions["EggWhite"]}}];
mesh["Wireframe"["MeshElementStyle" -> (Directive[EdgeForm[Gray], #] & /@ {FaceForm[Yellow], FaceForm[White]})]];

The values for the density, heat capacity and conductivity, for both egg white and yolk, depend on the temperature:

ρDataWhite=;
ρDataYolk=;
κDataWhite=;
κDataYolk=;

ρFunction=({{\[Piecewise], ({{Interpolation[ρDataYolk,][T[t,r,z]], ElementMarker==materialRegions["EggYolk"]}, {Interpolation[ρDataWhite,][T[t,r,z]], ElementMarker==materialRegions["EggWhite"]}})}})
κFunction=({{\[Piecewise], ({{Interpolation[κDataYolk,][T[t,r,z]], ElementMarker==materialRegions["EggYolk"]}, {Interpolation[κDataWhite,][T[t,r,z]], ElementMarker==materialRegions["EggWhite"]}})}})

Define a piecewise function for the heat capacity:

𝒞pFunction=({{\[Piecewise], ({{1933.42 +2.5 T[t,r,z], ElementMarker==materialRegions["EggYolk"]}, {2822.64+2.5 T[t,r,z], ElementMarker==materialRegions["EggWhite"]}})}});

vars={T[t,r,z],t,{r,z}};
pars=<|
"SpecificHeatCapacity"->𝒞pFunction,
"MassDensity"->ρFunction,
"ThermalConductivity"->IdentityMatrix[2]κFunction,
   "RegionSymmetry"->"Axisymmetric"|>;

op = HeatTransferPDEComponent[vars, pars];

Define the initial condition for a room-temperature egg:

initT=N@QuantityMagnitude[(Quantity[20, "°C", degrees Celsius, "DegreesCelsius"]),"Kelvins"]

ics={T[0,r,z]==initT};

tEnd=QuantityMagnitude[(Quantity[12, "min", minutes, "Minutes"]),"Seconds"];

Monitor[solution=NDSolveValue[{op==0,bcs,ics},T,{t,0,tEnd},{r,z}∈mesh,EvaluationMonitor:>(monitor=Row[{"t = ",CForm[t]}])],monitor]

minmax=MinMax[solution["ValuesOnGrid"]]

denatureTempWhite= QuantityMagnitude[(Quantity[60., "°C", degrees Celsius, "DegreesCelsius"]), "Kelvins"];

denatureTempYolk= QuantityMagnitude[(Quantity[76., "°C", degrees Celsius, "DegreesCelsius"]), "Kelvins"];

Animate the temperature evolution of the boiling egg:

nFrames=20;
frames=With[{rangeCelsius=QuantityMagnitude[Quantity[minmax,"Kelvins"],"DegreesCelsius"]},
Rasterize[Show[
ContourPlot[solution[#,r,z],{r,z}∈mesh,
PlotRange->(minmax+0.01*minmax*{-1,1}),
ColorFunction->ColorData[{"TemperatureMap",minmax}],ColorFunctionScaling->False,PlotLegends->BarLegend[{ColorData[{"TemperatureMap",rangeCelsius}],rangeCelsius},
LegendLabel->Quantity[None, "DegreesCelsius"]],AspectRatio->Automatic,PlotLabel->Text["Time t ="<>StringPadLeft[ToString[N[#]],4]<>"[s]"]],
ContourPlot[{solution[#,r,z]==denatureTempWhite,solution[#,r,z]==denatureTempYolk},{r,z}∈mesh,ContourStyle->{Directive[Green,Dashed],Directive[Magenta,Dashed]}]
(*bmesh["Wireframe"]*)],ImageResolution->144]&/@Range[0,tEnd,tEnd/nFrames]
];

The denature temperatures for the egg white and egg yolk are visualized as dashed green and magenta lines, respectively:

ListAnimate[frames, ]

Once a certain area of the egg white or egg yolk surpasses the corresponding denature temperature, that area is at least partially cooked:

Grid[{{"Starting condition","White denaturation","Yolk denaturation","Runny yolk"},{"Room temperature",(Quantity[4.8`, "min", minutes, "Minutes"]),(Quantity[9.`, "min", minutes, "Minutes"]),(Quantity[6.6`, "min", minutes, "Minutes"])}},]

What if the egg is taken directly from the refrigerator? Define a new initial condition:

initTFridge=N@QuantityMagnitude[(Quantity[4, "°C", degrees Celsius, "DegreesCelsius"]),"Kelvins"];
ics={T[0,r,z]==initTFridge}

Solve the PDE again:

Monitor[solutionFridge=NDSolveValue[{op==0,bcs,ics},T,{t,0,tEnd},{r,z}∈mesh,EvaluationMonitor:>(monitor=Row[{"t = ",CForm[t]}])],monitor]

Visualize the new solution:

nFrames=20;
framesFridge=With[{rangeCelsius=QuantityMagnitude[Quantity[minmax,"Kelvins"],"DegreesCelsius"]},
Rasterize[Show[
ContourPlot[solutionFridge[#,r,z],{r,z}∈mesh,
PlotRange->(minmax+0.01*minmax*{-1,1}),
ColorFunction->ColorData[{"TemperatureMap",minmax}],ColorFunctionScaling->False,PlotLegends->BarLegend[{ColorData[{"TemperatureMap",rangeCelsius}],rangeCelsius},
LegendLabel->Quantity[None, "DegreesCelsius"]],AspectRatio->Automatic,PlotLabel->Text["Time t ="<>StringPadLeft[ToString[N[#]],4]<>"[s]"]],
ContourPlot[{solutionFridge[#,r,z]==denatureTempWhite,solutionFridge[#,r,z]==denatureTempYolk},{r,z}∈mesh,ContourStyle->{Directive[Green,Dashed],Directive[Magenta,Dashed]}]
(*bmesh["Wireframe"]*)],ImageResolution->144]&/@Range[0,tEnd,tEnd/nFrames]
];

ListAnimate[framesFridge, ]

The new model demonstrates cooking time increases by about 0.6 minutes when the egg is taken straight from the refrigerator:

Grid[{{"Starting condition","White denaturation","Yolk denaturation","Runny yolk"},{"Room temperature",(Quantity[4.8`, "min", minutes, "Minutes"]),(Quantity[9.`, "min", minutes, "Minutes"]),(Quantity[6.6`, "min", minutes, "Minutes"])},{"Straight from fridge",(Quantity[5.4`, "min", minutes, "Minutes"]),(Quantity[9.6`, "min", minutes, "Minutes"]),(Quantity[7.2`, "min", minutes, "Minutes"])}},]

Therefore, however you like to cook your room-temperature eggs, add 40 seconds if they come out of the fridge.

Boiling an egg in 3D

Use the DensityPlot function to visualize a 3D version of the model:

egg3D[t_] := 
Module[{dp, outsideColor,eggShape,shell,cut,addy0,cut3D1,addx0,cut3D2,yolk,rangeCelsius},
outsideColor = ColorData[{"TemperatureMap",minmax}][solution[t,0,-2.5/100]];
dp=DensityPlot[solution[t,r,z],{r,z}∈mesh,
PlotRange->(minmax+0.01*minmax*{-1,1}),PlotPoints->60,
ColorFunction->ColorData[{"TemperatureMap",minmax}],ColorFunctionScaling->False,PlotLegends->BarLegend[{ColorData[{"TemperatureMap",minmax}],minmax}],AspectRatio->Automatic];
eggShape[z_]:=With[{L=5,B=4,w=-0.7},{Re[0.01 B Sqrt[((L/2-z) (L/2+z))/(L^2+4 w^2+4 w (L-2 (L/2+z)))]],0.01 z}];
shell=RevolutionPlot3D[Evaluate[eggShape[ z]],{z, -2.5, 2.5},{φ, 0, 3/2 Pi},Mesh -> False,PlotStyle->outsideColor];
cut=Cases[dp,_GraphicsComplex, ∞][[1]];
addy0[l_?MatrixQ] := {#1, 0, #2}&@@@ l;
cut3D1=MapAt[addy0, cut ,1];
addx0[l_?MatrixQ] := {0, -#1,#2}&@@@ l;
cut3D2=MapAt[addx0, cut ,1];
yolk={Glow[RGBColor[1.,0.86,0.08]], Opacity[0.4], Sphere[{0, 0,centerOfMassZ}, 0.01]};
rangeCelsius=QuantityMagnitude[UnitConvert[Quantity[minmax,"Kelvins"],"DegreesCelsius"]];
Style[Row[{Show[{shell, Graphics3D[{cut3D1,cut3D2, yolk}] },Lighting->"Neutral",Ticks->None,
ViewPoint -> {3, -3, 0.5}],
BarLegend[{ColorData[{"TemperatureMap",rangeCelsius}],rangeCelsius},
LegendLabel->Quantity[None, "DegreesCelsius"]]}],
ImageSizeMultipliers->1]
]

Internal egg temperatures at 2 minutes (120 sec)

egg3D[120]

For more PDE models in the Wolfram Language:

https://reference.wolfram.com/language/PDEModels/tutorial/PDEModelsOverview.html

Looking Ahead

Food and recipe analysis with semantic search and retrieval-augmented generation. First installment is available on Wolfram Community: Recipe management with SemanticSearchIndex and retrieval-augmented generation (RAG)

Wine-food pairings

Saturated and unsaturated fatty acids

Food chemicals

Carbon footprint of foods during production and processing