Created
October 28, 2011 12:26
-
-
Save markusl/1322155 to your computer and use it in GitHub Desktop.
Economic order quantity calculator in F#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Read more about EOQ from http://en.wikipedia.org/wiki/Economic_order_quantity | |
/// Using functional approach to handle user interface interaction using reactive programming style. | |
/// Any comments and improvements welcome :-) | |
/// -Markus L/2011 | |
#if INTERACTIVE | |
#r "PresentationCore" | |
#r "PresentationFramework" | |
#r "System.Xaml" | |
#r "UIAutomationTypes" | |
#r "WindowsBase" | |
#endif | |
open System.Windows | |
open System.Windows.Controls | |
open System.Windows.Data | |
open System.Windows.Media | |
open System.Windows.Shapes | |
open Microsoft.Win32 | |
[<Measure>] | |
type Money | |
(** Struct for storing data for doing Economic order quantity calculations. | |
* Read more from http://en.wikipedia.org/wiki/Economic_order_quantity *) | |
type EOQ = { | |
(** Annual demand quantity *) | |
d : float; | |
(** Purchase cost per unit *) | |
p : float<Money>; | |
(** Fixed cost per order *) | |
s : float<Money>; | |
(** Annual holding cost (percent of unit price) *) | |
f : float; | |
} | |
with | |
(** Annual holding cost/unit *) | |
member this.h = this.f * this.p | |
(** Calculate the optimal order quantity *) | |
member this.EOQ = sqrt((2. * this.d * this.s)/(this.h)) | |
member this.costPerYear quantity = (this.s * this.d / quantity) + 0.5*quantity*this.h | |
static member Construct = { new EOQ | |
with d = 70. | |
and p = 15.5<Money> | |
and s = 5.<Money> | |
and f = 0.1 | |
} | |
end | |
/// Type for visualizing EOQ data | |
type EOQ_Render() = | |
do () | |
(** Renders the given EOQ data to a canvas that is then returned *) | |
static member renderScene (data:EOQ) width height = | |
/// Get TextBlock describing the input values | |
let getEOQDescription (eoq:EOQ) = | |
let text = (sprintf"Annual demand: %f\r\nPurchase cost/unit: %f\r\nCost per order: %f\r\nHolding cost: %f" | |
eoq.d (eoq.p/1.<Money>) (eoq.s/1.<Money>) eoq.f) | |
new TextBlock(Text=text, Foreground=SolidColorBrush(Colors.WhiteSmoke)) | |
/// Get the brush to draw a single column, returns different brush for column that is emphasized | |
let graphBrush highlight = if highlight then SolidColorBrush(Color.FromRgb(27uy, 170uy, 90uy)) | |
else SolidColorBrush(Colors.White) | |
let backgroundBrush = SolidColorBrush(Color.FromRgb(14uy, 109uy, 56uy)) | |
/// Render EOQ data visually in columns | |
let createBars (sceneCanvas:Controls.Panel) best (nth,value) = | |
let groundLevel = height-40. | |
let x = nth * 4 + 2 | |
let line = new Line(X1 = float x, Y1 = groundLevel, X2=float x, Y2 = groundLevel - value, | |
StrokeThickness = 2., Stroke=graphBrush (nth = best)) | |
sceneCanvas.Children.Add(line) |> ignore | |
let sceneCanvas = new Canvas(Background=backgroundBrush, Width=width, Height=height) | |
let values = [1 .. 100] |> List.map (fun nth -> nth,(data.costPerYear ((float)nth)) / 1.<Money>) | |
let best = values |> List.sortBy snd |> List.head |> fst | |
values |> List.iter (createBars sceneCanvas best) | |
sceneCanvas.Children.Add(getEOQDescription data) |> ignore | |
sceneCanvas | |
type EOQWindow() as this = | |
inherit Window() | |
/// Define controls with labels, min-max values and initial values and change action | |
let controlsAndActions = [(1., 100., 70., "Annual demand", (fun newValue (eoq:EOQ) -> {eoq with d = newValue})); | |
(5., 200., 15.5, "Purchase cost/unit", (fun newValue (eoq:EOQ) -> {eoq with p = newValue*1.<Money>})); | |
(1., 40., 5., "Cost per order", (fun newValue (eoq:EOQ) -> {eoq with s = newValue*1.<Money>})); | |
(0.05, 0.95, 0.1, "Holding cost%", (fun newValue (eoq:EOQ) -> {eoq with f = newValue}))] | |
/// Create controls on given panel, add defined change action and merge all observables | |
let createAndGetControlEvents (panel:Controls.Panel) = | |
let createControlAndRegisterEvent (min, max, initial, label, action) = | |
panel.Children.Add(new Label(Content=label)) |> ignore | |
let slider = new Slider(Minimum=min, Maximum=max, Value=initial) | |
panel.Children.Add(slider) |> ignore | |
slider.ValueChanged |> Observable.map (fun rea -> action rea.NewValue) | |
controlsAndActions | |
|> List.map (createControlAndRegisterEvent) | |
|> List.reduce Observable.merge | |
/// Re-render the screen with changed data | |
let renderScene (mainPanel:Controls.Panel) data = | |
mainPanel.Children.RemoveAt(1) | |
mainPanel.Children.Add(EOQ_Render.renderScene data (this.Width-70.) this.Height) |> ignore | |
let initControlEventsAndAddListener leftPanel mainPanel = | |
let controlEvents = createAndGetControlEvents leftPanel | |
controlEvents | |
|> Observable.scan (fun a b -> b a) (EOQ.Construct) | |
|> Observable.add (renderScene mainPanel) | |
let createMainPanel = | |
let mainPanel = new StackPanel(Orientation = Orientation.Horizontal, VerticalAlignment = VerticalAlignment.Top) | |
let leftPanel = new StackPanel(Orientation = Orientation.Vertical) | |
initControlEventsAndAddListener leftPanel mainPanel | |
mainPanel.Children.Add(leftPanel) |> ignore | |
mainPanel.Children.Add(new TextBlock(Text="\r\n\r\n\r\n«« Start by modifying the values on the left")) |> ignore | |
mainPanel | |
do | |
this.Content <- createMainPanel | |
[<System.STAThread>] | |
do | |
(new Application()).Run(EOQWindow(Width=500., Height=350., Title="EOQ calculator")) |> ignore |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Screenshot http://img20.imageshack.us/img20/2457/eoqcalc.png