Using the Taylor class of ADiMat

ADiMat comes with a OO class type for computing Taylor series.

Contents

Setting up the Taylor class

This is enabled by the adimat_adjoint command.

adimat_adjoint taylor2

Creating Taylor Objects

This adds the class tseries2 to the path. Its constructor takes a double array and creates a Taylor object of the same size, and loads the the argument in the zero order value of the object.

x = rand(3,2);
tx = tseries2(x);

Inspecting Taylor objects

You can inspect the Taylor object using STRUCT

struct(tx)
ans = 
       m_ord: 11
    m_series: {11x1 cell}

The k-th entry in m_series holds the Taylor coefficients of order k-1. That is m_series{1} holds the value, m_series{2} the first order coefficients and so on.

The official method to access the Taylor coefficients is by using cell-style subscripts with braces {}. As you can see, the higher order components are all zero by default.

tx{1}
tx{2}
ans =
    0.4241    0.9262
    0.3411    0.2985
    0.5414    0.3381
ans =
     0     0
     0     0
     0     0

Setting the truncation order

In field m_ord you see that the default truncation order is 1. m_ord actually holds the number of components in the m_series cell array field. This can be changed by setting the global option 'maxorder', which however affects only newly created objects

set(tx, 'maxorder', 10);
tx = tseries2(x);

Seeding: Initializing coefficients of independent variables

To perform a derivative computation, you should first set (at least) the first order component of (at least) one Taylor object to non-zero values. The concatenation of the first order fields of all input Taylor objects makes up a derivative direction v:

v = ones(numel(x), 1);
tx{2}(:) = v;

Computing with Taylor objects

Now we are ready to compute something, for example the square root of x.

ty = sqrt(tx);

Many other common Matlab functions are also supported. However, for each function you want to apply to the Taylor class, the function must either exist in source code or the Taylor class must have a corresponding method. In this case SQRT is a method of class tseries2.

Reading Taylor coefficients

In ty{1} you now find the function result, sqrt(x), and in t{2} the first order derivative along the direction v, in t{3} the second order coefficients and so on.

ty{1}
ty{2}
ty{3}
ans =
    0.6512    0.9624
    0.5840    0.5464
    0.7358    0.5815
ans =
    0.7678    0.5195
    0.8562    0.9152
    0.6796    0.8599
ans =
   -0.4526   -0.1402
   -0.6276   -0.7665
   -0.3138   -0.6359

You can obtain all derivative coefficients (except the zero order) in an array using the method COEFFS while the zero order values can be accessed with the method VALUE.

TCs = coeffs(ty); whos TCs
assert(isequal(value(tx), x))
  Name      Size              Bytes  Class     Attributes

  TCs       3x2x10              480  double              

Using Taylor coefficients to approximate nonlinear functions

The Taylor coefficients in ty may be used to compute approximations at some point sqrt(x + h.*v) without actually evaluating the function. This is done with the method EVAL.

h = 0.1;
eval(ty, h)
ans =
    0.7239    1.0130
    0.6641    0.6313
    0.8008    0.6619

Compare this to the true function result

d = eval(ty, h) - sqrt(x + h .* reshape(v, size(x)))
d =
   1.0e-07 *
   -0.0054   -0.0000
   -0.0513   -0.2020
   -0.0004   -0.0561

The error in the approximation by EVAL is bounded by the last term in the Taylor sum. This is returned by the method ERRORS.

assert(all(all(errors(ty, h) > abs(d))))
errors(ty, h)
ans =
   1.0e-07 *
    0.0321    0.0000
    0.2543    0.9022
    0.0032    0.2764

As you can see the approximation is less exact for the components in x which are closer to zero. Also the approximation is more exact when h is smaller.

h = 0.01;
d = eval(ty, h) - sqrt(x + h .* reshape(v, size(x)))
d =
   1.0e-15 *
    0.1110         0
         0         0
         0   -0.1110

Using Taylor objects in vector mode

For Taylor objects in vector mode there is a separate class which is enabled by the adimat_adjoint command.

adimat_adjoint taylor3

This class has the same interface.

x = rand(7);
tx = tseries2(x);

It has two global options: The truncation order (maxorder) and the constructor handle of the inner derivative container class (inner). The derivative container class must also be made available in the path with adimat_derivclass.

adimat_derivclass arrderivclass
set(tx, 'maxorder', 50);
set(tx, 'inner', @arrdercont); % this is the default

Set the global property ndd of the inner class to the desired number of directional derivatives (ndd)

set(tx{2}, 'ndd', numel(x));

Changes take effect for newly created objects.

tx = tseries2(x);

tx{1}
tx{2}
ans =
    0.8595    0.6279    0.5876    0.4665    0.2332    0.9806    0.5274
    0.3405    0.4504    0.8776    0.4981    0.8616    0.6448    0.7250
    0.1381    0.4736    0.4691    0.4874    0.7117    0.8964    0.6074
    0.5078    0.9497    0.4374    0.2295    0.8728    0.4822    0.5884
    0.8567    0.0835    0.7462    0.0856    0.9380    0.0141    0.4334
    0.3843    0.2798    0.4679    0.0674    0.1397    0.6229    0.2442
    0.6957    0.4470    0.8608    0.8884    0.3939    0.2311    0.4290
