add ability to change malt specific heat; add brew2F, org-food -> org-cook
[org-brew.git] / brew-calc.el
blob79b562e627e7bd644bb2f7de6c973cf312c583da
1 ;;; brew-calc.el --- calc functions for brewing ber
2 ;;
3 ;; Copyright (C) 2009 Erik Hetzner
4 ;;
5 ;; Author: Erik Hetzner <ehetzner@gmail.com>
6 ;;
7 ;; This file is NOT part of GNU Emacs.
8 ;;
9 ;; This program is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ;; calc functions for brewing
23 (provide 'brew-calc)
24 (require 'calc)
25 (require 'org-cook)
27 (org-cook-alist-append 'math-additional-units
28 '((degPlato nil "Degrees Plato")))
30 (setq math-units-table nil)
32 (defmath brewNorm (v)
33 "Get the norm of a value with units."
34 (math-simplify (math-remove-units v)))
36 (defmath brew2F (temp)
37 "Ensure that a temperature is in degF."
38 (let ((old-units (math-units-in-expr-p temp t)))
39 (math-simplify
40 (cond ((not old-units)
41 (error "Not a temperature."))
42 ((eq (car old-units) 'degF)
43 temp)
44 ((eq (car old-units) 'degC)
45 (math-convert-temperature temp '(var degC var-degC) '(var degF var-degF)))
46 ((eq (car old-units) 'K)
47 (math-convert-temperature temp '(var K var-K) '(var degF var-degF)))))))
49 (defmath brew2C (temp)
50 "Ensure that a temperature is in degC."
51 (let ((old-units (math-units-in-expr-p temp t)))
52 (math-simplify
53 (cond ((not old-units)
54 (error "Not a temperature."))
55 ((eq (car old-units) 'degC)
56 temp)
57 ((eq (car old-units) 'degF)
58 (math-convert-temperature temp '(var degF var-degF) '(var degC var-degC)))
59 ((eq (car old-units) 'K)
60 (math-convert-temperature temp '(var K var-K) '(var degC var-degC)))))))
62 (defmath brew2K (temp)
63 "Ensure that a temperature is in degK."
64 (let ((old-units (math-units-in-expr-p temp t)))
65 (math-simplify
66 (cond ((not old-units)
67 (error "Not a temperature."))
68 ((eq (car old-units) 'degC)
69 (math-convert-temperature temp '(var degC var-degC) '(var K var-K)))
70 ((eq (car old-units) 'K)
71 temp)
72 ((eq (car old-units) 'degF)
73 (math-convert-temperature temp '(var degF var-degF) '(var K var-K)))))))
75 (defmath brew2MG_L (expr)
76 "Ensure that a mass / vol is in mg / L."
77 (math-convert-units
78 expr
79 '(/ (var mg var-m)
80 (var L var-L))))
82 (defmath brew2min (expr)
83 "Ensure that a time is in minutes."
84 (math-convert-units expr '(var min var-min)))
86 ;; From Wikipedia, Plato_scale
87 (defmath brewSG2P (sg)
88 "Convert from Specific gravity to Plato, correcting SG vector
89 if necessary."
90 (let ((sg1 (brewSGCorr sg)))
91 (* (+ :"-616.868"
92 (+ (* :"1111.14" sg1)
93 (+ (* :"-630.272"
94 (^ sg1 2))
95 (+ (* :"135.997"
96 (^ sg1 3))))))
97 '(var degPlato var-degPlato))))
99 ;; From Wikipedia, Plato_scale
100 (defmath brewP2SG (p)
101 "Convert Plato (degPlato) to specific gravity (unitless)."
102 (let ((pn (brewNorm p)))
103 (math-simplify (/ (- 668
104 (nroot (- (^ 668 2)
105 (* 820
106 (+ 463 pn)))
108 410))))
110 (defmath brew2P (n)
111 "Ensure that a value is in degrees Plato (degPlato)."
112 (if (eq (car (math-units-in-expr-p n t)) 'degPlato)
114 (brewSG2P n)))
116 (defmath brew2SG (n)
117 "Ensure that a value is in corrected, unitless, specific
118 gravity points."
119 (if (eq (car (math-units-in-expr-p n t)) 'degPlato)
120 (brewP2SG n)
121 (brewSGCorr n)))
123 ;;(defmath brewFpn (x)
124 ;; (let ((Pn (brewNorm (brew2P x))))
125 ;; (+ :"0.48394"
126 ;; (+ (* :"0.0024688" Pn)
127 ;; (* :"0.00001561" (^ Pn 2))))))
129 ;;(defmath brewAW (P n)
130 ;; (let ((P1 (brewNorm (brew2P P)))
131 ;; (n1 (brewNorm (brew2P n))))
132 ;; (* (brewFpn P1)
133 ;; (- P1 n1))))
135 (defmath brewAW2AV (aw ae)
136 "Convert Alcohol by weight to alcohol by volume."
137 (* aw (/ (brew2SG ae) :"0.79661")))
139 (defmath brewAV (oe ae)
140 "Calculate alc by vol. given original extract and apparent
141 final extract."
142 (brewAW2AV
143 (brewAWFix oe ae) ae))
145 ;; From George Fix, http://hbd.org/hbd/archive/880.html#880-9, due to
146 ;; Balling.
147 (defmath brewRE (oe ae)
148 "Calculate real extraction, given original extract and apparent
149 final extract."
150 (let ((oe1 (brewNorm (brew2P oe)))
151 (ae1 (brewNorm (brew2P ae))))
152 (math-simplify
153 (* (+ (* :"0.1808" oe1)
154 (* :"0.8192" ae1))
155 '(var degPlato var-degPlato)))))
157 (defmath brewAWFix (oe ae)
158 "Calculate alc. by weight, given original extract and apparent
159 final extract."
160 (let* ((oe1 (brewNorm (brew2P oe)))
161 (ae1 (brewNorm (brew2P ae)))
162 (re (brewNorm (brewRE oe ae))))
163 (/ (- oe1 re)
164 (- :"2.0665"
165 (* :"0.010665" oe1)))))
167 (defmath brewCalories (oe ae)
168 "Calculate cal/L, given original extract and apparent final
169 extract."
170 (let* ((fg (brew2SG ae))
171 (a (brewAWFix oe ae))
172 (re (brewNorm (brewRE oe ae))))
173 (* (* (* (+ (* :"6.9" a)
174 (* :"4.0" (- re :".1")))
177 (/ '(var Kcal var-Kcal)
178 '(var L var-L)))))
180 (defmath brewRA (oe ae)
181 "Calculate approximation to real attenuation."
182 (let ((re (brewNorm (brewRE oe ae)))
183 (oe1 (brewNorm (brew2P oe))))
184 (- 1 (/ re oe1))))
186 (defmath brewAA (oe ae)
187 "Calculate approximate attenuation."
188 (let ((ae1 (brewNorm (brew2P ae)))
189 (oe1 (brewNorm (brew2P oe))))
190 (- 1 (/ ae1 oe1))))
193 ;; Per http://www.csgnetwork.com/h2odenscalc.html and
194 ;; http://www.earthwardconsulting.com/density.xls
195 (defmath brewRhoCalc (temp)
196 "Calculate factor by which water at a given temp differs from
197 water at ref. temp (~4 degC)"
198 (let ((tempn (brewNorm (brew2C temp))))
199 (math-simplify
200 (- 1 (* (/ (+ tempn :"288.9414")
201 (* :"508929.2"
202 (+ tempn :"68.12963")))
203 (^ (- tempn :"3.9863") 2))))))
205 ;; Follows from above, assuming that beer has the same relationship to
206 ;; temperature as water does.
207 (defmath brewSGCorr (sg)
208 "Ensures that specific gravity is in a corrected, unitless
209 number, rather than a vector containing measured gravity, temp of
210 sampled liquid, and calibration temp, e.g. [1.010, 74 degF, 20
211 degC]"
212 (if (not (vectorp sg))
214 (let ((ir (nth 1 sg))
215 (it (nth 2 sg))
216 (ct (nth 3 sg)))
217 (/ (* (brewRhoCalc ct) ir)
218 (brewRhoCalc it)))))
220 (defmath brewTinsethBigness (ex)
221 "Calculate the Tinseth 'bigness' AA utilization factor."
222 (let ((sg (brew2SG ex)))
223 (* :"1.65"
224 (^ :"0.000125"
225 (- sg 1)))))
227 (defmath brewTinsethBoilTime (time)
228 "Calculate the Tinseth 'boil time' AA utilization factor."
229 (let ((min (brewNorm (brew2min time))))
230 (/ (- 1 (^ e (* min :"-0.04")))
231 :"4.15")))
233 (defmath brewTinsethAAUtil (ex m)
234 "Calculate the Tinseth AA utilization factor."
235 (* (brewTinsethBigness ex)
236 (brewTinsethBoilTime m)))
238 (defmath brewTinsethIBU (time hop-mass aa boil-size ex)
239 "Calculate the IBUs given a time, mass of hops, aa of hops,
240 size of boil, and extract (SG or Plato)."
241 (* (brewTinsethAAUtil ex time)
242 (brew2MG_L
243 (/ (* aa hop-mass) boil-size))))
245 ;; See http://en.wikipedia.org/wiki/Henry%27s_Law. For CO_2.
246 (defmath henryConst (temp1)
247 (let ((temp_st (* 298 '(var degK var-degK)))
248 (temp (brew2K temp1))
249 (k_h '(/ (* (float 294 -1) (* (var L var-L) (var atm var-atm)))
250 (var mol var-mol))))
251 (math-simplify (* k_h (^ e (* -2400
252 (- (/ 1 (math-remove-units temp))
253 (/ 1 (math-remove-units temp_st)))))))))
255 (defmath brewCarb (temp)
256 "Calculate equilibrium carbonation at 1 atm."
257 (brew2Volumes
258 (let ((k_h (henryConst temp))
259 (p (* 1 '(var atm var-atm))))
260 (/ p k_h))))
262 (defmath brew2Volumes (expr)
263 "Converts a gas in mol / L to volumes."
264 (math-simplify-units
265 (let ((V0 (math-convert-units '(var V0 var-V0)
266 '(/ (var L var-L)
267 (var mol var-mol)))))
268 (* V0 expr))))
270 (defmath brew2molPerL (expr)
271 (math-simplify-units
272 (let ((V0 (math-convert-units '(var V0 var-V0)
273 '(/ (var L var-L)
274 (var mol var-mol)))))
275 (/ expr V0))))
277 (defmath brewCO2gPerL (expr)
278 "Calculates g/L of CO2, given mol/L or volumes."
279 (let ((mm '(/ (* (float 4401 -2)
280 (var g var-g))
281 (var mol var-mol)))
282 (molPerL (if (math-units-in-expr-p expr t)
283 expr
284 (brew2molPerL expr))))
285 (math-simplify-units (* molPerL mm))))
287 ;; From http://oz.craftbrewer.org/Library/Methods/BulkPriming/TechnicalGuide.shtml
288 (defmath brewGramsGlucoseNeeded (expr)
289 "Calculate the grams of glucose needed per g CO_2 desired."
290 (* expr :"2.16"))
292 (defmath brewPrimingCalc (final-level1 temp volume)
293 "Given a final CO_2 level (in volumes or g/L), a temperature,
294 and a volume of beer, return grams of glucose necessary for
295 priming."
296 (let ((final-level (if (math-units-in-expr-p final-level1 t)
297 final-level1
298 (brewCO2gPerL final-level1)))
299 (current-level (brewCO2gPerL (brewCarb temp))))
300 (math-simplify-units
301 (math-simplify
302 (brewGramsGlucoseNeeded (* (- final-level current-level)
303 volume))))))
305 (defvar brew-calc-malt-default-specific-heat
306 (math-read-number "0.40")
307 "The default value for the specific heat of malt; can vary from
308 0.38 to 0.42 depending on moisture.")
310 (defmath brewCalcStrike (temp water-vol malt)
311 (brewCalcStrikeFull temp water-vol malt brew-calc-malt-default-specific-heat))
313 (defmath brewCalcStrikeFull (temp1 water-vol malt malt-sh)
314 (let* ((temp (brew2K temp1))
315 (water (* water-vol (/ '(var kg var-kg) '(var L var-L))))
316 (water-sh 1)
317 (room-temp (brew2K (* 74 '(var degF var-degF)))))
318 (brew2F (math-simplify-units
319 (/ (- (* temp (+ (* water water-sh)
320 (* malt malt-sh)))
321 (* room-temp (* malt malt-sh)))
322 (* water-sh water))))))