Talking about my Generation

Declarative Amsterdam 23

Lindenmayer

start with the symbol A

 

per iteration

  replace any A with  A and B

  replace any B with A

A

A B

A B

A

A B

A

A B

A B

A

A B

A B

A

A

A B

A B

A

A B

A

A B

A B

A

A B

A B

A

1

1

2

3

5

?

Alphabet

A B

Axiom

A

Production Rules

A → A B
B → A
<system>
  <axiom>A</axiom>
  <grammar>
    <variable match="A">AB</variable>
    <variable match="B">A</variable>
  </grammar>
</system>

XML Representation In Linsy

Binary Tree

axiom: 0
alphabet: 0 1 [ ]
rules:
  0 → 1[0]0
  1 → 11
11111111111111111111111111111111[1111111111111111[11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111111111111111[11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0
0
1[0]0
11[1[0]0]1[0]0
  [ → [
  ] → ]
<system>
  <axiom>0</axiom>
  <grammar>
    <variable match="0">1[0]0</variable>
    <variable match="1">11</variable>
    <terminal match="[" />
    <terminal match="]" />
  </grammar>
</system>

XML Representation In Linsy

Production
Rules

Binary Tree Production Rules

1 = draw line forward
0 = draw line forward
[ = push state and turn right (branch)
] = pop state and turn left (end branch)
0
1[0]0
11[1[0]0]1[0]0
11111111111111111111111111111111[1111111111111111[11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111111111111111[11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]11111111[1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0]1111[11[1[0]0]1[0]0]11[1[0]0]1[0]0

Render

<render viewBox="-120 0 240 240">
    <state x="0" y="240" orientation="90" 
           angle="45" velocity="16"  />
</render>

Inkscape

A-F = draw line forward
G-L = move forward
+   = turn right
-   = turn left
|   = turn around
[   = push state (branch)
]   = pop state (end branch)
<system>
  <axiom>B</axiom>
  <grammar>
    <variable match="A">AA</symbol>
    <variable match="B">A[+B]-B</symbol>
    <terminal match="+" />
    <terminal match="-" />
    <terminal match="[" />
    <terminal match="]" />
  </grammar>
  <render />
</system>
<system iterations="3">
  <axiom>B</axiom>
  <grammar>
    <variable match="A">AA</symbol>
    <variable match="B">A[+B]-B</symbol>
    <terminal match="+" />
    <terminal match="-" />
    <terminal match="[" />
    <terminal match="]" />
  </grammar>
  <render viewBox="-120 0 240 240">
    <state x="0" y="240" angle="45" velocity="16" orientation="90" />
  </render>
</system>

Angle

Angle

Angle

Angle

Angle

Angle

Angle

Angle

Angle

Angle

paulbourke.net/fractals/lsys/

# Increment the line width (by line width increment)
! Decrement the line width (by line width increment)
@ Draw a dot (with line width radius)
{ Open a polygon
} Close a polygon (and fill it with fill colour)
> Multiply the line length (by the line length scale factor)
< Divide the line length (by the line length scale factor)
& Swap the meaning of + and -
( Decrement turning angle (by turning angle increment)
) Increment turning angle (by turning angle increment)
axiom: A@
alphabet: A @ - + [ ] < >
rules:
   A → <A>
   @ → <A>[+A>@]-A>@
<system iterations="7">
    <axiom>A@</axiom>
    <grammar>
        <variable symbol="A">&lt;A&gt;</variable>
        <variable symbol="@">&lt;A&gt;[+A&gt;@]-A&gt;@</variable>
        <terminal symbol="&lt;"/>
        <terminal symbol="&gt;"/>
        <terminal symbol="["/>
        <terminal symbol="]"/>
        <terminal symbol="+"/>
        <terminal symbol="-"/>
    </grammar>
    <render viewBox="0 0 800 800">
        <state x="400" y="800"
            velocity="16" 
            angle="23" orientation="90"
            acceleration="1.4"
            color="darkgreen"
        />
    </render>
</system>

drawing function

declare function render:symbol (
    $state as map(*),
    $next-symbol as xs:string
) as map(*) { 
    switch($next-symbol)
        case "A" case "B" case "C" case "D" case "E" case "F"
            return render:line($state)
        case "G" case "H" case "I" case "J" case "K" case "L"
            return render:move($state)
        case "@" return render:circle($state)
        case "-" return render:turn-left($state)
        case "+" return render:turn-right($state)
        case "[" return render:push-stack($state)
        case "]" return render:pop-stack($state)
        case "<" return render:increase-velocity($state)
        case ">" return render:decrease-velocity($state)
        default return error()
};

stochastic

axiom: X
angle: 25
alhabet: F X [ ] + -
rules:
  X → F+[[X]-X]-F[-FX]+X (75%)
  X → F-F[-FX]+[[X]-X]+X (25%)
  F → FF (95%)
  F → FFF (5%)
<system iterations="4">
  <axiom>0</axiom>
  <grammar type="stochastic">
    <variable match="1">11</variable>
    <variable match="0">
      <option>1[0]0</option>
      <option>11[0]0</option>
      <option>0</option>
    </variable>
    <terminal match="[" />
    <terminal match="]" />
  </grammar>
</system>
[0.33333333333333333, ("1", "[", "0", "]", "0")],
[0.33333333333333333, ("1", "1", "[", "0", "]", "0")],
[0.33333333333333333, "0"]
[0.33333333333333333, ("1", "[", "0", "]", "0")],
[0.66666666666666667, ("1", "1", "[", "0", "]", "0")],
[1.0,                 "0"]
let $random := 0.3456789
declare function prob:select-option (
    $options as array(*)+,
    $random as xs:double
) {
    fold-left($options, (), prob:select(?, ?, $random))
};

declare %private function prob:select (
    $result as item()*,
    $next as array(*),
    $random as xs:double
) {
    if (empty($result) and $next?1 >= $random)
    then $next?2
    else $result 
};
<variable match="0">
  <option weight="10">1[0]0</option>
  <option weight="10">11[0]0</option>
  <option>0</option>
</variable>
<variable match="0">
  <option weight="10">1[0]0</option>
  <option weight="10">11[0]0</option>
  <option weight="1">0</option>
</variable>

But weight...

<system iterations="5" seed="23">
    <axiom>A</axiom>
    <grammar type="stochastic">
      ...
    </grammar>
    <render ... />
</system>

Growing Seeds

Performance &

Stack Overflows

<grammar>
  <variable match="0">1[0]0</variable>

  <variable match="1">2</variable>
  <variable match="2">3</variable>
  <variable match="3">4</variable>
  ...
  <variable match="9">a</variable>
  <variable match="a">b</variable>
  <variable match="b">c</variable>
  <variable match="d">e</variable>
  <variable match="e">f</variable>

  <terminal match="["/>
  <terminal match="]"/>
</grammar>
declare function local:draw ($state as map(*), $next-symbol as xs:string) { 
    switch($next-symbol)
        case "0" return $state => local:line(0) => local:leaf()
        case "1" return $state => local:line(0)
        case "2" return $state => local:line(1)
        case "3" return $state => local:line(2)
        case "4" return $state => local:line(3)
		...
        case "f" return $state => local:line(14)
        case "[" return $state => render:pop-stack() => render:turn-right()
        case "]" return $state => render:push-stack() => render:turn-left()
        default return error()
};

parametric

axiom: B(200,40)
A($l,$w) = width($w) forwards($l)
   [rotate(-24) B(0.75*$l,0.7*$w)]
   [rotate(22)A(0.85*$l,0.8*$w)]
B($l,$w) = width($w) forwards($l)
   [rotate(-19) A(0.8*$l,0.8*$w]
   [rotate(39) B(0.7*$l,0.6*$w)]

Evaluation in generation

"A(0)"
"rotate(136),[move(0,12)shape(12)]A(1)"
"rotate(136),[move(0,12)shape(12)]rotate(136),[move(1,12)shape(12)]A(2)"
A($n) = rotate($a),[move($n,$s)shape($s)]A($n+1)

Evaluation in interpretation

"rotate(136),[move(0,12)shape(12)]rotate(136),[move(1,12)shape(12)]A(2)"

Interpretation functions for each functional form

Putting it all together

let $axiom := "A(0)"
let $rules := map {
  "A($n)": "rotate($a),[move($n,$s)shape($s)]A($n+1)"
}
let $parms := map {"a": 136, "s": 12}
let $interpreters := map {
  "move#2": function($stack, $n, $s) { 
    $stack=>sys:move(2 * $s * math:sqrt($n)) 
   },
  "shape#1": function($stack, $s) { 
    stack=>sys:circle($s, sys:current($stack)("cix")) 
  }
}
<system axiom="leaf()">
  <grammar type="parametric">
    <variable match="branch($age)">branch($age+1)</variable>
    <variable match="leaf()">
      branch(1),
      push(), turn(+45), branch(1), leaf(),
      pop(),  turn(-45), branch(1), leaf()
    </variable>
    <terminal match="push()" />
    <terminal match="pop()" />
    <terminal match="turn($deg)" />
  </grammar>
</system>

invisible

angle: 90
axiom: F--F--F
F = F+F-F-F+F

Parsing output

root = (path | branch)+ .
path = (forward | turn | -extra)+ .
branch = -"[", (path | branch)+, -"]" .
forward = -"F" .
turn = direction .
@direction = left | right | reverse .
left = -"-",+"left"  .
right = -"+",+"right"  .
reverse = -"|",+reverse" .
extra = -~["F";"[";"]";"+";"-"] .

CoffeeSacks choose-alternative: greedy choice (longest leading match)

angle: 36
axiom: F++F++F++F++F
F = F++F++F|F-F++F

Parsing "FF+XF[X-F]+FF"

<root>
  <path>
    <forward/>
    <forward/>
    <turn direction="right"/>
    <forward/>
  </path>
  <branch>
    <path>
      <turn direction="left"/>
      <forward/>
    </path>
  </branch>
  <path>
    <turn direction="right"/>
    <forward/>
    <forward/>
  </path>
</root>

Generation

{ Terdragon curve: axiom: F; F = F+F-F }
{ F=forward; -=turn 120 left; +=turn 120 right }

root = (-F|"+"|"-")+ .
F = -"F",+"F+F-F" .

Generation

F => <root>F+F-F</root>
F+F-F => <root>F+F-F+F+F-F-F+F-</root> 
F+F-F+F+F-F-F+F-F

Stochastic Generation

{ Stochastic Terdragon curve: axiom: F; F = F+F-F (80%), F = F-F+F (20%) }
{ F=forward; -=turn 120 left; +=turn 120 right }

root = (-F|"+"|"-")+ .
F = -"F",+"F+F-F" | -"F",+"F-F+F".

Use CoffeeSacks choose-alternative: weighted random choice

visual

Inkscape
angle:120°,60°; length:±100%
axiom: FX+FX+
X = FX+FY
Y = FX-FY

Recursive self-similarity

Fractal

let $axiom := "A"
let $rules := map { 
  "A": "B-A-B", 
  "B": "A+B+A" 
}

Recursive self-similarity

Fractal

let $axiom := "A"
let $rules := map { 
  "A": "B-A-B", 
  "B": "A+B+A" 
}

Recursive self-similarity

Organic growth

    let $axiom := "X"
    let $rules := map { 
      "X": "F+[[X]-X]-F[-FX]+X",
      "F": "FF" 
    }

Stochastic Interpretation

Length, angle, width

let $axiom := "?X"
let $rules := map {
  "X": "F+[,\[,\X]-,X]-F[,\-F,X]+,X", 
  "F": "FF"
}
? = randomize values
, = increment colour index
\ = decrease width

Pattern-generation

Structured, rule-based

let $axiom := this:rbinary(0,31)
let $rules := map { 
  "0": "01", 
  "1": "10" 
}

Pattern-generation

Create an integer sequence

011
011010
011010010101
011010011001011001100110

musical

angle: 90
axiom: ?F
F = F+<GF-GF>

linguistic

axiom: R
R = SAVO (40%)
R = SVBO (30%)
R = SAV (7%)
R = SVB (3%)
R = SAVPO (7%)
R = SVBPO (3%)
R = SAVOCR (7%)
R = SVBOCR (3%)

This idiosyncratic Svengali tacitly despised the contractable safe-deposit box.

S = DJN
N = N (95%)
N = NN (5%)
O = DN (50%)
O = DJN (50%)
J = J (95%)
J = JJ (5%)

architecture

multiple

DA23 - Talking about my Generation

By Juri Leino

DA23 - Talking about my Generation

  • 32