# http://mathling.com/art/complex-functions  library module

http://mathling.com/art/complex-functions

Complex functions:
Create random complex functions for use in fractals and the like.

Example: Compute the complex function for every point in the canvas

let \$function := zf:function(\$randomizers, \$parameters)
let \$bounds := zf:bounds(\$randomizers, \$parameters)
let \$zvaluef := \$function=>zf:zvaluev(\$canvas, \$bounds)
for \$y in 1 to box:height(\$canvas)
for \$x in 1 to box:width(\$canvas)
return \$zvaluef((\$x, \$y))

Parameters:
For fixed mode (mostly just for testing):
zf.function.kind: kind of function to make
zf.order: polynomial order
zf.coeff: coefficients of polynomial
zf.coeff.1: real part of coefficients of polynomial (zpolynomial); must have zf.order of them
zf.coeff.2: imaginary part of coefficients of polynomial (zpolynomial); must have zf.order of them
zf.op: kind of polynomial
zf.root.1: real part of roots (product); must have zf.order of them
zf.root.2: imaginary part of roots (product); must have zf.order of them

zf.bounds: mapping bounds to use

Randomizers:
For random mode:
zf.function.kind: kind of function to make
keys to polynomial|zpolynomial|quotient|product
zf.quotient.kind: kind of quotient to make
keys to polynomial|zpolynomial
zf.order: polynomial order
zf.coeff: coefficients of polynomial
zf.op: kind of polynomial
keys to sinh|cosh|sin|cos|none; e.g. sinh=>polynomial in sinh(x)
zf.root: roots of polynomial to use (product)

zf.bounds: mapping bounds to use

Rendering parameters:

mmm 2023
Status: Bleeding edge

### Imports

http://mathling.com/art/components
```import module namespace components="http://mathling.com/art/components"
at "../art/components.xqy"```
http://mathling.com/type/distribution
```import module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy"```
http://mathling.com/type/wrapper
```import module namespace wrapper="http://mathling.com/type/wrapper"
at "../types/wrapper.xqy"```
http://mathling.com/geometric/rectangle
```import module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy"```
http://mathling.com/core/roots
```import module namespace roots="http://mathling.com/core/roots"
at "../core/roots.xqy"```
http://mathling.com/type/polynomial/complex
```import module namespace zpoly="http://mathling.com/type/polynomial/complex"
at "../types/cpolynomial.xqy"```
http://mathling.com/core/random
```import module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy"```
http://mathling.com/type/polynomial/quotient
```import module namespace quotient="http://mathling.com/type/polynomial/quotient"
at "../types/quotient.xqy"```
http://mathling.com/art/core
```import module namespace core="http://mathling.com/art/core"
at "../art/core.xqy"```
http://mathling.com/core/utilities
```import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy"```
http://mathling.com/core/functions
```import module namespace function="http://mathling.com/core/functions"
at "../core/functions.xqy"```
http://mathling.com/type/polynomial
```import module namespace poly="http://mathling.com/type/polynomial"
at "../types/polynomial.xqy"```
http://mathling.com/core/complex
```import module namespace z="http://mathling.com/core/complex"
at "../core/complex.xqy"```
http://mathling.com/core/complex/vector
```import module namespace zv="http://mathling.com/core/complex/vector"
at "../core/vcomplex.xqy"```
http://mathling.com/core/errors
```import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy"```

### Functions

#### `Function: componentsdeclare function components() as map(xs:string, map(xs:string,item()*))`

Standard callback: Subcomponent map.
Example. render=true() and mode="default" are the defaults if unspecified
map {
"example":
map {
"namespace": "http://mathling.com/art/example",
"render": true(),
"mode": "default"
}
}

##### Returns
• map(xs:string,map(xs:string,item()*))
```declare function this:components() as map(xs:string, map(xs:string,item()*))
{
components:expand(
map {
},
this:lookup#2
)
}```

#### ```Function: rendering-parametersdeclare function rendering-parameters(\$canvas as map(xs:string,item()*), \$algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

Standard callback: Set of rendering parameters.

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• algorithm-parameters as map(xs:string,item()*): set of algorithm parameters
##### Returns
• map(xs:string,item()*)
```declare function this:rendering-parameters(
\$canvas as map(xs:string,item()*),
\$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {}
}```

#### ```Function: algorithm-mode-parametersdeclare function algorithm-mode-parameters(\$mode as xs:string, \$resolution as xs:string, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)```

Standard callback: Set of algorithm parameters specific to a particular mode.

