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-2024
CC-BY (https://creativecommons.org/licenses/by/4.0/)

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: components
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()*)


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()*)


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()*)


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()*)


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?


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()*))


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()*)


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[$i],$coeff2[$i]))=>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()*)


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()*)


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()*)


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()*)


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*


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-2024
 : 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[$i],$coeff2[$i]))=>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
      )
    }
  )
};