Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nnichols committed Feb 19, 2024
1 parent 7ea4a54 commit 9a3b070
Show file tree
Hide file tree
Showing 22 changed files with 225 additions and 98 deletions.
90 changes: 59 additions & 31 deletions src/brewtility/calculations.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@
[brewtility.units.options :as options]
[brewtility.units.time :as time]
[brewtility.units.volume :as volume]
[brewtility.units.weight :as weight]))
[brewtility.units.weight :as weight])
(:refer-clojure :exclude [time]))


(defn normalize-fermentable
"Given a `common-beer-format` conforming `fermentable`, normalize it for color computation."
"Given a `common-beer-format` conforming `fermentable`, normalize it for color computation.
If insufficient data is provided, this function will throw an exception."
{:added "1.0"}
[fermentable]
(let [is-not-grain? (not (fermentables/grain? fermentable))
kg->lbs (fn [w] (weight/convert w options/kilogram options/pound))] ; MCU is calculated against pounds
(cond-> fermentable
true (update :amount kg->lbs)
is-not-grain? (update :color #(color/convert % options/srm options/lovibond))))) ; Grain color is in Lovibond, all other fermentables use SRM
[{:keys [amount color] :as fermentable}]
(if (and (number? amount)
(number? color))
(let [is-not-grain? (not (fermentables/grain? fermentable))
; MCU is calculated against pounds
kg->lbs (fn [w] (weight/convert w options/kilogram options/pound))]
(cond-> fermentable
true (update :amount kg->lbs)
; Grain color is in Lovibond, all other fermentables use SRM
is-not-grain? (update :color #(color/convert % options/srm options/lovibond))))
(throw (ex-info "Cannot calculate color with non-numeric values" {:amount amount
:color color}))))

(defn calculate-malt-color-units
"Given a collection of `common-beer-format` conforming `fermentables`, and a conformed `batch-size` in liters, return the overall Malt Color Units for a recipe."
Expand Down Expand Up @@ -85,23 +93,31 @@
{:added "1.0"
:see-also ["gravity-points->potential-gravity"]}
[potential-gravity weight]
(let [weight-in-pounds (weight/convert weight options/kilogram options/pound)]
(-> potential-gravity
(* 1000)
(- 1000)
(* weight-in-pounds))))
(if (and (number? potential-gravity)
(number? weight))
(let [weight-in-pounds (weight/convert weight options/kilogram options/pound)]
(-> potential-gravity
(* 1000)
(- 1000)
(* weight-in-pounds)))
(throw (ex-info "Cannot calculate gravity points with non-numeric values" {:potential-gravity potential-gravity
:weight weight}))))


(defn gravity-points->potential-gravity
"Given the `gravity-points` of a recipe, and the `volume` of the batch, calculate the potential gravity."
{:added "1.0"
:see-also ["potential-gravity->gravity-points"]}
[gravity-points volume]
(let [volume-in-gallons (volume/convert volume options/litre options/american-gallon)]
(-> gravity-points
(/ volume-in-gallons)
(+ 1000)
(/ 1000.0))))
(if (and (number? gravity-points)
(number? volume))
(let [volume-in-gallons (volume/convert volume options/litre options/american-gallon)]
(-> gravity-points
(/ volume-in-gallons)
(+ 1000)
(/ 1000.0)))
(throw (ex-info "Cannot calculate potential gravity with non-numeric values" {:gravity-points gravity-points
:volume volume}))))


(defn calculate-potential-gravity
Expand Down Expand Up @@ -138,10 +154,10 @@
(calculate-potential-final-gravity fermentables batch-size default-attenuation))

([fermentables batch-size attenuation]
(let [gravity (calculate-potential-gravity fermentables batch-size)
gravity-points (-> gravity
(* 1000)
(- 1000))
(let [gravity (calculate-potential-gravity fermentables batch-size)
gravity-points (-> gravity
(* 1000)
(- 1000))
attenuated-points (* gravity-points attenuation)]

(-> gravity-points
Expand All @@ -152,7 +168,8 @@

(defn calculate-potential-abv
"Given a collection of `common-beer-format` conforming `fermentables`, and a conformed `batch-size` in liters, estimate the ABV.
The primary fermentation yeast's `attenuation` may also be passed, otherwise 75% is assumed."
The primary fermentation yeast's `attenuation` may also be passed, otherwise 75% is assumed.
If insufficient data is provided, this function will throw an exception."
{:added "1.0"}
([fermentables batch-size]
(calculate-potential-abv fermentables batch-size default-attenuation))
Expand Down Expand Up @@ -180,24 +197,35 @@
"Calculate the maximum amount of alpha acid released by `weight` ounce of a hop at `percent` alpha acid."
{:added "1.0"}
[weight alpha]
(let [weight-in-ounces (weight/convert weight options/kilogram options/ounce)
(let [weight-in-ounces (weight/convert weight options/kilogram options/ounce)
aau-normalization-factor 100]
(* aau-normalization-factor weight-in-ounces alpha)))


(defn calculate-ibu-per-hop
"Given a `common-beer-format` conforming `hop`, `batch-size`, and `potential-gravity`, calculate the amount of IBUs generated."
{:added "1.0"}
[hop batch-size potential-gravity]
(let [utilization (calculate-hop-utilization potential-gravity (:time hop))
alpha-acid-units (calculate-alpha-acid-units (:amount hop) (:alpha hop))
imperial-volume (volume/convert batch-size options/litre options/american-gallon)
conversion-factor 74.89]
(/ (* alpha-acid-units utilization conversion-factor) imperial-volume)))
[{:keys [time amount alpha] :as _hop} batch-size potential-gravity]
(if (and (number? time)
(number? amount)
(number? alpha)
(number? batch-size)
(number? potential-gravity))
(let [utilization (calculate-hop-utilization potential-gravity time)
alpha-acid-units (calculate-alpha-acid-units amount alpha)
imperial-volume (volume/convert batch-size options/litre options/american-gallon)
conversion-factor 74.89]
(/ (* alpha-acid-units utilization conversion-factor) imperial-volume))
(throw (ex-info "Cannot calculate IBUs with non-numeric values" {:amount amount
:time time
:alpha alpha
:batch-size batch-size
:potential-gravity potential-gravity}))))


(defn calculate-recipe-ibus
"Given a collection of `common-beer-format` conforming `hops`, `batch-size`, and `potential-gravity` calculate the amount of IBUs generated."
"Given a collection of `common-beer-format` conforming `hops`, `batch-size`, and `potential-gravity` calculate the amount of IBUs generated.
If insufficient data is provided, this function will throw an exception."
{:added "1.0"}
[hops batch-size potential-gravity]
(let [reducing-fn (fn [acc h] (+ acc (calculate-ibu-per-hop h batch-size potential-gravity)))]
Expand Down
23 changes: 15 additions & 8 deletions src/brewtility/precision.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@
"Determine if `n2` approximates `n1` within `variance` percent."
{:added "1.0"}
[n1 n2 variance]
(let [upper-bound (* n1 (+ 1.0 variance))
lower-bound (* n1 (- 1.0 variance))]
(<= lower-bound n2 upper-bound)))
(if (and (number? n1)
(number? n2)
(number? variance))
(let [upper-bound (* n1 (+ 1.0 variance))
lower-bound (* n1 (- 1.0 variance))]
(<= lower-bound n2 upper-bound))
(throw (ex-info "Cannot approximate using non-numeric values" {:n1 n1
:n2 n2
:variance variance}))))


(defn ->precision
"Given a decimal `x` and the number of decimal places, returns that number rounded to `num-decimals` precision."
{:added "1.0"}
{:added "1.0"
:see-also ["->1dp" "->2dp" "->3dp"]}
[^double x ^long num-decimals]
(double
#?(:clj (.setScale (bigdec x) num-decimals RoundingMode/HALF_UP)
:cljs (let [denominator (Math/pow 10.0 (double num-decimals))
numerator (Math/round (* x denominator))]
(/ numerator denominator)))))
#?(:clj (.setScale (bigdec x) num-decimals RoundingMode/HALF_UP)
:cljs (let [denominator (Math/pow 10.0 (double num-decimals))
numerator (Math/round (* x denominator))]
(/ numerator denominator)))))


(defn ->1dp
Expand Down
Loading

0 comments on commit 9a3b070

Please sign in to comment.