##### Params
• mode as xs:string: selected mode
• resolution as xs:string: defined resolution
• canvas as map(xs:string,item()*): drawing canvas
##### Returns
• map(xs:string,item()*)
```declare function this:algorithm-mode-parameters(
\$mode as xs:string,
\$resolution as xs:string,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
util:merge-into((
map {
"zf.mode": \$mode
},
if (\$mode="fixed") then (
(: Default parameters for a fixed function :)
map {
"zf.function.kind": "polynomial",
"zf.bounds": box:box(-1.18,-2.22,1.41,0.86),
"zf.coeff": (1, -1, -1, 3), (: x^3 - x^2 - x + 3 :)
"zf.op": "none"
}
) else (
)
))
}```

#### ```Function: algorithm-parametersdeclare function algorithm-parameters(\$resolution as xs:string, \$canvas as map(xs:string, item()*)) as map(xs:string,item()*)```

Standard callback: Set of default algorithm parameters.

##### Params
• resolution as xs:string: defined resolution
• canvas as map(xs:string,item()*): drawing canvas
##### Returns
• map(xs:string,item()*)
```declare function this:algorithm-parameters(
\$resolution as xs:string,
\$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
map {
}
}```

#### ```Function: randomizersdeclare function randomizers(\$canvas as map(xs:string,item()*), \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

Standard callback: Set of randomizers.

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• parameters as map(xs:string,item()*): algorithm parameter bundle
##### Returns
• map(xs:string,item()*)
```declare function this:randomizers(
\$canvas as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"zf.function.kind": dist:weighted-index-of(map {
"polynomial": 4,
"zpolynomial": 2,
"quotient": 1,
"product": 1
}),
"zf.quotient.kind": dist:weighted-index-of(map {
"polynomial": 2,
"zpolynomial": 1
}),
"zf.coeff": dist:uniform(-3, 3)=>dist:cast("integer"),
"zf.root": dist:uniform(-2.5, 2.5)=>dist:cast("decimal"),
"zf.order": dist:zipf(1.1, 4)=>dist:post-shift(2),
"zf.op":
dist:weighted-index-of(map {
"sinh": 2,
"cosh": 1,
"sin": 3,
"cos": 2,
"none": 1
})
,
"zf.bounds": dist:normal(1.5, 0.5)=>dist:min(0.1)=>dist:cast("decimal")
}
}```

#### `Function: colophondeclare function colophon(\$parameters as map(xs:string,item()*)) as xs:string?`

Standard callback: component colophon (string attached to signature).

##### Params
• parameters as map(xs:string,item()*): algorithm parameter bundle
##### Returns
• xs:string?
```declare function this:colophon(\$parameters as map(xs:string,item()*)) as xs:string?
{
()
}```

#### ```Function: metadatadeclare function metadata(\$canvas as map(xs:string,item()*), \$randomizers as map(xs:string,item()*), \$parameters as map(xs:string,item()*))```

and parameters)

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• randomizers as map(xs:string,item()*): active randomizers for component
• parameters as map(xs:string,item()*): active parameters for component
```declare function this:metadata(
\$canvas as map(xs:string,item()*),
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
)
{
()
}```

#### ```Function: fixed-functiondeclare function fixed-function(\$kind as xs:string, \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

