Boiling Sous-Vide Eggs Using Clojure's Transducers
blog.eikeland.se
blog.eikeland.se
It is also used quite a bit in capacity allocation in a wide variety of industries. Instead of using NP Complete scheduling and discrete optimization that might not terminate, you take a page out of Hayek and use the PID algorithm to set a price on the capacity of each independent asset. Then, scheduling and allocation often becomes a matter of convex linear optimization.
In terms of practical use, I have zero doubt in my mind that the PID algorithm accounts for more economic impact than Page Rank or many other HN-popular algorithms. Just adding up the value of applications that I personally know about, it saves its users > $10B/year. I would wager that the only algorithm that surpasses its economic impact would be Simplex.
It is also incredibly extensible. I've seen variants where the integral term is swapped out with fourier transforms, or the derivative term is swapped out with ARIMAs or Hierarchical Forecasting algorithms or Neural Networks. You can hack the shit out of it, tailoring it to even the most obscure use cases if you wanted to. Like, for example, cooking an egg.
I was surprised to learn that it only dates back to the 1890's (according to Wikipedia). Seems to me like the Greeks or Romans would have figured it out already.
One of the beauties of the algorithm is how well it scales to complexity. It can be implemented in something as simple as a mechanical or hydrolic controller or DSP, but can also be used in heavily modified forms that take advantage of advances in Machine Learning, OR/Optimization, etc.
There's no natural resonance in the system caused by energy-storing elements. For instance, if you drop a cold egg into a pot of water at exactly 80 C, it won't overshoot and go up to 85 C before settling to 80 C. Mechanical systems on the other hand often have "spring-like" behavior that causes them to resonate at certain frequencies.
If you want to get hardcore with the control design then you could look into fuzzy logic controllers. It's common in high-end appliances: http://en.wikipedia.org/wiki/Fuzzy_control_system
[1] https://learn.adafruit.com/sous-vide-powered-by-arduino-the-... [2] http://seattlefoodgeek.com/2010/09/deep-fried-sous-vide-egg-...
(defn pid-transducer [set-point k-p k-i k-d]
(fn [xf]
(let [pid (volatile! (make-pid set-point k-d k-i k-d))]
(fn
([] (xf))
([result] (xf result))
([result input]
(vswap! pid (fn [p] (calculate-pid p input)))
(xf result (:output @pid)))))))
If guessing that the first 'k-d' on the third line should be a 'k-p'. I'm glad this bug didn't mess up your eggs!Running a linter (such as eastwood) in clojure does the trick:
$ lein eastwood '{:linters [:unused-fn-args]}'
== Eastwood 0.1.4 Clojure 1.7.0-alpha2 JVM 1.8.0_05
== Linting pid.core ==
{:linter :unused-fn-args,
:msg
"Function args [k-p (line 32, column 33)] of (or within) pid-transducer are never used",
:file "pid/core.clj",
:line 32,
:column 1}
Sadly it only works if you actually use it, and I'm a lazy bastard :pAlso, the sensor might not be calibrated correctly, I have a reference thermometer (therma-pen), but won't bother calibrating a toy setup when I have a proper one ;)
http://www.cookingissues.com/2011/05/03/tea-time-cooking-iss...
http://www.splendidtable.org/story/theres-more-than-one-way-...
he has a video demonstrating temperatures https://www.youtube.com/watch?v=GpvbNG1Dzhk
Guess it boiles down to personal preference
Over at the Food Lab, Kenji did some research - via mixing methods you can manage to set the outer white at those temperatures and not just the inner white: http://www.seriouseats.com/2013/10/sous-vide-101-all-about-e...
Personally I normally cook mine to 63, and yes the white can sometimes not be perfectly set. As with everything there is some unit to unit variation.
(def pid-output (chan (pid-transducer 65 0.1 0.02 0.01)))
Unless something has changed with core async I believe you need to provide a buffer if you are going to use a transducer.From the current doc: "If a transducer is supplied a buffer must be specified."
The record stayed though - but there's no reason why it couldn't simply be a map.
(defalias Pid
(HMap
:complete? true
:mandatory {:set-point Num
:k-p Num
:k-i Num
:k-d Num
:error-sum Num
:error-last Num
:output-max Num
:output Num}))
And then use it as you might expect: (ann calculate-pid [Pid Num -> Pid])
There's no real downside to using a record, but they're really only necessary for polymorphism.