user function

“msharpmath”, The Simple is the Best

[100] 022 Tutorial user function     user function

//————————————————————————————————————
// grammar of function
//————————————————————————————————————
Caution : double semi-colons (;;) activate screen-output inside a function, whereas a semi-colon (;) suppresses screen-output.

If not specified explicitly, a function argument is regarded as ‘double’. In this regard, the following declarations

double f(x) = x ;
double f(double x) = x ;
double f(double x) { return x; }  // standard C grammar

are exactly all the same in m#math. A use of ‘=’ is just for simple-expression function. Other functions are almost the same as in C-language.

//———————————-
// a simple example
//———————————-

#> double fn(x) = |x|*sin(x) ;

#>  fn ;
double fn(x)

#>  fn(3) ;
ans =      0.42336002

#>  fn’(3) ;  // derivative
ans =      -2.8288575

#>  fn”(3) ;  // 2nd-order derivative
ans =      -2.4033449

#>  fn~(0,3);  // integration over (0,3)
ans =       3.1110975

#>  fn++( [1,2,3] ) ; // upgrade for matrix
ans =  [      0.841471       1.81859       0.42336 ]

A double function can be upgraded to accept vector argument by attaching the postfix ‘++’. This upgrade alleviates the use of do-loop.

//———————————-
// return type
//———————————-

Several types including ‘void’ (no type) can be returned by a function. An example is

matrix test(double n) {  //  matrix test(n)  assumes n is also a matrix
(a,b,c) = (0,0,0);
switch(n) {
case 1: a = 10; break;   // case must be followed by an integer only
default: c = 5;
case 2: b = 20; break;
case 3: c = 100;
}
return [a,b,c];
}

#> test(1);
ans =  [            10             0             0 ]

#> test(2);
ans =  [             0            20             0 ]

#> test(3);
ans =  [             0             0           100 ]

#> test(4);
ans =  [             0            20             5 ]

//———————————-
// function upgrade,  matrix mftn(double x)
//———————————-
#> matrix ut(double x) = [ x; 1; x^2 ];
#> ut( 3 );
ans =
[             3 ]
[             1 ]
[             9 ]

#> ut++( [ 3,4,5 ] );
ans =
[             3             4             5 ]
[             1             1             1 ]
[             9            16            25 ]

#> ut++( [ 3,4; 5,6 ] );
ans =
[             3             4 ]
[             5             6 ]
[             1             1 ]
[             1             1 ]
[             9            16 ]
[            25            36 ]

//———————————-
// piecewise functions,  matrix mftn(double x)
//———————————-

matrix g(double x) =   // piecewise functions
[ 0, 1, x ;          // 0 <= x <= 1,  y = x
1, 2, x^2-1;       // 1 <= x <= 2,  y = x^2 - 1
2, 3, x^3-8;       // 2 <= x <= 3,  y = x^3 - 8
3, 4, x^4-81 ];    // 3 <= x <= 4,  y = x^4 – 81

#> g.spl( 1.5 );                // evaluate at x
#> g.spl( [0.5,1.5,2.5,3.5]);   // evaluate at multipoints [x]
ans =            1.25
ans =  [           0.5          1.25         7.625       69.0625 ]

It is also possible to define a typical ‘double(double)’ function utilizing ‘if-else’ clause as follows.

double gg(double x) {
if     ( 0 <= x && x <= 1 ) return x;
else if( 1 <= x && x <= 2 ) return x^2-1;
else if( 2 <= x && x <= 3 ) return x^3-8;
else if( 3 <= x && x <= 4 ) return x^4-81;
}
#> gg( 1.5 );                  // evaluate at x
#> gg++( [0.5,1.5,2.5,3.5]);   // evaluate at multipoints [x]
ans =            1.25
ans =  [           0.5          1.25         7.625       69.0625 ]

//———————————-
// copying argument
//———————————-
Arguments are by default copied inside of a function, and thus original variables are unchanged.

void swap1(x,y) { t = x; x = y; y = t; }
#> x = 3; y = 5;
x =               3
y =               5

#> swap1(x,y); x; y;
x =               3
y =               5

Nothing happens to x and y.

//———————————-
// reference
//———————————-
A variable can be refered by a prefix ‘&’, and the original variables are delivered inside a function.

void swap2(&x,&y) { t = x; x = y; y = t; }
#> x = 3; y = 5;
x =               3
y =               5

#> swap2(x,y); x; y;
x =               5
y =               3

Note that x and y are changed.

