http://mathling.com/core/vector library module
http://mathling.com/core/vector
Vectors of numbers
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Incomplete
Imports
http://mathling.com/core/utilitiesimport module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/core/config
import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"
Functions
Function: as-dimension
declare function as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double*
declare function as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double*
as-dimension()
Cast the vector to a vector with the given number of dimensions.
If we are reducing dimensions, just remove excess coordinates.
If we are expanding dimensions, fill in with 0.
Params
- vector as xs:double*: source vector
- dim as xs:integer: new dimension of vector
Returns
- xs:double*: new vector
declare function this:as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double* { ($vector, for $i in 1 to $dim return 0E0)[position() <= $dim] }
Function: unquote
declare function unquote($string as xs:string) as xs:double*
declare function unquote($string as xs:string) as xs:double*
unquote()
Parse a string representation of a vector and produce a vector.
The string consists of the coordinates in with commas and
optional spaces in between.
Params
- string as xs:string: the input string to parse
Returns
- xs:double*: vector
declare function this:unquote($string as xs:string) as xs:double* { tokenize($string, "[ ]*,[ ]*")!xs:double(.) }
Function: dimension
declare function dimension($vector as xs:double*) as xs:integer
declare function dimension($vector as xs:double*) as xs:integer
dimension()
Return the dimension of the vector.
Params
- vector as xs:double*: the vector
Returns
- xs:integer: length of vector
declare function this:dimension($vector as xs:double*) as xs:integer { count($vector) }
Function: map
declare function map($f as function(xs:double) as xs:double,
$vector as xs:double*) as xs:double*
declare function map($f as function(xs:double) as xs:double, $vector as xs:double*) as xs:double*
map()
Map a function over the coordinates of a vector, returning the new vector.
Params
- f as function(xs:double)asxs:double: function from coordinate to coordinate
- vector as xs:double*: input vector
Returns
- xs:double*: application of function to each coordinate
declare function this:map( $f as function(xs:double) as xs:double, $vector as xs:double* ) as xs:double* { for-each($vector, $f) }
Function: map
declare function map($f as function(xs:double) as xs:double,
$vector as xs:double*,
$d as xs:integer) as xs:double*
declare function map($f as function(xs:double) as xs:double, $vector as xs:double*, $d as xs:integer) as xs:double*
map()
Map a function over the coordinates of a vector, returning the new vector.
Treat the vector as having a particular dimension.
Params
- f as function(xs:double)asxs:double: function from coordinate to coordinate
- vector as xs:double*: input vector
- d as xs:integer: the effective dimension of the vector
Returns
- xs:double*: application of function to coordinates
declare function this:map( $f as function(xs:double) as xs:double, $vector as xs:double*, $d as xs:integer ) as xs:double* { for-each(this:as-dimension($vector, $d), $f) }
Function: map2
declare function map2($f as function(xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*) as xs:double*
declare function map2($f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*) as xs:double*
map2()
Map a function over the coordinates of two vectors, pair by pair, returning
the new vector. That is, apply function to the x coordinates, then the
y coordinates, and so on.
Params
- f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
Returns
- xs:double*: application of function to each pair
declare function this:map2( $f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double* ) as xs:double* { util:zip($f, $p1, $p2) }
Function: map2
declare function map2($f as function(xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*,
$d as xs:integer) as xs:double*
declare function map2($f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $d as xs:integer) as xs:double*
map2()
Map a function over the coordinates of two vectors, pair by pair, returning
the new vector. That is, apply function to the x coordinates, then the
y coordinates, and so on. Treat the vector as having a particular dimension.
Params
- f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
- d as xs:integer: the effective dimension of the vectors
Returns
- xs:double*: application of function to each pair
declare function this:map2( $f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $d as xs:integer ) as xs:double* { util:zip($f, this:as-dimension($p1, $d), this:as-dimension($p2, $d)) }
Function: map3
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*,
$p3 as xs:double*) as xs:double*
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*) as xs:double*
map3()
Map a function over the coordinates of three vectors, triple by triple,
returning the new vector. That is, apply function to the x coordinates,
then the y coordinates, and so on.
Params
- f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
- p3 as xs:double*: third input vector
Returns
- xs:double*: application of function to each triple
declare function this:map3( $f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double* ) as xs:double* { util:zip( function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)}, $p3, util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) }, $p1, $p2 ) ) }
Function: map3
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*,
$p3 as xs:double*,
$d as xs:integer) as xs:double*
declare function map3($f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $d as xs:integer) as xs:double*
map3()
Map a function over the coordinates of three vectors, triple by triple,
returning the new vector. That is, apply function to the x coordinates,
then the y coordinates, and so on. Treat the vector as having a
particular dimension.
Params
- f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
- p3 as xs:double*: third input vector
- d as xs:integer: the effective dimension of the vectors
Returns
- xs:double*: application of function to each triple
declare function this:map3( $f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $d as xs:integer ) as xs:double* { util:zip( function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)}, this:as-dimension($p3, $d), util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) }, this:as-dimension($p1, $d), this:as-dimension($p2, $d) ) ) }
Function: map4
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*,
$p3 as xs:double*,
$p4 as xs:double*) as xs:double*
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*) as xs:double*
map4()
Map a function over the coordinates of four vectors, coordinate by
coordinate, returning the new vector. That is, apply function to the x
coordinates, then the y coordinates, and so on.
Params
- f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
- p3 as xs:double*: third input vector
- p4 as xs:double*: fourth input vector
Returns
- xs:double*: application of function to each quad
declare function this:map4( $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double* ) as xs:double* { util:zip( function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)}, $p4, util:zip( function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)}, $p3, util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) }, $p1, $p2 ) ) ) }
Function: map4
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
$p1 as xs:double*,
$p2 as xs:double*,
$p3 as xs:double*,
$p4 as xs:double*,
$dim as xs:integer) as xs:double*
declare function map4($f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*, $dim as xs:integer) as xs:double*
map4()
Map a function over the coordinates of four vectors, coordinate by
coordinate, returning the new vector. That is, apply function to the x
coordinates, then the y coordinates, and so on. Treat the vector as having a
particular dimension.
Params
- f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
- p1 as xs:double*: one input vector
- p2 as xs:double*: another input vector
- p3 as xs:double*: third input vector
- p4 as xs:double*: fourth input vector
- dim as xs:integer
Returns
- xs:double*: application of function to each quad
declare function this:map4( $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*, $dim as xs:integer ) as xs:double* { util:zip( function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)}, this:as-dimension($p4, $dim), util:zip( function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)}, this:as-dimension($p3, $dim), util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) }, this:as-dimension($p1, $dim), this:as-dimension($p2, $dim) ) ) ) }
Function: px
declare function px($vector as xs:double*) as xs:double
declare function px($vector as xs:double*) as xs:double
px()
Accessor for the raw precision x coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:double: raw x coordinate
declare function this:px($vector as xs:double*) as xs:double { head(($vector[1],0E0)) }
Function: py
declare function py($vector as xs:double*) as xs:double
declare function py($vector as xs:double*) as xs:double
py()
Accessor for the raw precision y coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:double: raw y coordinate
declare function this:py($vector as xs:double*) as xs:double { head(($vector[2],0E0)) }
Function: pz
declare function pz($vector as xs:double*) as xs:double
declare function pz($vector as xs:double*) as xs:double
pz()
Accessor for the raw precision z coordinate; returns 0 for 2D vector
Params
- vector as xs:double*: the vector
Returns
- xs:double: raw z coordinate
declare function this:pz($vector as xs:double*) as xs:double { head(($vector[3],0E0)) }
Function: pw
declare function pw($vector as xs:double*) as xs:double
declare function pw($vector as xs:double*) as xs:double
pw()
Accessor for the raw precision w coordinate; returns 0 for 2D or 3D vector
Params
- vector as xs:double*: the vector
Returns
- xs:double: raw w coordinate
declare function this:pw($vector as xs:double*) as xs:double { head(($vector[4],0E0)) }
Function: pxy
declare function pxy($vector as xs:double*) as xs:double*
declare function pxy($vector as xs:double*) as xs:double*
pxy()
Accessor for the raw precision xy vector.
Params
- vector as xs:double*: the vector
Returns
- xs:double*: new vector of just the raw x and y coordinates
declare function this:pxy($vector as xs:double*) as xs:double* { head(($vector[1],0E0)), head(($vector[1],0E0)) }
Function: pxz
declare function pxz($vector as xs:double*) as xs:double*
declare function pxz($vector as xs:double*) as xs:double*
pxz()
Accessor for the raw precision xz vector.
Params
- vector as xs:double*: the vector
Returns
- xs:double*: new vector of just the raw x and z coordinates
declare function this:pxz($vector as xs:double*) as xs:double* { head(($vector[1],0E0)), head(($vector[3],0E0)) }
Function: x
declare function x($vector as xs:double*) as xs:integer
declare function x($vector as xs:double*) as xs:integer
x()
Accessor for the snapped x coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:integer: x coordinate, rounded
declare function this:x($vector as xs:double*) as xs:integer { let $x := head(($vector[1],0)) return ( if ($x = xs:double("INF")) then $util:UINT64_MAX else if ($x = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($x) cast as xs:integer ) }
Function: y
declare function y($vector as xs:double*) as xs:integer
declare function y($vector as xs:double*) as xs:integer
y()
Accessor for the snapped y coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:integer: y coordinate, rounded
declare function this:y($vector as xs:double*) as xs:integer { let $y := head(($vector[2],0)) return ( if ($y = xs:double("INF")) then $util:UINT64_MAX else if ($y = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($y) cast as xs:integer ) }
Function: z
declare function z($vector as xs:double*) as xs:integer
declare function z($vector as xs:double*) as xs:integer
z()
Accessor for the snapped z coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:integer: z coordinate, rounded
declare function this:z($vector as xs:double*) as xs:integer { let $z := head(($vector[3],0)) return ( if ($z = xs:double("INF")) then $util:UINT64_MAX else if ($z = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($z) cast as xs:integer ) }
Function: w
declare function w($vector as xs:double*) as xs:integer
declare function w($vector as xs:double*) as xs:integer
w()
Accessor for the snapped w coordinate
Params
- vector as xs:double*: the vector
Returns
- xs:integer: w coordinate, rounded
declare function this:w($vector as xs:double*) as xs:integer { let $w := head(($vector[4],0)) return ( if ($w = xs:double("INF")) then $util:UINT64_MAX else if ($w = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($w) cast as xs:integer ) }
Function: magnitude
declare function magnitude($v as xs:double*) as xs:double
declare function magnitude($v as xs:double*) as xs:double
magnitude()
Magnitude of the vector from origin.
Params
- v as xs:double*: the vector
Returns
- xs:double: Euclidean length of vector
declare function this:magnitude($v as xs:double*) as xs:double { math:sqrt( fn:sum(for $c in $v return $c*$c) ) }
Function: dot
declare function dot($p1 as xs:double*, $p2 as xs:double*) as xs:double
declare function dot($p1 as xs:double*, $p2 as xs:double*) as xs:double
dot()
Compute the dot product of two vectors
Params
- p1 as xs:double*: one vector
- p2 as xs:double*: another vector
Returns
- xs:double: p1·p2
declare function this:dot($p1 as xs:double*, $p2 as xs:double*) as xs:double { fn:sum( this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $p1, $p2) ) }
Function: dot2
declare function dot2($p1 as xs:double*) as xs:double
declare function dot2($p1 as xs:double*) as xs:double
dot2()
Compute the dot product of a vector with itself
Params
- p1 as xs:double*
Returns
- xs:double: p·p
declare function this:dot2($p1 as xs:double*) as xs:double { fn:sum( this:map(function ($a as xs:double) as xs:double {$a * $a}, $p1) ) }
Function: ndot
declare function ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double
declare function ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double
Negated dot: useful for some of the SDF functions.
Params
- p1 as xs:double*: one vector
- p2 as xs:double*: another vector
Returns
- xs:double: p1·-p2
declare function this:ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double { fn:sum( this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * -$b}, $p1, $p2) ) }
Function: mix
declare function mix($u as xs:double*,
$v as xs:double*,
$f as xs:double) as xs:double*
declare function mix($u as xs:double*, $v as xs:double*, $f as xs:double) as xs:double*
Linear combination of two vectors, coordinate by coordinate.
Generally $f in in [0,1], but you can
get extrapolations with numbers outside that range.
Params
- u as xs:double*: one vector
- v as xs:double*: other vector
- f as xs:double: combination fraction
Returns
- xs:double*: vector of linear combination of each coordinate pair
declare function this:mix( $u as xs:double*, $v as xs:double*, $f as xs:double ) as xs:double* { (: Logically $a + ($b - $a) * $f but better endvector behaviour :) this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * (1 - $f) + $b * $f}, $u, $v) }
Function: determinant
declare function determinant($u as xs:double*, $v as xs:double*) as xs:double
declare function determinant($u as xs:double*, $v as xs:double*) as xs:double
determinant()
Compute the determinant of two 2D vectors.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double
declare function this:determinant($u as xs:double*, $v as xs:double*) as xs:double { this:px($u)*this:py($v) - this:py($u)*this:px($v) }
Function: cross
declare function cross($u as xs:double*, $v as xs:double*) as xs:double*
declare function cross($u as xs:double*, $v as xs:double*) as xs:double*
cross()
Compute the cross product of two 3D vectors.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: uXv
declare function this:cross($u as xs:double*, $v as xs:double*) as xs:double* { this:py($u)*this:pz($v) - this:pz($u)*this:py($v), this:pz($u)*this:px($v) - this:px($u)*this:pz($v), this:px($u)*this:py($v) - this:py($u)*this:px($v) }
Function: round
declare function round($v as xs:double*) as xs:double*
declare function round($v as xs:double*) as xs:double*
round()
Round the coordinates of the vector.
Params
- v as xs:double*: vector
Returns
- xs:double*: rounded vector
declare function this:round($v as xs:double*) as xs:double* { this:map(function ($c as xs:double) as xs:double {fn:round($c)}, $v) }
Function: times
declare function times($v as xs:double*, $k as xs:double) as xs:double*
declare function times($v as xs:double*, $k as xs:double) as xs:double*
times()
Scale every coordinate by a constant, returning the new vector.
Params
- v as xs:double*
- k as xs:double: constant multiplier
Returns
- xs:double*: scaled vector
declare function this:times($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {$k * $c}, $v) }
Function: multiply
declare function multiply($u as xs:double*, $v as xs:double*) as xs:double*
declare function multiply($u as xs:double*, $v as xs:double*) as xs:double*
multiply()
Coordinate-by-coordinate multiplication.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: new vector
declare function this:multiply($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $u, $v) }
Function: div
declare function div($v as xs:double*, $k as xs:double) as xs:double*
declare function div($v as xs:double*, $k as xs:double) as xs:double*
div()
Divide every coordinate by a constant, returning the new vector.
Params
- v as xs:double*
- k as xs:double: constant multiplier
Returns
- xs:double*: scaled vector
declare function this:div($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {($c div $k) cast as xs:double}, $v) }
Function: divide
declare function divide($u as xs:double*, $v as xs:double*) as xs:double*
declare function divide($u as xs:double*, $v as xs:double*) as xs:double*
divide()
Coordinate-by-coordinate division
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: new vector
declare function this:divide($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a div $b}, $u, $v) }
Function: mod
declare function mod($v as xs:double*, $k as xs:double) as xs:double*
declare function mod($v as xs:double*, $k as xs:double) as xs:double*
mod()
Compute the mod of every coordinate by a constant, returning the new vector.
Params
- v as xs:double*
- k as xs:double: constant modulus
Returns
- xs:double*: new vector
declare function this:mod($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {$c mod $k}, $v) }
Function: modulo
declare function modulo($u as xs:double*, $v as xs:double*) as xs:double*
declare function modulo($u as xs:double*, $v as xs:double*) as xs:double*
divide()
Coordinate-by-coordinate modulo.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: new vector
declare function this:modulo($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a mod $b}, $u, $v) }
Function: add
declare function add($u as xs:double*, $v as xs:double*) as xs:double*
declare function add($u as xs:double*, $v as xs:double*) as xs:double*
add()
Add two vectors, returning the new vector.
Params
- u as xs:double*: one vector
- v as xs:double*: the other vector
Returns
- xs:double*: u+v
declare function this:add($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a + $b}, $u, $v) }
Function: plus
declare function plus($v as xs:double*, $k as xs:double) as xs:double*
declare function plus($v as xs:double*, $k as xs:double) as xs:double*
plus()
Add a constant to every coordinate in a vector, returning the new vector.
Params
- v as xs:double*: the vector
- k as xs:double: constant to add
Returns
- xs:double*: new vector
declare function this:plus($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {$a + $k}, $v) }
Function: minus
declare function minus($v as xs:double*) as xs:double*
declare function minus($v as xs:double*) as xs:double*
minus()
Negate the coordinates of the vector, returning the new vector.
Params
- v as xs:double*: the vector
Returns
- xs:double*: -v
declare function this:minus($v as xs:double*) as xs:double* { this:map(function ($a as xs:double) as xs:double {-$a}, $v) }
Function: minus
declare function minus($v as xs:double*, $k as xs:double) as xs:double*
declare function minus($v as xs:double*, $k as xs:double) as xs:double*
minus()
Subtract a constant to every coordinate in a vector, returning the new vector.
Params
- v as xs:double*: the vector
- k as xs:double: constant to subtract
Returns
- xs:double*: new vector
declare function this:minus($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {$a - $k}, $v) }
Function: sub
declare function sub($u as xs:double*, $v as xs:double*) as xs:double*
declare function sub($u as xs:double*, $v as xs:double*) as xs:double*
sub()
Subtract one vector from another, returning the new vector.
Params
- u as xs:double*: one vector
- v as xs:double*: the other vector
Returns
- xs:double*: u-v
declare function this:sub($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a - $b}, $u, $v) }
Function: abs
declare function abs($v as xs:double*) as xs:double*
declare function abs($v as xs:double*) as xs:double*
abs()
Coordinate-by-coordinate absolute value.
Params
- v as xs:double*: vector
Returns
- xs:double*: new vector
declare function this:abs($v as xs:double*) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:abs($a)}, $v) }
Function: sign
declare function sign($v as xs:double*) as xs:double*
declare function sign($v as xs:double*) as xs:double*
sign()
Coordinate-by-coordinate sign.
Params
- v as xs:double*: vector
Returns
- xs:double*: new vector
declare function this:sign($v as xs:double*) as xs:double* { this:map( function ($a as xs:double) as xs:double { if ($a = 0) then 0E0 else if ($a < 0) then -1E0 else 1E0 }, $v ) }
Function: min
declare function min($u as xs:double*, $v as xs:double*) as xs:double*
declare function min($u as xs:double*, $v as xs:double*) as xs:double*
min()
Coordinate-by-coordinate minimum: minimum of each coordinate pair.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: new vector
declare function this:min($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:min(($a,$b))}, $u, $v) }
Function: max
declare function max($u as xs:double*, $v as xs:double*) as xs:double*
declare function max($u as xs:double*, $v as xs:double*) as xs:double*
max()
Coordinate-by-coordinate maximum: maximum of each coordinate pair.
Params
- u as xs:double*: one vector
- v as xs:double*: another vector
Returns
- xs:double*: new vector
declare function this:max($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:max(($a,$b))}, $u, $v) }
Function: at-min
declare function at-min($v as xs:double*, $val as xs:double) as xs:double*
declare function at-min($v as xs:double*, $val as xs:double) as xs:double*
at-min()
Coordinate-by-coordinate minimum: minimum of each coordinate with value.
Params
- v as xs:double*: vectoral: comparison value
- val as xs:double: comparison value
Returns
- xs:double*: new vector
declare function this:at-min($v as xs:double*, $val as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:min(($a, $val))}, $v) }
Function: at-max
declare function at-max($v as xs:double*, $val as xs:double) as xs:double*
declare function at-max($v as xs:double*, $val as xs:double) as xs:double*
at-max()
Coordinate-by-coordinate maximum: maximum of each coordinate with value.
Params
- v as xs:double*: vectoral: comparison value
- val as xs:double: comparison value
Returns
- xs:double*: new vector
declare function this:at-max($v as xs:double*, $val as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:max(($a, $val))}, $v) }
Function: normalize
declare function normalize($v as xs:double*) as xs:double*
declare function normalize($v as xs:double*) as xs:double*
normalize()
Normalize a vector represented as a vector (scale to unit vector), returning
the new vector.
Params
- v as xs:double*: the vector
Returns
- xs:double*: normalized vector
declare function this:normalize($v as xs:double*) as xs:double* { let $l := this:magnitude($v) return if ($l = 0) then $v else this:map(function ($c as xs:double) as xs:double {$c div $l}, $v) }
Function: clamp
declare function clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double*
declare function clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double*
clamp()
Coordinate-by-coordinate clamping.
Params
- v as xs:double*: vector
- min as xs:double: minimum value
- max as xs:double: maximum value
Returns
- xs:double*: new vector
declare function this:clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {util:clamp($a, $min, $max)}, $v) }
Function: clampv
declare function clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double*
declare function clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double*
clampv()
Coordinate-by-coordinate clamping: clamp with corresponding coordinates of
min/max vectors.
Params
- v as xs:double*: vector
- minv as xs:double*: minimum values
- maxv as xs:double*: maximum values
Returns
- xs:double*: new vector
declare function this:clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double* { this:map3(function ($a as xs:double, $min as xs:double?, $max as xs:double?) as xs:double {util:clamp($a, $min, $max)}, $v, $minv, $maxv) }
Function: perpendicular
declare function perpendicular($v as xs:double*) as xs:double*
declare function perpendicular($v as xs:double*) as xs:double*
perpendicular()
Compute the perpendicular vector for a 2D vector represented as a vector.
(Rotated by 90 degrees.)
Params
- v as xs:double*: the vector
Returns
- xs:double*: perpendicular vector
declare function this:perpendicular($v as xs:double*) as xs:double* { (this:py($v), -this:px($v)) }
Function: snap
declare function snap($vector as xs:double*) as xs:integer*
declare function snap($vector as xs:double*) as xs:integer*
snap()
Snap the coordinates of the vector, returning the vector with snapped
(i.e. integer) coordinates. NaN snaps to UINT64_MAX.
Params
- vector as xs:double*: the vector
Returns
- xs:integer*
declare function this:snap($vector as xs:double*) as xs:integer* { for-each($vector, function ($c as xs:double) as xs:integer { if ($c = xs:double("INF") or not($c = $c)) then $util:UINT64_MAX else if ($c = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($c) cast as xs:integer } ) }
Function: floor
declare function floor($vector as xs:double*) as xs:integer*
declare function floor($vector as xs:double*) as xs:integer*
floor()
Return vector where all the coordinates have been set to the floor of
the original vector's values. Differs from snap() for negative coordinates.
Params
- vector as xs:double*: the vector
Returns
- xs:integer*
declare function this:floor($vector as xs:double*) as xs:integer* { for-each($vector, function ($c as xs:double) as xs:integer {fn:floor($c) cast as xs:integer}) }
Function: decimal
declare function decimal($vector as xs:double*, $digits as xs:integer) as xs:double*
declare function decimal($vector as xs:double*, $digits as xs:integer) as xs:double*
decimal()
Perform decimal rounding on all the coordinates (see util:decimal).
Params
- vector as xs:double*: the vector to round
- digits as xs:integer: how many digits after the decimal vector to keep
Returns
- xs:double*
declare function this:decimal($vector as xs:double*, $digits as xs:integer) as xs:double* { for-each($vector, function ($c as xs:double) as xs:double {util:decimal($c, $digits)}) }
Function: quote
declare function quote($vector as xs:double*) as xs:string
declare function quote($vector as xs:double*) as xs:string
quote()
Return a string value for the vector, suitable for debugging.
Params
- vector as xs:double*: the vector to quote
Returns
- xs:string
declare function this:quote($vector as xs:double*) as xs:string { string-join($vector!string(.),",") }
Function: valid
declare function valid($point as xs:double*) as xs:boolean
declare function valid($point as xs:double*) as xs:boolean
valid()
False if a coordinate is INF or NaN
Params
- point as xs:double*
Returns
- xs:boolean
declare function this:valid($point as xs:double*) as xs:boolean { every $c in $point satisfies $c=$c and not($c=(xs:double("INF"),xs:double("-INF"))) }
Function: same
declare function same($this as xs:double*, $other as xs:double*) as xs:boolean
declare function same($this as xs:double*, $other as xs:double*) as xs:boolean
same()
Equality comparison for vectors, ignoring annotation properties.
Return true() if they have equal coordinates.
Params
- this as xs:double*: one vector
- other as xs:double*: the vector to compare it to
Returns
- xs:boolean: this=other
declare function this:same($this as xs:double*, $other as xs:double*) as xs:boolean { let $d := max((count($this), count($other))) return ( deep-equal( this:as-dimension($this, $d), this:as-dimension($other, $d) ) ) }
Function: distance
declare function distance($v1 as xs:double*, $v2 as xs:double*) as xs:double
declare function distance($v1 as xs:double*, $v2 as xs:double*) as xs:double
distance()
Euclidean distance between vectors
Params
- v1 as xs:double*: one vector
- v2 as xs:double*: another vector
Returns
- xs:double: distance(v1,v2)
declare function this:distance($v1 as xs:double*, $v2 as xs:double*) as xs:double { let $d := fn:max((this:dimension($v1), this:dimension($v2))) return ( math:sqrt( fn:sum( util:zip( function ($c1 as xs:double, $c2 as xs:double) as xs:double { ($c1 - $c2)*($c1 - $c2) }, this:as-dimension($v1, $d), this:as-dimension($v2, $d) ) ) ) ) }
Function: polar-distance
declare function polar-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function polar-distance($p as xs:double*, $q as xs:double*) as xs:double
polar-distance()
Angular distance between points taken as vectors.
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:polar-distance( $p as xs:double*, $q as xs:double* ) as xs:double { abs(this:angle((0,0), $p) - this:angle((0,0), $q)) }
Function: taxi-distance
declare function taxi-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function taxi-distance($p as xs:double*, $q as xs:double*) as xs:double
taxi-distance()
Taxi-cab distance between two points. That is, right angle distance, such as
a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks.
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:taxi-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( sum( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }
Function: avg-distance
declare function avg-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function avg-distance($p as xs:double*, $q as xs:double*) as xs:double
avg-distance()
Average of distances along each dimension.
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:avg-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( avg( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }
Function: rail-distance
declare function rail-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function rail-distance($p as xs:double*, $q as xs:double*) as xs:double
rail-distance()
Railway distance, e.g distance to the central hub and back out.
The hub is the origin.
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:rail-distance( $p as xs:double*, $q as xs:double* ) as xs:double { this:distance($p, (0,0)) + this:distance($q, (0,0)) }
Function: rail-distance
declare function rail-distance($p as xs:double*,
$q as xs:double*,
$origin as xs:double*) as xs:double
declare function rail-distance($p as xs:double*, $q as xs:double*, $origin as xs:double*) as xs:double
rail-distance()
Railway distance, e.g distance to the central hub and back out where the hub
is specified.
Params
- p as xs:double*
- q as xs:double*
- origin as xs:double*
Returns
- xs:double
declare function this:rail-distance( $p as xs:double*, $q as xs:double*, $origin as xs:double* ) as xs:double { this:distance($p, $origin) + this:distance($q, $origin) }
Function: chebyshev-distance
declare function chebyshev-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function chebyshev-distance($p as xs:double*, $q as xs:double*) as xs:double
chebyshev-distance()
Chebyshev distance metric. The maximum of the dimensional distances.
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:chebyshev-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( max( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }
Function: minkowski-distance
declare function minkowski-distance($p as xs:double*,
$q as xs:double*,
$order as xs:double) as xs:double
declare function minkowski-distance($p as xs:double*, $q as xs:double*, $order as xs:double) as xs:double
minwokski-distance()
Minkowski distance metric of the given order. Certain orders give rise to
other metrics:
order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev;
order<1 is not a metric unless undo the final pow. i.e. X is a metric but
X^(1/order) is not.
Params
- p as xs:double*
- q as xs:double*
- order as xs:double
Returns
- xs:double
declare function this:minkowski-distance( $p as xs:double*, $q as xs:double*, $order as xs:double ) as xs:double { let $d := max((count($p), count($q))) return ( math:pow( sum( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { math:pow(abs($cp - $cq), $order) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ), 1 div $order ) ) }
Function: cosine-similarity
declare function cosine-similarity($p as xs:double*,
$q as xs:double*) as xs:double
declare function cosine-similarity($p as xs:double*, $q as xs:double*) as xs:double
cosine-similarity()
Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²)
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:cosine-similarity( $p as xs:double*, $q as xs:double* ) as xs:double { this:dot($p, $q) div (this:magnitude($p)*this:magnitude($q)) }
Function: cosine-distance
declare function cosine-distance($p as xs:double*,
$q as xs:double*) as xs:double
declare function cosine-distance($p as xs:double*, $q as xs:double*) as xs:double
cosine-distance()
Cosine similarity as a metric: acos(similarity)/π => [0,1]
Params
- p as xs:double*
- q as xs:double*
Returns
- xs:double
declare function this:cosine-distance( $p as xs:double*, $q as xs:double* ) as xs:double { math:acos(this:cosine-similarity($p, $q)) div math:pi() }
Function: angle
declare function angle($last as xs:double*, $curr as xs:double*) as xs:double
declare function angle($last as xs:double*, $curr as xs:double*) as xs:double
angle()
Compute the angle (azimuth) from one point to the next, in degrees
Return 0 if points are the same
Params
- last as xs:double*: previous point; use (0,0) if no previous
- curr as xs:double*: current point
Returns
- xs:double
declare function this:angle($last as xs:double*, $curr as xs:double*) as xs:double { let $this := $curr=>this:as-dimension(2) let $prev := if (empty($last)) then (0,0) else this:as-dimension($last,2) return if (this:same($prev, $this) or (this:distance($prev,$this) < $config:ε)) then 0 else if (this:px($this)=this:px($prev)) then ( if (this:py($prev) > this:py($this)) then 270 (: remapped -90 :) else 90 ) else ( util:remap-degrees(util:degrees( (math:pi() div 2) - math:atan2(this:px($this) - this:px($prev), this:py($this) - this:py($prev)) )) ) }
Function: inclination
declare function inclination($last as xs:double*, $curr as xs:double*) as xs:double
declare function inclination($last as xs:double*, $curr as xs:double*) as xs:double
inclination()
Compute the inclination angle from one point in the next, in degrees
Return 90 if the points are the same
Params
- last as xs:double*: previous point; use (0,0,0) if no previous
- curr as xs:double*: current point
Returns
- xs:double
declare function this:inclination($last as xs:double*, $curr as xs:double*) as xs:double { let $curr := $curr=>this:as-dimension(3) let $prev := if (empty($last)) then (0,0,0) else $last=>this:as-dimension(3) let $d := this:distance($prev,$curr) return if (this:same($prev,$curr) or ($d < $config:ε)) then 90 else ( util:remap-degrees(util:degrees( math:acos( (this:pz($curr) - this:pz($prev)) div $d ) )) ) }
Function: destination
declare function destination($point as xs:double*,
$degrees as xs:double,
$length as xs:double) as xs:double*
declare function destination($point as xs:double*, $degrees as xs:double, $length as xs:double) as xs:double*
destination()
Point at a particular distance and direction.
Params
- point as xs:double*: starting point
- degrees as xs:double: bearing from point
- length as xs:double: how far along bearing to travel
Returns
- xs:double*
declare function this:destination( $point as xs:double*, $degrees as xs:double, $length as xs:double ) as xs:double* { let $angle := util:radians($degrees) return ( this:px($point) + math:cos($angle)*$length, this:py($point) + math:sin($angle)*$length ) }
Function: mutate
declare function mutate($vectors as xs:double*,
$mutate as function(xs:double*) as xs:double*) as xs:double*
declare function mutate($vectors as xs:double*, $mutate as function(xs:double*) as xs:double*) as xs:double*
mutate()
Mutate a sequence of 2D vectors using a mutation function.
Params
- vectors as xs:double*: the vectors
- mutate as function(xs:double*)asxs:double*: function that takes a vector as an argument and returns a new vector
Returns
- xs:double*
declare function this:mutate( $vectors as xs:double*, $mutate as function(xs:double*) as xs:double* ) as xs:double* { for $i in 1 to count($vectors) idiv 2 return ( $mutate(($vectors[2*$i - 1], $vectors[2*$i])) ) }
Original Source Code
xquery version "3.1"; (:~ : Vectors of numbers : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since May 2021 : @custom:Status Incomplete :) module namespace this="http://mathling.com/core/vector"; import module namespace config="http://mathling.com/core/config" at "../core/config.xqy"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; (:~ : as-dimension() : Cast the vector to a vector with the given number of dimensions. : If we are reducing dimensions, just remove excess coordinates. : If we are expanding dimensions, fill in with 0. : : @param $vector: source vector : @param $dim: new dimension of vector : @return new vector :) declare function this:as-dimension($vector as xs:double*, $dim as xs:integer) as xs:double* { ($vector, for $i in 1 to $dim return 0E0)[position() <= $dim] }; (:~ : unquote() : Parse a string representation of a vector and produce a vector. : The string consists of the coordinates in with commas and : optional spaces in between. : : @param $string: the input string to parse : @return vector :) declare function this:unquote($string as xs:string) as xs:double* { tokenize($string, "[ ]*,[ ]*")!xs:double(.) }; (:~ : dimension() : Return the dimension of the vector. : : @param $vector: the vector : @return length of vector :) declare function this:dimension($vector as xs:double*) as xs:integer { count($vector) }; (:~ : map() : Map a function over the coordinates of a vector, returning the new vector. : : @param $f: function from coordinate to coordinate : @param $vector: input vector : @return application of function to each coordinate :) declare function this:map( $f as function(xs:double) as xs:double, $vector as xs:double* ) as xs:double* { for-each($vector, $f) }; (:~ : map() : Map a function over the coordinates of a vector, returning the new vector. : Treat the vector as having a particular dimension. : : @param $f: function from coordinate to coordinate : @param $vector: input vector : @param $d: the effective dimension of the vector : @return application of function to coordinates :) declare function this:map( $f as function(xs:double) as xs:double, $vector as xs:double*, $d as xs:integer ) as xs:double* { for-each(this:as-dimension($vector, $d), $f) }; (:~ : map2() : Map a function over the coordinates of two vectors, pair by pair, returning : the new vector. That is, apply function to the x coordinates, then the : y coordinates, and so on. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @return application of function to each pair :) declare function this:map2( $f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double* ) as xs:double* { util:zip($f, $p1, $p2) }; (:~ : map2() : Map a function over the coordinates of two vectors, pair by pair, returning : the new vector. That is, apply function to the x coordinates, then the : y coordinates, and so on. Treat the vector as having a particular dimension. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @param $d: the effective dimension of the vectors : @return application of function to each pair :) declare function this:map2( $f as function(xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $d as xs:integer ) as xs:double* { util:zip($f, this:as-dimension($p1, $d), this:as-dimension($p2, $d)) }; (:~ : map3() : Map a function over the coordinates of three vectors, triple by triple, : returning the new vector. That is, apply function to the x coordinates, : then the y coordinates, and so on. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @param $p3: third input vector : @return application of function to each triple :) declare function this:map3( $f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double* ) as xs:double* { util:zip( function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)}, $p3, util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) }, $p1, $p2 ) ) }; (:~ : map3() : Map a function over the coordinates of three vectors, triple by triple, : returning the new vector. That is, apply function to the x coordinates, : then the y coordinates, and so on. Treat the vector as having a : particular dimension. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @param $p3: third input vector : @param $d: the effective dimension of the vectors : @return application of function to each triple :) declare function this:map3( $f as function(xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $d as xs:integer ) as xs:double* { util:zip( function ($a as xs:double, $fc as function(xs:double) as xs:double) as xs:double {$fc($a)}, this:as-dimension($p3, $d), util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?) }, this:as-dimension($p1, $d), this:as-dimension($p2, $d) ) ) }; (:~ : map4() : Map a function over the coordinates of four vectors, coordinate by : coordinate, returning the new vector. That is, apply function to the x : coordinates, then the y coordinates, and so on. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @param $p3: third input vector : @param $p4: fourth input vector : @return application of function to each quad :) declare function this:map4( $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double* ) as xs:double* { util:zip( function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)}, $p4, util:zip( function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)}, $p3, util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) }, $p1, $p2 ) ) ) }; (:~ : map4() : Map a function over the coordinates of four vectors, coordinate by : coordinate, returning the new vector. That is, apply function to the x : coordinates, then the y coordinates, and so on. Treat the vector as having a : particular dimension. : : @param $f: function from coordinate pairs to coordinate : @param $p1: one input vector : @param $p2: another input vector : @param $p3: third input vector : @param $p4: fourth input vector : @param $d: the effective dimension of the vectors : @return application of function to each quad :) declare function this:map4( $f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, $p1 as xs:double*, $p2 as xs:double*, $p3 as xs:double*, $p4 as xs:double*, $dim as xs:integer ) as xs:double* { util:zip( function ($d as xs:double, $fd as function(xs:double) as xs:double) as xs:double {$fd($d)}, this:as-dimension($p4, $dim), util:zip( function ($c as xs:double, $fc as function(xs:double, xs:double) as xs:double) as function(*) {$fc($c, ?)}, this:as-dimension($p3, $dim), util:zip( function ($a as xs:double, $b as xs:double) as function(*) { $f($a, $b, ?, ?) }, this:as-dimension($p1, $dim), this:as-dimension($p2, $dim) ) ) ) }; (:~ : px() : Accessor for the raw precision x coordinate : : @param $vector: the vector : @return raw x coordinate :) declare function this:px($vector as xs:double*) as xs:double { head(($vector[1],0E0)) }; (:~ : py() : Accessor for the raw precision y coordinate : : @param $vector: the vector : @return raw y coordinate :) declare function this:py($vector as xs:double*) as xs:double { head(($vector[2],0E0)) }; (:~ : pz() : Accessor for the raw precision z coordinate; returns 0 for 2D vector : : @param $vector: the vector : @return raw z coordinate :) declare function this:pz($vector as xs:double*) as xs:double { head(($vector[3],0E0)) }; (:~ : pw() : Accessor for the raw precision w coordinate; returns 0 for 2D or 3D vector : : @param $vector: the vector : @return raw w coordinate :) declare function this:pw($vector as xs:double*) as xs:double { head(($vector[4],0E0)) }; (:~ : pxy() : Accessor for the raw precision xy vector. : : @param $vector: the vector : @return new vector of just the raw x and y coordinates :) declare function this:pxy($vector as xs:double*) as xs:double* { head(($vector[1],0E0)), head(($vector[1],0E0)) }; (:~ : pxz() : Accessor for the raw precision xz vector. : : @param $vector: the vector : @return new vector of just the raw x and z coordinates :) declare function this:pxz($vector as xs:double*) as xs:double* { head(($vector[1],0E0)), head(($vector[3],0E0)) }; (:~ : x() : Accessor for the snapped x coordinate : : @param $vector: the vector : @return x coordinate, rounded :) declare function this:x($vector as xs:double*) as xs:integer { let $x := head(($vector[1],0)) return ( if ($x = xs:double("INF")) then $util:UINT64_MAX else if ($x = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($x) cast as xs:integer ) }; (:~ : y() : Accessor for the snapped y coordinate : : @param $vector: the vector : @return y coordinate, rounded :) declare function this:y($vector as xs:double*) as xs:integer { let $y := head(($vector[2],0)) return ( if ($y = xs:double("INF")) then $util:UINT64_MAX else if ($y = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($y) cast as xs:integer ) }; (:~ : z() : Accessor for the snapped z coordinate : : @param $vector: the vector : @return z coordinate, rounded :) declare function this:z($vector as xs:double*) as xs:integer { let $z := head(($vector[3],0)) return ( if ($z = xs:double("INF")) then $util:UINT64_MAX else if ($z = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($z) cast as xs:integer ) }; (:~ : w() : Accessor for the snapped w coordinate : : @param $vector: the vector : @return w coordinate, rounded :) declare function this:w($vector as xs:double*) as xs:integer { let $w := head(($vector[4],0)) return ( if ($w = xs:double("INF")) then $util:UINT64_MAX else if ($w = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($w) cast as xs:integer ) }; (:~ : magnitude() : Magnitude of the vector from origin. : : @param $v: the vector : @return Euclidean length of vector :) declare function this:magnitude($v as xs:double*) as xs:double { math:sqrt( fn:sum(for $c in $v return $c*$c) ) }; (:~ : dot() : Compute the dot product of two vectors : : @param $p1: one vector : @param $p2: another vector : @return p1·p2 :) declare function this:dot($p1 as xs:double*, $p2 as xs:double*) as xs:double { fn:sum( this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $p1, $p2) ) }; (:~ : dot2() : Compute the dot product of a vector with itself : : @param $p: the vector : @return p·p :) declare function this:dot2($p1 as xs:double*) as xs:double { fn:sum( this:map(function ($a as xs:double) as xs:double {$a * $a}, $p1) ) }; (:~ : Negated dot: useful for some of the SDF functions. : : @param $p1: one vector : @param $p2: another vector : @return p1·-p2 :) declare function this:ndot($p1 as xs:double*, $p2 as xs:double*) as xs:double { fn:sum( this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * -$b}, $p1, $p2) ) }; (:~ : Linear combination of two vectors, coordinate by coordinate. : Generally $f in in [0,1], but you can : get extrapolations with numbers outside that range. : : @param $u: one vector : @param $v: other vector : @param $f: combination fraction : @return vector of linear combination of each coordinate pair :) declare function this:mix( $u as xs:double*, $v as xs:double*, $f as xs:double ) as xs:double* { (: Logically $a + ($b - $a) * $f but better endvector behaviour :) this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * (1 - $f) + $b * $f}, $u, $v) }; (:~ : determinant() : Compute the determinant of two 2D vectors. : : @param $u: one vector : @param $v: another vector : @param det(u,v) :) declare function this:determinant($u as xs:double*, $v as xs:double*) as xs:double { this:px($u)*this:py($v) - this:py($u)*this:px($v) }; (:~ : cross() : Compute the cross product of two 3D vectors. : : @param $u: one vector : @param $v: another vector : @return uXv :) declare function this:cross($u as xs:double*, $v as xs:double*) as xs:double* { this:py($u)*this:pz($v) - this:pz($u)*this:py($v), this:pz($u)*this:px($v) - this:px($u)*this:pz($v), this:px($u)*this:py($v) - this:py($u)*this:px($v) }; (:~ : round() : Round the coordinates of the vector. : : @param $v: vector : @return rounded vector :) declare function this:round($v as xs:double*) as xs:double* { this:map(function ($c as xs:double) as xs:double {fn:round($c)}, $v) }; (:~ : times() : Scale every coordinate by a constant, returning the new vector. : : @param $u: vector to scale : @param $k: constant multiplier : @return scaled vector :) declare function this:times($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {$k * $c}, $v) }; (:~ : multiply() : Coordinate-by-coordinate multiplication. : : @param $u: one vector : @param $v: another vector : @return new vector :) declare function this:multiply($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a * $b}, $u, $v) }; (:~ : div() : Divide every coordinate by a constant, returning the new vector. : : @param $u: vector to scale : @param $k: constant multiplier : @return scaled vector :) declare function this:div($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {($c div $k) cast as xs:double}, $v) }; (:~ : divide() : Coordinate-by-coordinate division : : @param $u: one vector : @param $v: another vector : @return new vector :) declare function this:divide($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a div $b}, $u, $v) }; (:~ : mod() : Compute the mod of every coordinate by a constant, returning the new vector. : : @param $u: vector : @param $k: constant modulus : @return new vector :) declare function this:mod($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($c as xs:double) as xs:double {$c mod $k}, $v) }; (:~ : divide() : Coordinate-by-coordinate modulo. : : @param $u: one vector : @param $v: another vector : @return new vector :) declare function this:modulo($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a mod $b}, $u, $v) }; (:~ : add() : Add two vectors, returning the new vector. : : @param $u: one vector : @param $v: the other vector : @return u+v :) declare function this:add($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a + $b}, $u, $v) }; (:~ : plus() : Add a constant to every coordinate in a vector, returning the new vector. : : @param $v: the vector : @param $k: constant to add : @return new vector :) declare function this:plus($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {$a + $k}, $v) }; (:~ : minus() : Negate the coordinates of the vector, returning the new vector. : : @param $v: the vector : @return -v :) declare function this:minus($v as xs:double*) as xs:double* { this:map(function ($a as xs:double) as xs:double {-$a}, $v) }; (:~ : minus() : Subtract a constant to every coordinate in a vector, returning the new vector. : : @param $v: the vector : @param $k: constant to subtract : @return new vector :) declare function this:minus($v as xs:double*, $k as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {$a - $k}, $v) }; (:~ : sub() : Subtract one vector from another, returning the new vector. : : @param $u: one vector : @param $v: the other vector : @return u-v :) declare function this:sub($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {$a - $b}, $u, $v) }; (:~ : abs() : Coordinate-by-coordinate absolute value. : : @param $v: vector : @return new vector :) declare function this:abs($v as xs:double*) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:abs($a)}, $v) }; (:~ : sign() : Coordinate-by-coordinate sign. : : @param $v: vector : @return new vector :) declare function this:sign($v as xs:double*) as xs:double* { this:map( function ($a as xs:double) as xs:double { if ($a = 0) then 0E0 else if ($a < 0) then -1E0 else 1E0 }, $v ) }; (:~ : min() : Coordinate-by-coordinate minimum: minimum of each coordinate pair. : : @param $u: one vector : @param $v: another vector : @return new vector :) declare function this:min($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:min(($a,$b))}, $u, $v) }; (:~ : max() : Coordinate-by-coordinate maximum: maximum of each coordinate pair. : : @param $u: one vector : @param $v: another vector : @return new vector :) declare function this:max($u as xs:double*, $v as xs:double*) as xs:double* { this:map2(function ($a as xs:double, $b as xs:double) as xs:double {fn:max(($a,$b))}, $u, $v) }; (:~ : at-min() : Coordinate-by-coordinate minimum: minimum of each coordinate with value. : : @param $v: vector : @param $val: comparison value : @return new vector :) declare function this:at-min($v as xs:double*, $val as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:min(($a, $val))}, $v) }; (:~ : at-max() : Coordinate-by-coordinate maximum: maximum of each coordinate with value. : : @param $v: vector : @param $val: comparison value : @return new vector :) declare function this:at-max($v as xs:double*, $val as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {fn:max(($a, $val))}, $v) }; (:~ : normalize() : Normalize a vector represented as a vector (scale to unit vector), returning : the new vector. : : @param $v: the vector : @return normalized vector :) declare function this:normalize($v as xs:double*) as xs:double* { let $l := this:magnitude($v) return if ($l = 0) then $v else this:map(function ($c as xs:double) as xs:double {$c div $l}, $v) }; (:~ : clamp() : Coordinate-by-coordinate clamping. : : @param $v: vector : @param $min: minimum value : @param $max: maximum value : @return new vector :) declare function this:clamp($v as xs:double*, $min as xs:double, $max as xs:double) as xs:double* { this:map(function ($a as xs:double) as xs:double {util:clamp($a, $min, $max)}, $v) }; (:~ : clampv() : Coordinate-by-coordinate clamping: clamp with corresponding coordinates of : min/max vectors. : : @param $v: vector : @param $minv: minimum values : @param $maxv: maximum values : @return new vector :) declare function this:clampv($v as xs:double*, $minv as xs:double*, $maxv as xs:double*) as xs:double* { this:map3(function ($a as xs:double, $min as xs:double?, $max as xs:double?) as xs:double {util:clamp($a, $min, $max)}, $v, $minv, $maxv) }; (:~ : perpendicular() : Compute the perpendicular vector for a 2D vector represented as a vector. : (Rotated by 90 degrees.) : : @param $v: the vector : @return perpendicular vector :) declare function this:perpendicular($v as xs:double*) as xs:double* { (this:py($v), -this:px($v)) }; (:~ : snap() : Snap the coordinates of the vector, returning the vector with snapped : (i.e. integer) coordinates. NaN snaps to UINT64_MAX. : : @param $vector: the vector :) declare function this:snap($vector as xs:double*) as xs:integer* { for-each($vector, function ($c as xs:double) as xs:integer { if ($c = xs:double("INF") or not($c = $c)) then $util:UINT64_MAX else if ($c = xs:double("-INF")) then -$util:UINT64_MAX else round-half-to-even($c) cast as xs:integer } ) }; (:~ : floor() : Return vector where all the coordinates have been set to the floor of : the original vector's values. Differs from snap() for negative coordinates. : : @param $vector: the vector :) declare function this:floor($vector as xs:double*) as xs:integer* { for-each($vector, function ($c as xs:double) as xs:integer {fn:floor($c) cast as xs:integer}) }; (:~ : decimal() : Perform decimal rounding on all the coordinates (see util:decimal). : : @param $vector: the vector to round : @param $digits: how many digits after the decimal vector to keep :) declare function this:decimal($vector as xs:double*, $digits as xs:integer) as xs:double* { for-each($vector, function ($c as xs:double) as xs:double {util:decimal($c, $digits)}) }; (:~ : quote() : Return a string value for the vector, suitable for debugging. : : @param $vector: the vector to quote :) declare function this:quote($vector as xs:double*) as xs:string { string-join($vector!string(.),",") }; (:~ : valid() : False if a coordinate is INF or NaN :) declare function this:valid($point as xs:double*) as xs:boolean { every $c in $point satisfies $c=$c and not($c=(xs:double("INF"),xs:double("-INF"))) }; (:~ : same() : Equality comparison for vectors, ignoring annotation properties. : Return true() if they have equal coordinates. : : @param $this: one vector : @param $other: the vector to compare it to : @return this=other :) declare function this:same($this as xs:double*, $other as xs:double*) as xs:boolean { let $d := max((count($this), count($other))) return ( deep-equal( this:as-dimension($this, $d), this:as-dimension($other, $d) ) ) }; (:~ : distance() : Euclidean distance between vectors : : @param $v1: one vector : @param $v2: another vector : @return distance(v1,v2) :) declare function this:distance($v1 as xs:double*, $v2 as xs:double*) as xs:double { let $d := fn:max((this:dimension($v1), this:dimension($v2))) return ( math:sqrt( fn:sum( util:zip( function ($c1 as xs:double, $c2 as xs:double) as xs:double { ($c1 - $c2)*($c1 - $c2) }, this:as-dimension($v1, $d), this:as-dimension($v2, $d) ) ) ) ) }; (:~ : polar-distance() : Angular distance between points taken as vectors. :) declare function this:polar-distance( $p as xs:double*, $q as xs:double* ) as xs:double { abs(this:angle((0,0), $p) - this:angle((0,0), $q)) }; (:~ : taxi-distance() : Taxi-cab distance between two points. That is, right angle distance, such as : a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks. :) declare function this:taxi-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( sum( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }; (:~ : avg-distance() : Average of distances along each dimension. :) declare function this:avg-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( avg( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }; (:~ : rail-distance() : Railway distance, e.g distance to the central hub and back out. : The hub is the origin. :) declare function this:rail-distance( $p as xs:double*, $q as xs:double* ) as xs:double { this:distance($p, (0,0)) + this:distance($q, (0,0)) }; (:~ : rail-distance() : Railway distance, e.g distance to the central hub and back out where the hub : is specified. :) declare function this:rail-distance( $p as xs:double*, $q as xs:double*, $origin as xs:double* ) as xs:double { this:distance($p, $origin) + this:distance($q, $origin) }; (:~ : chebyshev-distance() : Chebyshev distance metric. The maximum of the dimensional distances. :) declare function this:chebyshev-distance( $p as xs:double*, $q as xs:double* ) as xs:double { let $d := max((count($p), count($q))) return ( max( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { abs($cp - $cq) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ) ) }; (:~ : minwokski-distance() : Minkowski distance metric of the given order. Certain orders give rise to : other metrics: : order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev; : order<1 is not a metric unless undo the final pow. i.e. X is a metric but : X^(1/order) is not. :) declare function this:minkowski-distance( $p as xs:double*, $q as xs:double*, $order as xs:double ) as xs:double { let $d := max((count($p), count($q))) return ( math:pow( sum( util:zip( function ($cp as xs:double, $cq as xs:double) as xs:double { math:pow(abs($cp - $cq), $order) }, this:as-dimension($p, $d), this:as-dimension($q, $d) ) ), 1 div $order ) ) }; (:~ : cosine-similarity() : Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²) :) declare function this:cosine-similarity( $p as xs:double*, $q as xs:double* ) as xs:double { this:dot($p, $q) div (this:magnitude($p)*this:magnitude($q)) }; (:~ : cosine-distance() : Cosine similarity as a metric: acos(similarity)/π => [0,1] :) declare function this:cosine-distance( $p as xs:double*, $q as xs:double* ) as xs:double { math:acos(this:cosine-similarity($p, $q)) div math:pi() }; (:~ : angle() : Compute the angle (azimuth) from one point to the next, in degrees : Return 0 if points are the same : : @param $last: previous point; use (0,0) if no previous : @param $curr: current point :) declare function this:angle($last as xs:double*, $curr as xs:double*) as xs:double { let $this := $curr=>this:as-dimension(2) let $prev := if (empty($last)) then (0,0) else this:as-dimension($last,2) return if (this:same($prev, $this) or (this:distance($prev,$this) < $config:ε)) then 0 else if (this:px($this)=this:px($prev)) then ( if (this:py($prev) > this:py($this)) then 270 (: remapped -90 :) else 90 ) else ( util:remap-degrees(util:degrees( (math:pi() div 2) - math:atan2(this:px($this) - this:px($prev), this:py($this) - this:py($prev)) )) ) }; (:~ : inclination() : Compute the inclination angle from one point in the next, in degrees : Return 90 if the points are the same : : @param $last: previous point; use (0,0,0) if no previous : @param $curr: current point :) declare function this:inclination($last as xs:double*, $curr as xs:double*) as xs:double { let $curr := $curr=>this:as-dimension(3) let $prev := if (empty($last)) then (0,0,0) else $last=>this:as-dimension(3) let $d := this:distance($prev,$curr) return if (this:same($prev,$curr) or ($d < $config:ε)) then 90 else ( util:remap-degrees(util:degrees( math:acos( (this:pz($curr) - this:pz($prev)) div $d ) )) ) }; (:~ : destination() : Point at a particular distance and direction. : : @param $point: starting point : @param $degrees: bearing from point : @param $length: how far along bearing to travel :) declare function this:destination( $point as xs:double*, $degrees as xs:double, $length as xs:double ) as xs:double* { let $angle := util:radians($degrees) return ( this:px($point) + math:cos($angle)*$length, this:py($point) + math:sin($angle)*$length ) }; (:~ : mutate() : Mutate a sequence of 2D vectors using a mutation function. : : @param $vectors: the vectors : @param $mutate: function that takes a vector as an argument and returns a new vector :) declare function this:mutate( $vectors as xs:double*, $mutate as function(xs:double*) as xs:double* ) as xs:double* { for $i in 1 to count($vectors) idiv 2 return ( $mutate(($vectors[2*$i - 1], $vectors[2*$i])) ) };