ans = 
	arrdercont object: 7-by-7

As you can see the higher order coefficients are now objects of a derivate container class. Now we can initialize the first order coefficients with bundles of derivative directions.

S = rand(numel(x));
tx{2} = set(tx{2}, 'deriv', reshape(S.', [numel(x), size(x)]));
assert(isequal(admJacFor(tx{2}), S))

Let us now compute the matrix exponential.

fun = @expm;
ty = fun(tx);

The method COEFFS of this class will return the coefficients in the fixed format of a 3D array with dimensions [numel(x), ndd, maxOrder].

TCs = coeffs(ty); whos TCs
  Name       Size                Bytes  Class     Attributes

  TCs       49x49x50            960400  double              

The method EVAL can compute approximations of the form expm(x + h(1).*S(:,1) + h(2).*S(:,2) + ...).

h = rand(size(x)) .* 1e-4;
eval(ty, h)
ans =
    7.6178    5.5898    7.3976    4.6563    6.3338    7.2655    5.9396
    6.3066    6.3865    7.9095    4.7793    7.4294    6.7330    6.2793
    5.1026    4.5614    7.3360    3.9895    6.1400    5.9275    5.1961
    6.2454    5.5963    7.2256    5.3255    7.1318    6.2773    5.9108
    5.5141    3.7621    6.0123    3.3283    6.8014    4.6659    4.6082
    3.2034    2.6913    3.8408    2.1374    3.1367    4.7809    2.9143
    6.3420    5.3779    7.4927    4.9989    6.6652    6.2180    6.8055

Compare this to the true function result. The difference is now much larger as we compute univariate Taylor coefficients only and are ignoring the mixed derivative directions.

d_vec = norm(eval(ty, h) - fun(x + h))
d_vec =
    0.3426

You can set the truncation order for the evaluation of the series to a smaller value than what was computed

d_o3 = norm(eval(ty, h, 3) - fun(x + h))
d_o2 = norm(eval(ty, h, 2) - fun(x + h))
d_o1 = norm(eval(ty, h, 1) - fun(x + h))
d_o3 =
    0.3426
d_o2 =
    0.3426
d_o1 =
    0.3426

You can also evaluate just a few of the derivative directions, for example compute expm(x + h.*v1) and expm(x + h.*v2), where v1 and v2 are the first and second derivative direction. Compare that to the true result, where we get v1 and v2 back from the input Taylor object.

h = 1e-1;
d_dir1 = norm(eval(ty, h, 20, 1) - fun(x + h.*tx{2}{1}))
d_dir2 = norm(eval(ty, h, 20, 2) - fun(x + h.*tx{2}{2}))
d_dir3 = norm(eval(ty, h, 20, 3) - fun(x + h.*tx{2}{3}))
d_dir1 =
   1.9090e-14
d_dir2 =
   2.0469e-14
d_dir3 =
   8.3088e-14

With just a single direction the Taylor series approximation is much more precise again, for we are evaluating then same things as with the scalar mode Taylor objects, but we ran the function just once for all the directions.

d_dir1_o6 = norm(eval(ty, h, 6, 1) - fun(x + h.*tx{2}{1}))
d_dir1_o3 = norm(eval(ty, h, 3, 1) - fun(x + h.*tx{2}{1}))
d_dir1_o2 = norm(eval(ty, h, 2, 1) - fun(x + h.*tx{2}{1}))
d_dir1_o1 = norm(eval(ty, h, 1, 1) - fun(x + h.*tx{2}{1}))
d_dir1_o6 =
   5.7434e-06
d_dir1_o3 =
    0.0273
d_dir1_o2 =
    0.3127
d_dir1_o1 =
    2.7330

Here we are evaluating the same things as when

Common problems with Taylor objects

Using the Taylor objects the same problems may occur as with any other overloaded type in Matlab. Possibly the most common problem regards the indexed assignment of a Taylor object into a double array. Consider the following example:

try
  tz = zeros(numel(x),1);
  for k=1:numel(x)
    tz(k) = tx(k) .^ k;
  end
catch
  l = lasterror;
  disp(l.message)
end
The following error occurred converting from tseries2 to double:
Error using double
Conversion to double from tseries2 is not possible.

This problem might be considered a bug in Matlab, for if the indexed assignment is rewritten as a call to subsasgn, the overloaded subsasgn is called with no problem.

for k=1:numel(x)
  tz = subsasgn(tz, struct('type', '()', 'subs', {{k}}), tx(k) .^ k);
end

Another common workaround however is to change to initialization of t such that it is a Taylor object from the start, taking care that the value and derivatives are still as desired. For example, by adding a zero Taylor object, which can be produced from any available Taylor object by multiplying with 0.

tz = zeros(numel(x),1) + tx(1).*0;
for k=1:numel(x)
  tz(k) = tx(k) .^ k;
end