You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.6 KiB
263 lines
8.6 KiB
//Useful Websites |
|
// http://math.stackexchange.com/questions/406082/numerical-method-to-solve-a-trigonometric-cotangent-function-transient-heat |
|
// http://web.cecs.pdx.edu/~gerry/epub/pdf/transientConductionSphere.pdf |
|
// http://web.cecs.pdx.edu/~gerry/epub/ |
|
//http://highered.mcgraw-hill.com/sites/dl/free/0073129305/314124/cen29305_ch04.pdf |
|
//http://www.nt.ntnu.no/users/skoge/prost/proceedings/aiche-2005/topical/pdffiles/T9/papers/554a.pdf |
|
//http://www.rohanliston.com/portfolio/just-for-fun?id=23 |
|
|
|
//Global Variables for Turkey |
|
density = 1050; // kg/m3 Assuming Density of Water 1000 kg/m3 |
|
cp = 2000 // 2810 J/kg K for Turkey. Extra is to semi-account for water evaporation energy |
|
heatConvection = 9; // W/m2 K Some Reasonable estimate for natural Convection. Change as needed. 5-25 |
|
thermalConduct = 0.412 // W/m K // Chicken |
|
globalTime = 0; |
|
|
|
function celsiusToFarenheit(celsius) { |
|
farenheit = (celsius*(9/5)) + 32; |
|
return(farenheit) |
|
} |
|
|
|
function farenheitToCelsius (farenheit) { |
|
celsius = (farenheit*(9/5)) + 32; |
|
return(celsius) |
|
} |
|
|
|
function poundsToKilograms(pounds) { |
|
kilograms = (pounds * 0.453592); |
|
return(kilograms) |
|
} |
|
|
|
function calculateRadius(weight) { |
|
//Using Ratios for a rectangular Box Turkey |
|
ratioLvG=1.4; //1.4, Turkey length vs shoulder girth |
|
ratioLvH=2; //2, Turkey length vs height from resting position |
|
|
|
length = Math.pow(weight/((1/ratioLvG)*(1/ratioLvH)*density),(1/3)) |
|
depth = 1/(ratioLvG /length); |
|
height = 1/(ratioLvH /length); |
|
simpleRadius = length/2; //Doesn't take into account equal Volume |
|
|
|
rectangleVolume = depth*height*length; //m^3 Multiple by 1/3 to account for triangular shape and empty Space |
|
complexRadius = Math.pow(rectangleVolume/((4/3)*Math.PI), 1/3); //Volume of 3D Box = 3D Sphere |
|
|
|
console.log("Simple Radius " + simpleRadius + " Meters") |
|
console.log("Complex Radius " + complexRadius + " Meters") |
|
return(complexRadius) |
|
} |
|
|
|
|
|
function findAllRoots(min,max,splitsNum,Biot) { |
|
var step = ( max - min ) / ( splitsNum - 1 ); |
|
var storage = []; |
|
for (var i = step; i < max; i=i+step ) { |
|
negativeTest = lambdaFormula(i-step, Biot)*lambdaFormula(i, Biot); |
|
if (negativeTest <= 0) { |
|
answer = bisectionMethod(i-step,i,Biot); |
|
if (answer !=undefined) { |
|
storage.push(answer); |
|
} |
|
} |
|
else { |
|
//console.log("No Bracketed Root " + negativeTest) |
|
} |
|
} |
|
return(storage) |
|
} |
|
|
|
|
|
function bisectionMethod(min,max,Biot) { |
|
errorTolerance = (1/Math.pow(10,8)) |
|
result = Infinity // some large value to ensure the calculation goes through. |
|
negativeTest =lambdaFormula(min, Biot)*lambdaFormula(max, Biot) |
|
if (negativeTest <=0 ) { |
|
var antiFreeze=0; |
|
while (Math.abs(result) > errorTolerance && antiFreeze<=500) { //The greater the antiFreeze, the more wasted cycles around a singularity |
|
lambdaN = (min+max)/2 |
|
result=lambdaFormula(lambdaN, Biot) |
|
if (Math.abs(result) <= errorTolerance && result<=errorTolerance) { |
|
return (lambdaN); //At Root |
|
} |
|
else if ((lambdaFormula(min, Biot)*lambdaFormula(lambdaN, Biot))>=0) { |
|
min=lambdaN; |
|
} |
|
else if ((lambdaFormula(max, Biot)*lambdaFormula(lambdaN, Biot))>=0) { |
|
max=lambdaN; |
|
} |
|
antiFreeze++ |
|
} |
|
} |
|
} |
|
|
|
function lambdaFormula(lambdaN, Biot) { |
|
result = 1-lambdaN*(1/Math.tan(lambdaN))-Biot; |
|
return(result) |
|
} |
|
|
|
function oven() { |
|
this.tempInfini=20; //C |
|
this.setTemp = 20; |
|
this.steadyTemp = 20; |
|
this.steadyTimer = 0; |
|
var proportional = 0.004; // This value is arbitrary to how fast you want the temperatures to converge. (Or oscillate, which could be realistic as well) |
|
var errorTolerance = 10; //Stove is accurate to 25 degree Celcius Should hopefully oscillate below that value. |
|
|
|
this.changeTemp = function(setTemp) { |
|
this.setTemp = setTemp; |
|
} |
|
|
|
this.equalizeTemp = function() { // Equalize Temp will need to be sent each time iteration |
|
var error = Math.abs(this.setTemp-this.tempInfini); |
|
if (this.setTemp>this.tempInfini) { |
|
this.tempInfini = this.tempInfini + error*proportional; |
|
} |
|
else if (this.setTemp<this.tempInfini) { |
|
this.tempInfini = this.tempInfini - error*proportional; |
|
} |
|
if (error>errorTolerance) { |
|
return (true) //Need to run the Heat Calculations again next cycle |
|
} |
|
else { |
|
this.steadyTemp = this.tempInfini; |
|
} |
|
} |
|
} |
|
|
|
function layerModel(name,radiusPercentage) { |
|
this.name = name; |
|
this.radiusPercent=radiusPercentage; |
|
this.initialTemp = 20; |
|
this.waterLost = 0; |
|
this.finalTemperature = 20; |
|
} |
|
|
|
|
|
function turkeyModel(weight) { |
|
this.totalRadius = calculateRadius(weight) |
|
this.skin = new layerModel("Skin",0.85) |
|
this.body = new layerModel("Body",0.45) |
|
this.core = new layerModel("Core",0.01) |
|
|
|
this.cookCondition = function(cookValue) { |
|
var multiplier = 1; |
|
if (cookValue>=multiplier*250000) { |
|
return("House Fire") |
|
} |
|
else if(cookValue>=multiplier*150000) { |
|
return("Charcoal") |
|
} |
|
else if (cookValue>=multiplier*80000) { |
|
return("Dry") |
|
} |
|
else if (cookValue>=multiplier*18000) { |
|
return("Cooked") |
|
} |
|
else if (cookValue>=multiplier*5000) { |
|
return("Undercooked") |
|
} |
|
else { |
|
return("Raw") |
|
} |
|
} |
|
|
|
this.resetLayerTemps = function () { |
|
this.skin.initialTemp = this.skin.finalTemperature; |
|
this.body.initialTemp = this.body.finalTemperature; |
|
this.core.initialTemp = this.core.finalTemperature; |
|
} |
|
|
|
this.updateLayerTemps = function() { |
|
//Temperature Calculations |
|
this.skin.finalTemperature = transientSphereSeries (this.skin.radiusPercent*this.totalRadius,this.totalRadius,this.skin.initialTemp,ovenObject.steadyTemp,globalTime) |
|
this.body.finalTemperature = transientSphereSeries (this.body.radiusPercent*this.totalRadius,this.totalRadius,this.body.initialTemp,ovenObject.steadyTemp,globalTime) |
|
this.core.finalTemperature = transientSphereSeries (this.core.radiusPercent*this.totalRadius,this.totalRadius,this.core.initialTemp,ovenObject.steadyTemp,globalTime) |
|
|
|
//Water Loss Calculations |
|
this.skin.waterLost = this.skin.waterLost + waterLoss(this.skin.finalTemperature) |
|
this.body.waterLost = this.body.waterLost + waterLoss(this.body.finalTemperature) |
|
this.core.waterLost = this.core.waterLost + waterLoss(this.core.finalTemperature) |
|
|
|
console.log("Skin: "+ this.skin.waterLost + " " + turkey.cookCondition(this.skin.waterLost)); |
|
console.log("Body: "+ this.body.waterLost + " " + turkey.cookCondition(this.body.waterLost)); |
|
console.log("Core: "+ this.core.waterLost + " " + turkey.cookCondition(this.core.waterLost)); |
|
} |
|
} |
|
|
|
function sphereVolume (radius) { |
|
return((4/3)*Math.PI*Math.pow(radius,3)) |
|
} |
|
|
|
function sphereSurfaceArea(radius) { |
|
return (4*Math.PI*Math.pow(radius,2)) |
|
} |
|
|
|
function waterLoss(temperature) { |
|
return (Math.pow(10,(temperature-20)/80)-1) |
|
} |
|
|
|
var oldBiot=null; |
|
function transientSphereSeries (rPosition,rTotal,tempInitial,tempInfini,t) { |
|
var min = 0; |
|
var max = 10000; // This are for setting Lambda boundries and nothing else |
|
//thermalConduct = ((tempInitial-20)*(0.13/60)) + 0.32; |
|
var sum=0; |
|
var alpha = thermalConduct/(density*cp) |
|
//console.log("Alpha is " + alpha) |
|
|
|
var Fourier = (alpha*t)/Math.pow(rTotal,2) |
|
//console.log("Fourier is " + Fourier) |
|
|
|
var biotNum = heatConvection * rTotal/thermalConduct |
|
|
|
if (biotNum != oldBiot) { |
|
console.log("Recalculating Lambda Terms") |
|
lambdaTerms = findAllRoots(min,max,max*Math.PI*10,biotNum) |
|
oldBiot = biotNum; |
|
} |
|
|
|
//console.log("The Biot Value is " + biotNum) |
|
|
|
for (var i = 0; i<lambdaTerms.length; i++) { |
|
lambdaN = lambdaTerms[i] |
|
sinPortion= Math.sin(lambdaN*rPosition/rTotal)/(lambdaN*rPosition/rTotal); |
|
exponentialPortion = (1/Math.exp(Math.pow(lambdaN,2)*Fourier)) |
|
frontCoefficientPortion = 4*(Math.sin(lambdaN)-(lambdaN*Math.cos(lambdaN)))/ (2*lambdaN-Math.sin(2*lambdaN)) |
|
sum = frontCoefficientPortion*exponentialPortion*sinPortion + sum |
|
} |
|
tempAtTimeAndRadius=(sum*(tempInitial-tempInfini))+tempInfini |
|
|
|
console.log("The Temperature at radius " + rPosition + " m and time " + t/60/60 + " hours is " + tempAtTimeAndRadius + " C or " + celsiusToFarenheit(tempAtTimeAndRadius) + " F"); |
|
return(tempAtTimeAndRadius) |
|
} |
|
|
|
|
|
//Running the Program Stuff |
|
|
|
ovenObject = new oven (); |
|
turkey = new turkeyModel(7.257); |
|
|
|
setInterval(function(){time()},1000); |
|
|
|
totalCookTime = 0; |
|
scale = 1 |
|
function time() { |
|
console.clear() |
|
var equalized = ovenObject.equalizeTemp() |
|
if (ovenObject.steadyTimer>=80*scale && equalized) { |
|
ovenObject.steadyTimer = 0; |
|
ovenObject.steadyTemp = ovenObject.tempInfini |
|
turkey.resetLayerTemps(); |
|
globalTime = 0; //Reset the model's time calculation if there are major changes in the tolerance of the temperature |
|
} |
|
else { |
|
globalTime = globalTime + scale; |
|
ovenObject.steadyTimer = ovenObject.steadyTimer + scale; |
|
totalCookTime = totalCookTime + scale; |
|
} |
|
|
|
console.log(ovenObject.tempInfini) |
|
console.log(ovenObject.steadyTemp) |
|
console.log(ovenObject.steadyTimer) |
|
console.log(totalCookTime/60/60 +" hours") |
|
turkey.updateLayerTemps(); |
|
} |