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:
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Bleeding edge
Imports
http://mathling.com/art/componentsimport 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: components
declare function components() as map(xs:string, map(xs:string,item()*))
declare 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-parameters
declare function rendering-parameters($canvas as map(xs:string,item()*),
$algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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-parameters
declare function algorithm-mode-parameters($mode as xs:string,
$resolution as xs:string,
$canvas as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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-parameters
declare function algorithm-parameters($resolution as xs:string,
$canvas as map(xs:string, item()*)) as map(xs:string,item()*)
declare 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: randomizers
declare function randomizers($canvas as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: colophon
declare function colophon($parameters as map(xs:string,item()*)) as xs:string?
declare 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: metadata
declare function metadata($canvas as map(xs:string,item()*),
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*))
declare function metadata($canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*))
Standard callback: component metadata (in addition to dump of randomizers
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-function
declare function fixed-function($kind as xs:string,
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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), zpoly:polynomial(($z:one,z:minus(head($roots))), $op), 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-function
declare function random-function($kind as xs:string,
$randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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), zpoly:polynomial(($z:one,z:minus(head($roots))), $op), 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: function
declare function function($randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: bounds
declare function bounds($randomizers as map(xs:string,item()*),
$parameters as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: zvalue
declare 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()*)
declare 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: zvaluev
declare 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*
declare 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: : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @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? { () }; (:~ : Standard callback: component metadata (in addition to dump of randomizers : and parameters) : : @param $canvas: drawing canvas : @param $randomizers: active randomizers for component : @param $parameters: 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()*) ) { () }; (:======================================================================:) (:~ : 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), zpoly:polynomial(($z:one,z:minus(head($roots))), $op), 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), zpoly:polynomial(($z:one,z:minus(head($roots))), $op), 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 ) } ) };