User-Defined Functions/User-defined function examples
This page collects examples of function implementations for educational purposes, to illustrate various concepts related to parameter declaration, syntax, array abstraction, etc.
Count number of occurrences in text
Function CountOccurrences( text, pattern : Text atom) : Size(FindInText(pattern,text,repeat:true))
Calling it:
CountOccurrences( "Strawberry", "r" )
→ 3
The use of atom
as a qualifier is necessary because FindInText
with repeat:True
returns a list. With array passed in, without atom
, that would be an array of lists, which is not allowed.
However, you would not need atom
if you implement it as
Function CountOccurrences( text, pattern : Text ) : TextLength(text) - TextLength(TextReplace(text,pattern,"",all:true))
Reverse the characters in a text value
Function TextReverse( t : Text Atom ) : JoinText( Reverse( SplitText( t, "" ) ) )
Example:
TextReverse("Hello") → "olleH"
The use of the Atom
qualifier here is essential. If t
were an array of text strings, then SplitText would return an array of lists of differing lengths, which is disallowed. By keeping t
atomic, we have a single list, which Reverse without an index parameter operates on.
Taylor series approximation
We use here an obscure function as an illustrative example of how to implement a Taylor Series approximation of a function. The Polylogarithm function, [math]\displaystyle{ Li_s(z) }[/math], defined for [math]\displaystyle{ |z|\lt 1 }[/math] and integer order, [math]\displaystyle{ s }[/math] (positive or negative), has uses in quantum physics. The best way to compute it is via its Taylor Series expansion. We'll compare three alternative approaches.
Explicit iteration (inferior method):
Function PolyLog1(z, s) ::= Local res := 0; For k:=1..200 Do res := res + z^k / k^s; res
Using array abstraction (preferred method in this case):
Function PolyLog2(z, s) ::= LocalIndex k:=1..200 Do Sum( z^k / k^s, k )
Array abstraction with global iteration index (great method):
Index Polylog_Iter := 1..200
Function PolyLog3(z, s) ::= Sum( z^PolyLog_iter / PolyLog_iter^s, PolyLog_iter )
We compared speed of evaluation of each across a wide range of input values and measured the wall-time evaluation speed to be 19.4μsec, 1.46μsec and 1.44μsec respectively (Intel Core i7-8700K@3.7GHz, considered a high-end desktop computer as of Aug 2018). Although the third method is fastest, the extra overhead in this case of using the local index rather than the global index is negligible. However, in many cases (especially when parameters are declared atomic), the benefit of using a global index can be more substantial. If in doubt, and speed is really important, go with the third method (use a global iteration index).
Non-negative matrix factorization
Also known as positive matrix factorization. See the article Non-negative matrix factorization for more information on what this factorization is and its uses. The algorithm here uses Lee and Seung's multiplicative update rule which is described in that article.
Given a source matrix of non-negative values, [math]\displaystyle{ A }[/math], decompose it into two matrices [math]\displaystyle{ W }[/math] and [math]\displaystyle{ H }[/math] so that [math]\displaystyle{ A=WH }[/math]. These new matrices have a new latent dimension not shared by A, which is usually smaller that either index of A.
Function NMF( a : NonNegative [I,J] ; I,J,K : Index ; iterations : number atom=30) ::= Local w[I, K] := Random(over:I,K); Local h[K, J] := Random(over:K,J); Local ϵ := 1e-9; { small epsilon to avoid division by zero } For n:=1..iterations Do ( { Update H based on W } h := Relu(h * (Sum(w*a, I) / Max([ϵ,Sum( w * Sum(w*h, K),I)]))); { Update W based on H } w := Relu(w * (Sum(a*h, J) / Max([ϵ,Sum( Sum(w*h,K) * h, J)]))); ); MultiResult(w,h)
Download: Non-Negative Matrix Factorization.ana
The example model contains an example usage of this function.
Many Analytica concepts are demonstrated in this example.
The parameter qualifiers for «a» demonstrate both a data type qualifier (NonNegative
) as well as explicit dimensionality, [I, J]
, in which the I
and J
refer to the second and third parameters of the function.
The first two lines initialize matrices w
and h
to i.i.d. random numbers between 0 and 1. The dimensionality declaration on the variable names is optional, but can help to catch mistakes.
The For..Do syntax illustrates how to iterate from 1 to «iterations».
The use of Relu(x)
shows a shortcut for the equivalent Max( [0, x] )
. The uses of Max( [ϵ, x ])
demonstrate how to find the largest of two or more values by providing both to the first parameter of Max as a list using brackets.
The iterative loop demonstrates iterative assignments to local variables, a side-effect that is allowed in Analytica.
Finally, the last line shows how to return two values from a function (which have different dimensionality) using the MultiResult function, MultiResult(w, h)
, which is synonymous with the underscore function _(w,h)
. The caller would capture these using, e.g.,
Local (w,h) := NMF( A, I,J,K );
The example model demonstrates how to conveniently capture these in two global variables using ComputedBy.
Enable comment auto-refresher