Skip to content

Commit

Permalink
Merge pull request #315 from google-research/dex-plot-library
Browse files Browse the repository at this point in the history
Implement plotting in Dex instead of Haskell
  • Loading branch information
dougalm authored Dec 11, 2020
2 parents 8abae5d + 283c9e4 commit 20940b8
Show file tree
Hide file tree
Showing 32 changed files with 813 additions and 288 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
os: [ubuntu-18.04, macos-latest]
include:
- os: macos-latest
install_deps: brew install llvm@9
install_deps: brew install llvm@9 pkg-config
path_extension: $(brew --prefix llvm@9)/bin
- os: ubuntu-18.04
install_deps: sudo apt-get install llvm-9-tools llvm-9-dev
install_deps: sudo apt-get install llvm-9-tools llvm-9-dev pkg-config
path_extension: /usr/lib/llvm-9/bin

runs-on: ${{ matrix.os }}
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ or these example programs:
* [Estimating pi](https://google-research.github.io/dex-lang/pi.html)
* [Hamiltonian Monte Carlo](https://google-research.github.io/dex-lang/mcmc.html)
* [ODE integrator](https://google-research.github.io/dex-lang/ode-integrator.html)
* [Sierpinsky triangle](https://google-research.github.io/dex-lang/sierpinsky.html)
* [Sierpinski triangle](https://google-research.github.io/dex-lang/sierpinski.html)
* [Basis function regression](https://google-research.github.io/dex-lang/regression.html)
* [Brownian bridge](https://google-research.github.io/dex-lang/brownian_motion.html)

Expand All @@ -30,6 +30,7 @@ development. Contributions welcome!
* Install LLVM 9
* `apt-get install llvm-9-dev` on Ubuntu/Debian,
* `brew install llvm@9` on macOS, and ensure it is on your `PATH` e.g. via `export PATH="$(brew --prefix llvm@9)/bin:$PATH"` before building.
* Install libpng (often included by default in *nix)

## Building

Expand Down
6 changes: 3 additions & 3 deletions dex.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@ library
Parser, Util, Imp, Imp.Embed, Imp.Optimize,
PPrint, Algebra, Parallelize, Optimize, Serialize
Actor, Cat, Flops, Embed,
RenderHtml, Plot, LiveOutput, Simplify, TopLevel,
RenderHtml, LiveOutput, Simplify, TopLevel,
Autodiff, Interpreter, Logging, PipeRPC, CUDA
build-depends: base, containers, mtl, binary, bytestring,
time, tf-random, llvm-hs-pure ==9.*, llvm-hs ==9.*,
aeson, megaparsec >=8.0, warp, wai, filepath,
parser-combinators, http-types, prettyprinter, text,
blaze-html, cmark, diagrams-lib, ansi-terminal,
diagrams-rasterific, JuicyPixels, transformers,
base64-bytestring, vector, directory, mmap, unix,
transformers, directory, mmap, unix,
process, primitive, store, dex-resources, temporary,
if !os(darwin)
exposed-modules: Resources
Expand All @@ -56,6 +55,7 @@ library
cxx-options: -std=c++11 -fPIC
default-extensions: CPP, DeriveTraversable, TypeApplications, OverloadedStrings,
TupleSections, ScopedTypeVariables, LambdaCase, PatternSynonyms
pkgconfig-depends: libpng
if flag(cuda)
include-dirs: /usr/local/cuda/include
extra-libraries: cuda
Expand Down
2 changes: 1 addition & 1 deletion examples/brownian_motion.dx
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ xs = linspace (Fin 1000) 0.0 1.0
ys = map (sampleBM (newKey 0)) xs

-- :plot zip xs ys
-- > <graphical output>
-- > <html output>
181 changes: 181 additions & 0 deletions examples/diagram.dx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'# Vector graphics library

Point : Type = (Float & Float)

data Geom =
PointGeom
Circle Float
Rectangle Float Float -- width, height
Line Point

-- HTML color string. TODO: consider an RGB triple instead
HtmlColor : Type = String

-- TODO: we should add overloaded string literals so we don't need this
def str (n:Int) ?-> (s:(Fin n=>Char)) : String = AsList _ s

black : HtmlColor = str "black"
white : HtmlColor = str "white"
gray : HtmlColor = str "gray"
red : HtmlColor = str "red"
green : HtmlColor = str "green"
blue : HtmlColor = str "blue"

GeomStyle : Type =
{ fillColor : Maybe HtmlColor
& strokeColor : Maybe HtmlColor
& strokeWidth : Int }

defaultGeomStyle : GeomStyle =
{ fillColor = Nothing
, strokeColor = Just black
, strokeWidth = 1 }

-- TODO: consider sharing attributes among a set of objects for efficiency
data Diagram = MkDiagram (List (GeomStyle & Point & Geom))

instance monoidDiagram : Monoid Diagram where
mempty = MkDiagram mempty
mcombine = \(MkDiagram d1) (MkDiagram d2). MkDiagram $ d1 <> d2

def concatDiagrams (diagrams:n=>Diagram) : Diagram =
MkDiagram $ concat for i.
(MkDiagram d) = diagrams.i
d

-- TODO: arbitrary affine transformations. Our current representation of
-- rectangles and circles means we can only do scale/flip/rotate90.
-- Should we use lenses/isomorphisms for these instead?
def applyTransformation
(transformPoint: Point -> Point)
(transformGeom: Geom -> Geom)
(d:Diagram) : Diagram =
(MkDiagram (AsList _ objs)) = d
(MkDiagram $ AsList _ for i.
(attr, p, geom) = objs.i
(attr, transformPoint p, transformGeom geom))

flipY : Diagram -> Diagram =
applyTransformation (\(x,y). (x, -y)) \geom. case geom of
PointGeom -> PointGeom
Circle r -> Circle r
Rectangle w h -> Rectangle w h
Line (x, y) -> Line (x, -y)

def scale (s:Float) : (Diagram -> Diagram) =
applyTransformation ( \(x,y). (s * x, s * y) ) \geom. case geom of
PointGeom -> PointGeom
Circle r -> Circle (s * r)
Rectangle w h -> Rectangle (s * w) (s * h)
Line (x, y) -> Line (s * x, s * y)

def moveXY ((offX, offY) : Point) : (Diagram -> Diagram) =
applyTransformation (\(x,y). (x + offX, y + offY) ) id

def singletonDefault (geom:Geom) : Diagram =
MkDiagram $ AsList _ [(defaultGeomStyle, (0.0, 0.0), geom)]

-- TODO: should really allow nullary functions with `def`
pointDiagram : Diagram = singletonDefault PointGeom
def circle (r:Float) : Diagram = singletonDefault $ Circle r
def rect (w:Float) (h:Float) : Diagram = singletonDefault $ Rectangle w h
def line (p:Point) : Diagram = singletonDefault $ Line p

def updateGeom (update: GeomStyle -> GeomStyle) (d:Diagram) : Diagram =
(MkDiagram (AsList _ objs)) = d
MkDiagram $ AsList _ for i.
(attr , geoms) = objs.i
(update attr, geoms)

def setFillColor (c:HtmlColor) : Diagram -> Diagram = updateGeom $ setAt #fillColor (Just c)
def setStrokeColor (c:HtmlColor) : Diagram -> Diagram = updateGeom $ setAt #strokeColor (Just c)
def setStrokeWidth (w:Int) : Diagram -> Diagram = updateGeom $ setAt #strokeWidth w
-- TODO: argumentless def!
removeStroke : Diagram -> Diagram = updateGeom $ setAt #strokeColor Nothing
removeFill : Diagram -> Diagram = updateGeom $ setAt #fillColor Nothing

'## Serialization to SVG string

def quote (s:String) : String = str "\"" <> s <> str "\""

def (<+>) (s1:String) (s2:String) : String = s1 <> str " " <> s2

def selfClosingBrackets (s:String) : String = str "<" <> s <> str "/>"

def tagBrackets (tag:String) (s:String) : String =
str "<" <> tag <> str ">" <> s <> str "</" <> tag <> str ">"

def tagBracketsAttr (tag:String) (attr:String) (s:String) : String =
str "<" <> tag <+> attr <> str ">" <> s <> str "</" <> tag <> str ">"

def makeAttr (attr:String) (val:String) : String =
attr <> str "=" <> quote val

def optionalStr (c: Maybe String) : String =
case c of
Nothing -> str "none"
Just s -> s

def attrString (attr:GeomStyle) : String =
( makeAttr (str "stroke") (optionalStr $ getAt #strokeColor attr)
<+> makeAttr (str "fill") (optionalStr $ getAt #fillColor attr)
<+> makeAttr (str "stroke-width") (show $ getAt #strokeWidth attr))

def pointAttrString (attr:GeomStyle) : String =
( makeAttr (str "stroke") (optionalStr $ getAt #strokeColor attr)
<+> makeAttr (str "fill") (optionalStr $ getAt #strokeColor attr)
<+> makeAttr (str "stroke-width") (show $ getAt #strokeWidth attr))

def renderGeom (attr:GeomStyle) ((x,y):Point) (geom:Geom) : String =
case geom of
PointGeom ->
tagBracketsAttr (str "g") (pointAttrString attr) $ selfClosingBrackets $
(str "circle" <+>
str "cx=" <> quote (show x) <>
str "cy=" <> quote (show y) <>
str "r=\"1\"")
Circle r ->
tagBracketsAttr (str "g") (attrString attr) $ selfClosingBrackets $
(str "circle" <+>
str "cx=" <> quote (show x) <>
str "cy=" <> quote (show y) <>
str "r=" <> quote (show r))
Rectangle w h ->
tagBracketsAttr (str "g") (attrString attr) $ selfClosingBrackets $
(str "rect" <+>
str "width=" <> quote (show w) <>
str "height=" <> quote (show h) <>
str "x=" <> quote (show (x - (w/2.0))) <>
str "y=" <> quote (show (y - (h/2.0))))

BoundingBox : Type = (Point & Point)

def renderSVG (d:Diagram) (bounds:BoundingBox) : String =
((xmin, ymin), (xmax, ymax)) = bounds
imgWidth = 400.0
scaleFactor = imgWidth / (xmax - xmin)
imgHeight = (ymax - ymin) * scaleFactor
(MkDiagram (AsList _ objs)) = d |> flipY |> scale scaleFactor
viewBoxStr = makeAttr (str "viewBox") $
(show (xmin * scaleFactor) <+> show (-(ymax * scaleFactor)) <+>
show imgWidth <+> show imgHeight)
svgAttrStr = ( makeAttr (str "width" ) (show imgWidth)
<+> makeAttr (str "height") (show imgHeight)
<+> viewBoxStr)
tagBracketsAttr (str "svg") svgAttrStr $
concat for i.
(attr, pos, geom) = objs.i
renderGeom attr pos geom

'## Derived convenience methods and combinators

moveX : Float -> Diagram -> Diagram = \x. moveXY (x, 0.0)
moveY : Float -> Diagram -> Diagram = \y. moveXY (0.0, y)

-- mydiagram : Diagram =
-- ( (circle 7.0 |> moveXY (20.0, 20.0) |> setFillColor blue |> setStrokeColor red)
-- <> (circle 5.0 |> moveXY (40.0, 40.0))
-- <> (rect 10.0 20.0 |> moveXY (5.0, 10.0) |> setStrokeColor red)
-- )

-- :html renderSVG mydiagram ((0.0, 0.0), (100.0, 50.0))
14 changes: 6 additions & 8 deletions examples/fluidsim.dx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Fluid simulation code based on
[Real-Time Fluid Dynamics for Games](https://www.josstam.com/publications) by Jos Stam

include "examples/plot.dx"

def zeroedges (dict:VSpace a) ?=> (n:Type) ?-> (m:Type) ?-> (x: n=>m=>a) : n=>m=>a =
-- Todo: update in place without starting with a copy.
snd $ withState x \buf.
Expand Down Expand Up @@ -81,8 +83,6 @@ def bilinear_interp (dict:VSpace a) ?=> (right_weight:Float) --o (bottom_weight:
N = Fin 100
M = Fin 100

def clip (x:Float):Float = max 0.0 (min 1.0 x)

-- BUG: Changing the order of implicit arguments causes an error further down.
-- i.e. it doesn't work to start the next line with
-- (n:Type) ?-> (m:Type) ?-> (dict:VSpace a) ?=>
Expand All @@ -109,8 +109,8 @@ def advect (dict:VSpace a) ?=> (n:Type) ?-> (m:Type) ?-> (f: n=>m=>a) (v: n=>m=>

-- Relative weight of right-hand and bottom cells.
-- TODO: clipping shouldn't be necessary here, find out why it is.
right_weight = clip $ center_xs - source_col
bottom_weight = clip $ center_ys - source_row
right_weight = clip (0.0, 1.0) $ center_xs - source_col
bottom_weight = clip (0.0, 1.0) $ center_ys - source_row

-- Cast back to indices, wrapping around edges.
source_col_int = FToI source_col
Expand Down Expand Up @@ -149,7 +149,5 @@ init_color = for i:N j:M.
num_steps = 50
final_color = fluidsim num_steps init_color v

-- FIXME(https://github.com/google-research/dex-lang/issues/173):
-- Uncomment when plotting is fixed.
-- :plotmat final_color
-- > <graphical output>
:html matshow final_color
> <html output>
6 changes: 4 additions & 2 deletions examples/mandelbrot.dx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'# Mandelbrot set

include "examples/plot.dx"

'Escape time algorithm

update : Complex -> Complex -> Complex =
Expand All @@ -21,5 +23,5 @@ ys = linspace (Fin 200) (-1.0) 1.0

escapeGrid = for j i. escapeTime (MkComplex xs.i ys.j)

-- :plotmat escapeGrid
-- > <graphical output>
:html matshow (-escapeGrid)
> <html output>
Loading

0 comments on commit 20940b8

Please sign in to comment.