1 ;;; brew-calc.el --- calc functions for brewing ber
3 ;; Copyright (C) 2009 Erik Hetzner
5 ;; Author: Erik Hetzner <ehetzner@gmail.com>
7 ;; This file is NOT part of GNU Emacs.
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
27 (org-cook-alist-append 'math-additional-units
28 '((degPlato nil
"Degrees Plato")))
30 (setq math-units-table nil
)
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
)))
40 (cond ((not old-units
)
41 (error "Not a temperature."))
42 ((eq (car old-units
) 'degF
)
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
)))
53 (cond ((not old-units
)
54 (error "Not a temperature."))
55 ((eq (car old-units
) 'degC
)
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
)))
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
)
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."
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
90 (let ((sg1 (brewSGCorr sg
)))
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
111 "Ensure that a value is in degrees Plato (degPlato)."
112 (if (eq (car (math-units-in-expr-p n t
)) 'degPlato
)
117 "Ensure that a value is in corrected, unitless, specific
119 (if (eq (car (math-units-in-expr-p n t
)) 'degPlato
)
123 ;;(defmath brewFpn (x)
124 ;; (let ((Pn (brewNorm (brew2P x))))
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))))
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
143 (brewAWFix oe ae
) ae
))
145 ;; From George Fix, http://hbd.org/hbd/archive/880.html#880-9, due to
147 (defmath brewRE
(oe ae
)
148 "Calculate real extraction, given original extract and apparent
150 (let ((oe1 (brewNorm (brew2P oe
)))
151 (ae1 (brewNorm (brew2P ae
))))
153 (* (+ (* :"0.1808" oe1
)
155 '(var degPlato var-degPlato
)))))
157 (defmath brewAWFix
(oe ae
)
158 "Calculate alc. by weight, given original extract and apparent
160 (let* ((oe1 (brewNorm (brew2P oe
)))
161 (ae1 (brewNorm (brew2P ae
)))
162 (re (brewNorm (brewRE oe ae
))))
165 (* :"0.010665" oe1
)))))
167 (defmath brewCalories
(oe ae
)
168 "Calculate cal/L, given original extract and apparent final
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
)
180 (defmath brewRA
(oe ae
)
181 "Calculate approximation to real attenuation."
182 (let ((re (brewNorm (brewRE oe ae
)))
183 (oe1 (brewNorm (brew2P oe
))))
186 (defmath brewAA
(oe ae
)
187 "Calculate approximate attenuation."
188 (let ((ae1 (brewNorm (brew2P ae
)))
189 (oe1 (brewNorm (brew2P oe
))))
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
))))
200 (- 1 (* (/ (+ tempn
:"288.9414")
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
212 (if (not (vectorp sg
))
214 (let ((ir (nth 1 sg
))
217 (/ (* (brewRhoCalc ct
) ir
)
220 (defmath brewTinsethBigness
(ex)
221 "Calculate the Tinseth 'bigness' AA utilization factor."
222 (let ((sg (brew2SG ex
)))
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")))
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
)
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
)))
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."
258 (let ((k_h (henryConst temp
))
259 (p (* 1 '(var atm var-atm
))))
262 (defmath brew2Volumes
(expr)
263 "Converts a gas in mol / L to volumes."
265 (let ((V0 (math-convert-units '(var V0 var-V0
)
267 (var mol var-mol
)))))
270 (defmath brew2molPerL
(expr)
272 (let ((V0 (math-convert-units '(var V0 var-V0
)
274 (var mol var-mol
)))))
277 (defmath brewCO2gPerL
(expr)
278 "Calculates g/L of CO2, given mol/L or volumes."
279 (let ((mm '(/ (* (float 4401 -
2)
282 (molPerL (if (math-units-in-expr-p expr t
)
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."
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
296 (let ((final-level (if (math-units-in-expr-p final-level1 t
)
298 (brewCO2gPerL final-level1
)))
299 (current-level (brewCO2gPerL (brewCarb temp
))))
302 (brewGramsGlucoseNeeded (* (- final-level current-level
)
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
))))
317 (room-temp (brew2K (* 74 '(var degF var-degF
)))))
318 (brew2F (math-simplify-units
319 (/ (- (* temp
(+ (* water water-sh
)
321 (* room-temp
(* malt malt-sh
)))
322 (* water-sh water
))))))