//———————————-
// array argument
//———————————-
A prefix ‘*’ is attached to array arguments and data delivery is by reference.

void array(matrix *A)
{
A[0] = .zeros(3);
A[2] = [ 1,2,3,4; 5,6,7,8 ];
}

#> matrix An[4];  array(An);  An;
An = matrix [4]
[0] =
[             0             0             0 ]
[             0             0             0 ]
[             0             0             0 ]
[1] =
[ undefined ]
[2] =
[             1             2             3             4 ]
[             5             6             7             8 ]
[3] =
[ undefined ]

//———————————-
// repeating types
//———————————-
When the same data types are repeated, only the leading variable can be declared such that

(double a,b,c, vertex u,v)

which is the same as

(double a, double b, double c, vertex u, vertex v)

%> an example
complex leading(double a,b,c, vertex u,v) {
z = a+b+c + (u.x*v.x + u.y*v.y)! ;
return z;
}

#> leading(3,4,5, <1,2,-1>,<4,-1,5>);
ans =  12 + 2!

//———————————-
// recurrence function
//———————————-
A function can call itself inside its definition, and this is the case of recurrence function

%> factorial function

double fac(n) {
if( n == 1 ) return 1;
return n*fac(n-1);
}

#> fac(5);
ans =             120

//———————————-
// dynamic assignment
//———————————-
Arguments of a function can be expressed by the arguments defined before.

double area( a, b=a ) = a*b;  // b is expressed by a

#> area ; double area( a, b=a )

#> area(3);
ans =               9

#> area(3,4);
ans =              12

//———————————-
// function argument
//———————————-
A special grammar is innovated for a function argument as follows.

double ffx(x) = x^2;
double ggx(x) = x*sin(x);
double pftn(f(x),x) = f(x)^2;   // function argument f(x)

#> pftn(ffx,3); pftn(ggx,3);
ans =              81
ans =      0.17923371

//———————————-
// auto tracing
//———————————-
A history of calling functions is traced automatically.

void f1(&x1) { x1 = sin(x1);; z1=1;; }
void f2(&x2) { x2 = cos(x2);; f1(x2);; z2=2;; }
void f3(&x3) { x3 = exp(x3);; f2(x3);; z3=3;; }
void f4(&x4) { x4 = x4^2;; f3(x4);; z4=4;; }

#> y=3; f4(y);
y =               3
f4.y =               9
f4.f3.y =       8103.0839
f4.f3.f2.y =      -0.6086217
f4.f3.f2.f1.y =      -0.5717372
f4.f3.f2.f1.z1 =               1
f4.f3.f2.z2 =               2
f4.f3.z3 =               3
f4.z4 =               4

%> case of no reference

void g1(x1) { x1 = sin(x1);; z1=1;; }
void g2(x2) { x2 = cos(x2);; g1(x2);; z2=2;; }
void g3(x3) { x3 = exp(x3);; g2(x3);; z3=3;; }
void g4(x4) { x4 = x4^2;; g3(x4);; z4=4;; }

#> y=3; g4(y);
y =               3
g4.x4 =               9
g4.g3.x3 =       8103.0839
g4.g3.g2.x2 =      -0.6086217
g4.g3.g2.g1.x1 =      -0.5717372
g4.g3.g2.g1.z1 =               1
g4.g3.g2.z2 =               2
g4.g3.z3 =               3
g4.z4 =               4

//———————————-
// Compatibility with C-Language
//———————————-
Consider the following two subroutines developed in C language.

double x_fun(double);
double x_fun(double x) { return exp(x)-sin(x)-2; }

double x_bisect(double (*fun)(double),double,double);
double x_bisect(double (*fun)(double),double xa,double xb ) {
double iter,eps,fa,fb,x,f;

iter = 0;   eps = 1.e-7;
fa = fun(xa);   if( fabs(fa) < eps ) return xa;
fb = fun(xb);   if( fabs(fb) < eps ) return xb;
if( fa*fb > 0 ) { return 0; }
while( ++iter < 200 ) {
x = 0.5*(xa+xb);
f = fun(x);
if( fabs(f) < eps) return x;
if( fa*f > 0 ) { xa = x; fa = f; }
else           { xb = x; fb = f; }
}
return 0;
}

It is easy to find that the role of these two subroutines is to find a root by the bisection method in numerical analysis. The grammars with the function pointer, prototypes are all implemented in m#math too. Therefore, the above two subroutines can be compiled in m#math and thus the following command

#> bisect_x3 = x_bisect(x_fun,0,4);

results in

bisect_x3 =       1.0541271

Comments are closed.