fixed-function()
Return a function to can use for the iteration that is defined by a
set of fixed properties. (zf.

##### Params
• kind as xs:string: one of "polynomial", "zpolynomial", "product", "quotient"
• parameters as map(xs:string,item()*): set of parameters to use
##### Returns
• map(xs:string,item()*): a wrapper of a function object
```declare function this:fixed-function(
\$kind as xs:string,
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$roots :=
if (\$kind="product") then (
let \$order := core:parameter("zf.order", \$parameters)
let \$rroot := core:parameter("zf.root.1", \$parameters)
let \$iroot := core:parameter("zf.root.2", \$parameters)
for \$i in 1 to \$order + 1 return z:complex(\$rroot[1], \$iroot[2])
) else (
)
let \$function := (
if (\$kind="polynomial") then (
let \$op := core:parameter("zf.op", \$parameters)
let \$coeff := core:parameter("zf.coeff", \$parameters)
return (
poly:polynomial(\$coeff, \$op)
)
) else if (\$kind="zpolynomial") then (
let \$op := core:parameter("zf.op", \$parameters)
let \$order := core:parameter("zf.order", \$parameters)
let \$coeff1 := core:parameter("zf.coeff.1", \$parameters)
let \$coeff2 := core:parameter("zf.coeff.2", \$parameters)
return (
zpoly:polynomial(
(for \$i in 1 to \$order + 1 return z:complex(\$coeff1[1],\$coeff2[1]))=>trace("z"),
\$op
)
)
) else if (\$kind="product") then (
let \$op := core:parameter("zf.op", \$parameters)
return (
fold-left(
tail(\$roots),
function(\$zpoly as map(xs:string,item()*), \$root as map(xs:string,item()*)) {
\$zpoly=>zpoly:multiply(
zpoly:polynomial((\$z:one, z:minus(\$root)), \$op)
)
}
)
)
) else if (\$kind="quotient") then (
let \$kinds := core:parameter("zf.quotient.kind", \$parameters)
let \$u := this:fixed-function(\$kinds[1], core:parameter("zf.quotient.u", \$parameters))
let \$v := this:fixed-function(\$kinds[2], core:parameter("zf.quotient.v", \$parameters))
return (
quotient:quotient(wrapper:body(\$u), wrapper:body(\$v))
)
) else ()
)
let \$roots :=
if (\$kind="product") then (
(\$roots!array{z:as-vector(.)})
) else (
try { (roots:roots(\$function)!array{z:as-vector(.)})=>trace("roots") }
catch * { () }
)
return (
wrapper:wrapper(
\$function,
if (empty(\$roots)) then map {} else map {"zf.roots": \$roots}
)
)
}```

#### ```Function: random-functiondeclare function random-function(\$kind as xs:string, \$randomizers as map(xs:string,item()*), \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

random-function()
Return a random function that we can use for the iteration.

##### Params
• kind as xs:string: one of "polynomial", "zpolynomial", "product", "quotient"
• randomizers as map(xs:string,item()*): set of randomizers to use
• parameters as map(xs:string,item()*): set of parameters to use
##### Returns
• map(xs:string,item()*): a wrapper of a function object
```declare function this:random-function(
\$kind as xs:string,
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$dynamics := (
core:random-parameters(
if (\$kind="quotient") then (
core:random-parameter-map((
"zf.quotient.kind", "zf.quotient.kind"
))
) else (
core:random-parameter-map((
"zf.op", "zf.order"
))
),
\$randomizers
)
)
let \$dynamics :=
util:merge-into(\$dynamics,
core:random-parameters(
if (\$kind="polynomial") then (
core:random-parameter-map((
let \$order := core:parameter("zf.order", \$dynamics)
for \$i in 1 to \$order + 1 return "zf.coeff"
))
) else if (\$kind="zpolynomial") then (
let \$order := core:parameter("zf.order", \$dynamics)
return (
util:merge-into(
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.coeff", 1
),
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.coeff", 2
)
)
)
) else if (\$kind="product") then (
let \$order := core:parameter("zf.order" ,\$dynamics)
return (
util:merge-into(
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.root", 1
),
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.root", 2
)
)
)
) else if (\$kind="quotient") then (
map {}
) else (
map {}
),
\$randomizers
)
)
let \$roots :=
if (\$kind="product") then (
let \$order := core:parameter("zf.order", \$dynamics)
let \$rroot := core:parameter("zf.root.1", \$dynamics)
let \$iroot := core:parameter("zf.root.2", \$dynamics)
for \$i in 1 to \$order + 1 return z:complex(\$rroot[1], \$iroot[2])
) else (
)
let \$function := (
if (\$kind="polynomial") then (
let \$op := core:parameter("zf.op", \$dynamics)
let \$coeff := core:parameter("zf.coeff", \$dynamics)
return (
poly:polynomial(\$coeff, \$op)
)
) else if (\$kind="zpolynomial") then (
let \$op := core:parameter("zf.op", \$dynamics)
let \$order := core:parameter("zf.order", \$dynamics)
let \$coeff1 := core:parameter("zf.coeff.1", \$dynamics)
let \$coeff2 := core:parameter("zf.coeff.2", \$dynamics)
return (
zpoly:polynomial(
for \$i in 1 to \$order + 1 return z:complex(\$coeff1[\$i],\$coeff2[\$i]),
\$op
)
)
) else if (\$kind="product") then (
let \$op := core:parameter("zf.op", \$dynamics)
return (
fold-left(
tail(\$roots),
function(\$zpoly as map(xs:string,item()*), \$root as map(xs:string,item()*)) {
\$zpoly=>zpoly:multiply(
zpoly:polynomial((\$z:one, z:minus(\$root)), \$op)
)
}
)
)
) else if (\$kind="quotient") then (
) else ()
)
let \$roots :=
if (\$kind="product") then (
(\$roots!array{z:as-vector(.)})
) else (
try { (roots:roots(\$function)!array{z:as-vector(.)}) }
catch * { () }
)
let \$dynamics := \$dynamics=>map:put("zf.roots", \$roots)
return (
if (\$kind="quotient") then (
let \$kinds := core:parameter("zf.quotient.kind", \$dynamics)
(: Since derivatives end up combining u and dv and v and du
: we can't represent that if the operator is not "none" :)
let \$no-ops := \$randomizers=>map:put("zf.op", dist:uniform-index-of("none"))
let \$u := this:random-function(\$kinds[1], \$no-ops, \$parameters)
let \$v := this:random-function(\$kinds[2], \$no-ops, \$parameters)
return (
wrapper:wrapper(
quotient:quotient(wrapper:body(\$u), wrapper:body(\$v)),
util:merge-into((
\$dynamics,
map {"zf.quotient.u": wrapper:dynamics(\$u)},
map {"zf.quotient.v": wrapper:dynamics(\$v)}
))
)
)
) else (
wrapper:wrapper(
\$function,
\$dynamics
)
)
)
}```

#### ```Function: functiondeclare function function(\$randomizers as map(xs:string,item()*), \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

function()
Make a complex function according to the mode, random or fixed.

##### Params
• randomizers as map(xs:string,item()*): set of randomizers to use
• parameters as map(xs:string,item()*): set of parameters to use
##### Returns
• map(xs:string,item()*): complex function in a wrapper
```declare function this:function(
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
if (core:optional-parameter("zf.mode", \$parameters)="fixed") then (
this:fixed-function(
core:parameter("zf.function.kind", \$parameters),
\$parameters
)
) else (
this:random-function(
core:randomize("zf.function.kind", \$randomizers),
\$randomizers,
\$parameters
)
)
}```

#### ```Function: boundsdeclare function bounds(\$randomizers as map(xs:string,item()*), \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)```

bounds()
Make bounds according to the mode, random or fixed.

##### Params
• randomizers as map(xs:string,item()*): set of randomizers to use
• parameters as map(xs:string,item()*): set of parameters to use
##### Returns
• map(xs:string,item()*): a box
```declare function this:bounds(
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$mode := core:optional-parameter("zf.mode", \$parameters)
let \$pbounds := core:optional-parameter("zf.bounds", \$parameters)
let \$bounds-raw := for \$i in 1 to 4 return core:randomize("zf.bounds", \$randomizers)
return (
if (\$mode="fixed" and exists(\$pbounds))
then \$pbounds
else box:box(-\$bounds-raw[1], -\$bounds-raw[2], \$bounds-raw[3], \$bounds-raw[4])
)
}```

#### ```Function: zvaluedeclare function zvalue(\$function as map(xs:string,item()*), \$canvas as map(xs:string,item()*), \$bounds as map(xs:string,item()*)) as function(map(xs:string,item()*)) as map(xs:string,item()*)```

zvalue()
Return a function that compute the zvalue of the function, scaled to the
bounds and back. For example, if bounds is [-1,-1,1,1] and canvas is
[0,0,1600,1600] then calculate (0,800) will call f(-1,0) and map result
back to canvas space.

##### Params
• function as map(xs:string,item()*): complex function
• canvas as map(xs:string,item()*): full canvas space
• bounds as map(xs:string,item()*): bounds to calculate function over
##### Returns
• function(map(xs:string,item()*))asmap(xs:string,item()*): function that takes a complex value (point form) and returns a complex value (point form)
```declare function this:zvalue(
\$function as map(xs:string,item()*),
\$canvas as map(xs:string,item()*),
\$bounds as map(xs:string,item()*)
) as function(map(xs:string,item()*)) as map(xs:string,item()*)
{
let \$function := (
if (util:kind(\$function)="wrapper")
then wrapper:body(\$function)
else \$function
)
let \$rows := box:height(\$canvas) cast as xs:integer
let \$columns := box:width(\$canvas) cast as xs:integer
let \$min-r := box:min-px(\$bounds)
let \$scale-r := (box:max-px(\$bounds) - box:min-px(\$bounds)) div \$columns
let \$min-i := box:min-py(\$bounds)
let \$scale-i := (box:max-py(\$bounds) - box:min-py(\$bounds)) div \$rows
return (
function (\$z as map(xs:string,item()*)) as map(xs:string,item()*) {
let \$raw :=
\$function=>function:zvaluev((
\$min-r + z:pr(\$z)*\$scale-r,
\$min-i + z:pi(\$z)*\$scale-i
))
return (
z:complex(
(zv:pr(\$raw) - \$min-r) div \$scale-r,
(zv:pi(\$raw) - \$min-i) div \$scale-r
)
)
}
)
}```

#### ```Function: zvaluevdeclare function zvaluev(\$function as map(xs:string,item()*), \$canvas as map(xs:string,item()*), \$bounds as map(xs:string,item()*)) as function(xs:double*) as xs:double*```

zvaluev()
Return a function that compute the zvaluev of the function, scaled to the
bounds and back. For example, if bounds is [-1,-1,1,1] and canvas is
[0,0,1600,1600] then calculate (0,800) will call f(-1,0) and map result
back to canvas space.

##### Params
• function as map(xs:string,item()*): complex function
• canvas as map(xs:string,item()*): full canvas space
• bounds as map(xs:string,item()*): bounds to calculate function over
##### Returns
• function(xs:double*)asxs:double*: function that takes a complex value (vector form) and returns a complex value (vector form)
```declare function this:zvaluev(
\$function as map(xs:string,item()*),
\$canvas as map(xs:string,item()*),
\$bounds as map(xs:string,item()*)
) as function(xs:double*) as xs:double*
{
let \$function := (
if (util:kind(\$function)="wrapper")
then wrapper:body(\$function)
else \$function
)
let \$rows := box:height(\$canvas) cast as xs:integer
let \$columns := box:width(\$canvas) cast as xs:integer
let \$min-r := box:min-px(\$bounds)
let \$scale-r := (box:max-px(\$bounds) - box:min-px(\$bounds)) div \$columns
let \$min-i := box:min-py(\$bounds)
let \$scale-i := (box:max-py(\$bounds) - box:min-py(\$bounds)) div \$rows
return (
function (\$z as xs:double*) as xs:double* {
let \$raw :=
\$function=>function:zvaluev((
\$min-r + zv:pr(\$z)*\$scale-r,
\$min-i + zv:pi(\$z)*\$scale-i
))
return (
(zv:pr(\$raw) - \$min-r) div \$scale-r,
(zv:pi(\$raw) - \$min-i) div \$scale-r
)
}
)
}```

### Original Source Code

```xquery version "3.1";
(:~
: Complex functions:
: Create random complex functions for use in fractals and the like.
:
: Example: Compute the complex function for every point in the canvas
:
: let \$function := zf:function(\$randomizers, \$parameters)
: let \$bounds := zf:bounds(\$randomizers, \$parameters)
: let \$zvaluef := \$function=>zf:zvaluev(\$canvas, \$bounds)
: for \$y in 1 to box:height(\$canvas)
: for \$x in 1 to box:width(\$canvas)
: return \$zvaluef((\$x, \$y))
:
: Parameters:
: For fixed mode (mostly just for testing):
: zf.function.kind: kind of function to make
: zf.order: polynomial order
: zf.coeff: coefficients of polynomial
: zf.coeff.1: real part of coefficients of polynomial (zpolynomial); must have zf.order of them
: zf.coeff.2: imaginary part of coefficients of polynomial (zpolynomial); must have zf.order of them
: zf.op: kind of polynomial
: zf.root.1: real part of roots (product); must have zf.order of them
: zf.root.2: imaginary part of roots (product); must have zf.order of them
:
: zf.bounds: mapping bounds to use
:
: Randomizers:
: For random mode:
: zf.function.kind: kind of function to make
:   keys to polynomial|zpolynomial|quotient|product
: zf.quotient.kind: kind of quotient to make
:   keys to polynomial|zpolynomial
: zf.order: polynomial order
: zf.coeff: coefficients of polynomial
: zf.op: kind of polynomial
:  keys to sinh|cosh|sin|cos|none; e.g. sinh=>polynomial in sinh(x)
: zf.root: roots of polynomial to use (product)
:
: zf.bounds: mapping bounds to use
:
: Rendering parameters:
:
: @since mmm 2023
: @custom:Status Bleeding edge
:)
module namespace this="http://mathling.com/art/complex-functions";

import module namespace core="http://mathling.com/art/core"
at "../art/core.xqy";
import module namespace components="http://mathling.com/art/components"
at "../art/components.xqy";
import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy";
import module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy";
import module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy";
import module namespace z="http://mathling.com/core/complex"
at "../core/complex.xqy";
import module namespace zv="http://mathling.com/core/complex/vector"
at "../core/vcomplex.xqy";
import module namespace wrapper="http://mathling.com/type/wrapper"
at "../types/wrapper.xqy";
import module namespace function="http://mathling.com/core/functions"
at "../core/functions.xqy";
import module namespace poly="http://mathling.com/type/polynomial"
at "../types/polynomial.xqy";
import module namespace zpoly="http://mathling.com/type/polynomial/complex"
at "../types/cpolynomial.xqy";
import module namespace quotient="http://mathling.com/type/polynomial/quotient"
at "../types/quotient.xqy";
import module namespace roots="http://mathling.com/core/roots"
at "../core/roots.xqy";
import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy";

declare namespace svg="http://www.w3.org/2000/svg";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";
declare namespace art="http://mathling.com/art";

(:~
: In XQuery this is pointless. It is here for single-sourcing, because in
: XSLT we have to make sure the lookup happens in the right context. So:
: a bit of a hack, to be sure.
:)
declare %private function this:lookup(\$qname as xs:QName, \$n as xs:integer) as function(*)?
{
function-lookup(\$qname, \$n)
};

(:~
: Standard callback: Subcomponent map.
: Example. render=true() and mode="default" are the defaults if unspecified
: map {
:   "example":
:     map {
:       "namespace": "http://mathling.com/art/example",
:       "render": true(),
:       "mode": "default"
:     }
: }
:)
declare function this:components() as map(xs:string, map(xs:string,item()*))
{
components:expand(
map {
},
this:lookup#2
)
};

(:~
: Standard callback: Set of rendering parameters.
: @param \$canvas: drawing canvas
: @param \$algorithm-parameters: set of algorithm parameters
:)
declare function this:rendering-parameters(
\$canvas as map(xs:string,item()*),
\$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {}
};

(:~
: Standard callback: Set of algorithm parameters specific to a particular mode.
: @param \$mode: selected mode
: @param \$resolution: defined resolution
: @param \$canvas: drawing canvas
:)
declare function this:algorithm-mode-parameters(
\$mode as xs:string,
\$resolution as xs:string,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
util:merge-into((
map {
"zf.mode": \$mode
},
if (\$mode="fixed") then (
(: Default parameters for a fixed function :)
map {
"zf.function.kind": "polynomial",
"zf.bounds": box:box(-1.18,-2.22,1.41,0.86),
"zf.coeff": (1, -1, -1, 3), (: x^3 - x^2 - x + 3 :)
"zf.op": "none"
}
) else (
)
))
};

(:~
: Standard callback: Set of default algorithm parameters.
: @param \$resolution: defined resolution
: @param \$canvas: drawing canvas
:)
declare function this:algorithm-parameters(
\$resolution as xs:string,
\$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
map {
}
};

(:~
: Standard callback: Set of randomizers.
: @param \$canvas: drawing canvas
: @param \$parameters: algorithm parameter bundle
:)
declare function this:randomizers(
\$canvas as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"zf.function.kind": dist:weighted-index-of(map {
"polynomial": 4,
"zpolynomial": 2,
"quotient": 1,
"product": 1
}),
"zf.quotient.kind": dist:weighted-index-of(map {
"polynomial": 2,
"zpolynomial": 1
}),
"zf.coeff": dist:uniform(-3, 3)=>dist:cast("integer"),
"zf.root": dist:uniform(-2.5, 2.5)=>dist:cast("decimal"),
"zf.order": dist:zipf(1.1, 4)=>dist:post-shift(2),
"zf.op":
dist:weighted-index-of(map {
"sinh": 2,
"cosh": 1,
"sin": 3,
"cos": 2,
"none": 1
})
,
"zf.bounds": dist:normal(1.5, 0.5)=>dist:min(0.1)=>dist:cast("decimal")
}
};

(:~
: Standard callback: component colophon (string attached to signature).
: @param \$parameters: algorithm parameter bundle
:)
declare function this:colophon(\$parameters as map(xs:string,item()*)) as xs:string?
{
()
};

(:~
: and parameters)
:
: @param \$canvas: drawing canvas
: @param \$randomizers: active randomizers for component
: @param \$parameters: active parameters for component
:)
\$canvas as map(xs:string,item()*),
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
)
{
()
};

(:======================================================================:)

(:~
: fixed-function()
: Return a function to can use for the iteration that is defined by a
: set of fixed properties. (zf.
: @param \$kind: one of "polynomial", "zpolynomial", "product", "quotient"
: @param \$parameters: set of parameters to use
: @return a wrapper of a function object
:)
declare function this:fixed-function(
\$kind as xs:string,
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$roots :=
if (\$kind="product") then (
let \$order := core:parameter("zf.order", \$parameters)
let \$rroot := core:parameter("zf.root.1", \$parameters)
let \$iroot := core:parameter("zf.root.2", \$parameters)
for \$i in 1 to \$order + 1 return z:complex(\$rroot[1], \$iroot[2])
) else (
)
let \$function := (
if (\$kind="polynomial") then (
let \$op := core:parameter("zf.op", \$parameters)
let \$coeff := core:parameter("zf.coeff", \$parameters)
return (
poly:polynomial(\$coeff, \$op)
)
) else if (\$kind="zpolynomial") then (
let \$op := core:parameter("zf.op", \$parameters)
let \$order := core:parameter("zf.order", \$parameters)
let \$coeff1 := core:parameter("zf.coeff.1", \$parameters)
let \$coeff2 := core:parameter("zf.coeff.2", \$parameters)
return (
zpoly:polynomial(
(for \$i in 1 to \$order + 1 return z:complex(\$coeff1[1],\$coeff2[1]))=>trace("z"),
\$op
)
)
) else if (\$kind="product") then (
let \$op := core:parameter("zf.op", \$parameters)
return (
fold-left(
tail(\$roots),
function(\$zpoly as map(xs:string,item()*), \$root as map(xs:string,item()*)) {
\$zpoly=>zpoly:multiply(
zpoly:polynomial((\$z:one, z:minus(\$root)), \$op)
)
}
)
)
) else if (\$kind="quotient") then (
let \$kinds := core:parameter("zf.quotient.kind", \$parameters)
let \$u := this:fixed-function(\$kinds[1], core:parameter("zf.quotient.u", \$parameters))
let \$v := this:fixed-function(\$kinds[2], core:parameter("zf.quotient.v", \$parameters))
return (
quotient:quotient(wrapper:body(\$u), wrapper:body(\$v))
)
) else ()
)
let \$roots :=
if (\$kind="product") then (
(\$roots!array{z:as-vector(.)})
) else (
try { (roots:roots(\$function)!array{z:as-vector(.)})=>trace("roots") }
catch * { () }
)
return (
wrapper:wrapper(
\$function,
if (empty(\$roots)) then map {} else map {"zf.roots": \$roots}
)
)
};

(:~
: random-function()
: Return a random function that we can use for the iteration.
: @param \$kind: one of "polynomial", "zpolynomial", "product", "quotient"
: @param \$randomizers: set of randomizers to use
: @param \$parameters: set of parameters to use
: @return a wrapper of a function object
:)
declare function this:random-function(
\$kind as xs:string,
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$dynamics := (
core:random-parameters(
if (\$kind="quotient") then (
core:random-parameter-map((
"zf.quotient.kind", "zf.quotient.kind"
))
) else (
core:random-parameter-map((
"zf.op", "zf.order"
))
),
\$randomizers
)
)
let \$dynamics :=
util:merge-into(\$dynamics,
core:random-parameters(
if (\$kind="polynomial") then (
core:random-parameter-map((
let \$order := core:parameter("zf.order", \$dynamics)
for \$i in 1 to \$order + 1 return "zf.coeff"
))
) else if (\$kind="zpolynomial") then (
let \$order := core:parameter("zf.order", \$dynamics)
return (
util:merge-into(
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.coeff", 1
),
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.coeff", 2
)
)
)
) else if (\$kind="product") then (
let \$order := core:parameter("zf.order" ,\$dynamics)
return (
util:merge-into(
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.root", 1
),
core:random-parameter-map(
for \$i in 1 to \$order + 1 return "zf.root", 2
)
)
)
) else if (\$kind="quotient") then (
map {}
) else (
map {}
),
\$randomizers
)
)
let \$roots :=
if (\$kind="product") then (
let \$order := core:parameter("zf.order", \$dynamics)
let \$rroot := core:parameter("zf.root.1", \$dynamics)
let \$iroot := core:parameter("zf.root.2", \$dynamics)
for \$i in 1 to \$order + 1 return z:complex(\$rroot[1], \$iroot[2])
) else (
)
let \$function := (
if (\$kind="polynomial") then (
let \$op := core:parameter("zf.op", \$dynamics)
let \$coeff := core:parameter("zf.coeff", \$dynamics)
return (
poly:polynomial(\$coeff, \$op)
)
) else if (\$kind="zpolynomial") then (
let \$op := core:parameter("zf.op", \$dynamics)
let \$order := core:parameter("zf.order", \$dynamics)
let \$coeff1 := core:parameter("zf.coeff.1", \$dynamics)
let \$coeff2 := core:parameter("zf.coeff.2", \$dynamics)
return (
zpoly:polynomial(
for \$i in 1 to \$order + 1 return z:complex(\$coeff1[\$i],\$coeff2[\$i]),
\$op
)
)
) else if (\$kind="product") then (
let \$op := core:parameter("zf.op", \$dynamics)
return (
fold-left(
tail(\$roots),
function(\$zpoly as map(xs:string,item()*), \$root as map(xs:string,item()*)) {
\$zpoly=>zpoly:multiply(
zpoly:polynomial((\$z:one, z:minus(\$root)), \$op)
)
}
)
)
) else if (\$kind="quotient") then (
) else ()
)
let \$roots :=
if (\$kind="product") then (
(\$roots!array{z:as-vector(.)})
) else (
try { (roots:roots(\$function)!array{z:as-vector(.)}) }
catch * { () }
)
let \$dynamics := \$dynamics=>map:put("zf.roots", \$roots)
return (
if (\$kind="quotient") then (
let \$kinds := core:parameter("zf.quotient.kind", \$dynamics)
(: Since derivatives end up combining u and dv and v and du
: we can't represent that if the operator is not "none" :)
let \$no-ops := \$randomizers=>map:put("zf.op", dist:uniform-index-of("none"))
let \$u := this:random-function(\$kinds[1], \$no-ops, \$parameters)
let \$v := this:random-function(\$kinds[2], \$no-ops, \$parameters)
return (
wrapper:wrapper(
quotient:quotient(wrapper:body(\$u), wrapper:body(\$v)),
util:merge-into((
\$dynamics,
map {"zf.quotient.u": wrapper:dynamics(\$u)},
map {"zf.quotient.v": wrapper:dynamics(\$v)}
))
)
)
) else (
wrapper:wrapper(
\$function,
\$dynamics
)
)
)
};

(:~
: function()
: Make a complex function according to the mode, random or fixed.
:
: @param \$randomizers: set of randomizers to use
: @param \$parameters: set of parameters to use
: @return complex function in a wrapper
:)
declare function this:function(
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
if (core:optional-parameter("zf.mode", \$parameters)="fixed") then (
this:fixed-function(
core:parameter("zf.function.kind", \$parameters),
\$parameters
)
) else (
this:random-function(
core:randomize("zf.function.kind", \$randomizers),
\$randomizers,
\$parameters
)
)
};

(:~
: bounds()
: Make bounds according to the mode, random or fixed.
:
: @param \$randomizers: set of randomizers to use
: @param \$parameters: set of parameters to use
: @return a box
:)
declare function this:bounds(
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$mode := core:optional-parameter("zf.mode", \$parameters)
let \$pbounds := core:optional-parameter("zf.bounds", \$parameters)
let \$bounds-raw := for \$i in 1 to 4 return core:randomize("zf.bounds", \$randomizers)
return (
if (\$mode="fixed" and exists(\$pbounds))
then \$pbounds
else box:box(-\$bounds-raw[1], -\$bounds-raw[2], \$bounds-raw[3], \$bounds-raw[4])
)
};

(:~
: zvalue()
: Return a function that compute the zvalue of the function, scaled to the
: bounds and back. For example, if bounds is [-1,-1,1,1] and canvas is
: [0,0,1600,1600] then calculate (0,800) will call f(-1,0) and map result
: back to canvas space.
:
: @param \$function: complex function
: @param \$canvas: full canvas space
: @param \$bounds: bounds to calculate function over
: @return function that takes a complex value (point form) and returns a
:    complex value (point form)
:)
declare function this:zvalue(
\$function as map(xs:string,item()*),
\$canvas as map(xs:string,item()*),
\$bounds as map(xs:string,item()*)
) as function(map(xs:string,item()*)) as map(xs:string,item()*)
{
let \$function := (
if (util:kind(\$function)="wrapper")
then wrapper:body(\$function)
else \$function
)
let \$rows := box:height(\$canvas) cast as xs:integer
let \$columns := box:width(\$canvas) cast as xs:integer
let \$min-r := box:min-px(\$bounds)
let \$scale-r := (box:max-px(\$bounds) - box:min-px(\$bounds)) div \$columns
let \$min-i := box:min-py(\$bounds)
let \$scale-i := (box:max-py(\$bounds) - box:min-py(\$bounds)) div \$rows
return (
function (\$z as map(xs:string,item()*)) as map(xs:string,item()*) {
let \$raw :=
\$function=>function:zvaluev((
\$min-r + z:pr(\$z)*\$scale-r,
\$min-i + z:pi(\$z)*\$scale-i
))
return (
z:complex(
(zv:pr(\$raw) - \$min-r) div \$scale-r,
(zv:pi(\$raw) - \$min-i) div \$scale-r
)
)
}
)
};

(:~
: zvaluev()
: Return a function that compute the zvaluev of the function, scaled to the
: bounds and back. For example, if bounds is [-1,-1,1,1] and canvas is
: [0,0,1600,1600] then calculate (0,800) will call f(-1,0) and map result
: back to canvas space.
:
: @param \$function: complex function
: @param \$canvas: full canvas space
: @param \$bounds: bounds to calculate function over
: @return function that takes a complex value (vector form) and returns a
:    complex value (vector form)
:)
declare function this:zvaluev(
\$function as map(xs:string,item()*),
\$canvas as map(xs:string,item()*),
\$bounds as map(xs:string,item()*)
) as function(xs:double*) as xs:double*
{
let \$function := (
if (util:kind(\$function)="wrapper")
then wrapper:body(\$function)
else \$function
)
let \$rows := box:height(\$canvas) cast as xs:integer
let \$columns := box:width(\$canvas) cast as xs:integer
let \$min-r := box:min-px(\$bounds)
let \$scale-r := (box:max-px(\$bounds) - box:min-px(\$bounds)) div \$columns
let \$min-i := box:min-py(\$bounds)
let \$scale-i := (box:max-py(\$bounds) - box:min-py(\$bounds)) div \$rows
return (
function (\$z as xs:double*) as xs:double* {
let \$raw :=
\$function=>function:zvaluev((
\$min-r + zv:pr(\$z)*\$scale-r,
\$min-i + zv:pi(\$z)*\$scale-i
))
return (
(zv:pr(\$raw) - \$min-r) div \$scale-r,
(zv:pi(\$raw) - \$min-i) div \$scale-r
)
}
)
};```