Maple 2020 Questions and Posts

These are Posts and Questions associated with the product, Maple 2020

Maple's pdsolve() is quite capable of solving the PDE that describes the motion of a single-span Euler beam.  As far as I have been able to ascertain, there is no obvious way of applying pdsolve() to solve multi-span beams.  The worksheet attached to this post provides tools for solving multi-span Euler beams.  Shown below are a few demos.  The worksheet contains more demos.

 

A module for solving the Euler beam with the method of lines

beamsolve

 

The beamsolve proc solves a (possibly multi-span) Euler beam equation:``

"rho ((∂)^2u)/((∂)^( )t^2)+ ((∂)^2)/((∂)^( )x^2)(EI ((∂)^(2)u)/((∂)^( )x^(2)))=f"

subject to initial and boundary conditions.  The solution u = u(x, t) is the

transverse deflection of the beam at point x at time t, subject to the load
density (i.e., load per unit length) given by f = f(x, t). The coefficient rho 

is the beam's mass density (mass per unit length), E is the Young's modulus of

the beam's material, and I is the beam's cross-sectional moment of inertia

about the neutral axis.  The figure below illustrates a 3-span beam (drawn in green)
supported on four supports, and loaded by a variable density load (drawn in gray)
which may vary in time.  The objective is to determine the deformed shape of the
beam as a function of time.


The number of spans, their lengths, and the nature of the supports may be specified

as arguments to beamsolve.

 

In this worksheet we assume that rho, E, I are constants for simplicity. Since only
the product of the coefficients E and I enters the calculations, we lump the two

together into a single variable which we indicate with the two-letter symbol EI.

Commonly, EI is referred to as the beam's rigidity.

 

The PDE needs to be supplied with boundary conditions, two at each end, each

condition prescribing a value (possibly time-dependent) for one of u, u__x, u__xx, u__xxx 
(that's 36 possible combinations!) where I have used subscripts to indicate

derivatives.  Thus, for a single-span beam of length L, the following is an admissible

set of boundary conditions:
u(0, t) = 0, u__xx(0, t) = 0, u__xx(L, t) = 0, u__xxx(t) = sin*t.   (Oops, coorection, that last
condition was meant to be uxxx(L,t) = sin t.)

Additionally, the PDE needs to be supplied with initial conditions which express

the initial displacement and the initial velocity:
"u(x,0)=phi(x),   `u__t`(x,0)=psi(x)."

 

The PDE is solved through the Method of Lines.  Thus, each span is subdivided into

subintervals and the PDE's spatial derivatives are approximated through finite differences.

The time derivatives, however, are not discretized.  This reduces the PDE into a set of

ODEs which are solved with Maple's dsolve().  

 

Calling sequence:

        beamsolve(L, n, options)

 

Parameters:

        L:  List of span lengths, in order from left to right, as in [L__1, L__2 .. (), `L__ν`].

        n The number of subintervals in the shortest span (for the finite difference approximation)

 

Notes:

• 

It is assumed that the spans are laid back-to-back along the x axis, with the left end
of the overall beam at x = 0.

• 

The interior supports, that is, those supports where any two spans meet, are assumed
to be of the so-called simple type.  A simple support is immobile and it doesn't exert
a bending moment on the beam.  Supports at the far left and far right of the beam can
be of general type; see the BC_left and BC_right options below.

• 

If the beam consists of a single span, then the argument L may be entered as a number
rather than as a list. That is, L__1 is equivalent to [L__1].

 

Options:

        All options are of the form option_name=value, and have built-in default values.

        Only options that are other than the defaults need be specified.

 

        rho: the beam's (constant) mass density per unit length (default: rho = 1)

        EI: the beam's (constant) rigidity (default: EI = 1)

        T: solve the PDE over the time interval 0 < t and t < T (default: T = 1)

        F: an expression in x and t that describes the applied force f(x, t)  (default: F = 0)
        IC: the list [u(x, 0), u__t(x, 0)]of the initial displacement and velocity,  as
                expressions in x (default: IC = [0,0])

        BC_left: a list consisting of a pair of boundary conditions at the left end of
                the overall (possibly multi-span beam.  These can be any two of
                u = alpha(t), u_x = beta(t), u_xx = gamma(t), u_xxx = delta(t). The right-hand sides of these equations

                can be any expression in t.  The left-hand sides should be entered literally as indicated.

                If a right-hand side is omitted, it is taken to be zero.   (default: BC_left = [u, u_xx] which

                corresponds to a simple support).

        BC_right: like BC_left, but for the right end of the overall beam (default: BC_right = "[u,u_xx])"

 

The returned module:

        A call to beamsolve returns a module which presents three methods.  The methods are:

 

        plot (t, refine=val, options)

                plots the solution u(x, t) at time t.  If the discretization in the x direction

                is too coarse and the graph looks non-smooth, the refine option

                (default: refine=1) may be specified to smooth out the graph by introducing

                val number of intermediate points within each discretized subinterval.

                All other options are assumed to be plot options and are passed to plots:-display.

 

        plot3d (m=val, options)

                plots the surface u(x, t).  The optional m = val specification requests

                a grid consisting of val subintervals in the time direction (default: "m=25)"

                Note that this grid is for plotting purposes only; the solution is computed

                as a continuous (not discrete) function of time. All other options are assumed

                to be plot3d options and are passed to plots:-display.

 

        animate (frames=val, refine=val, options)

                produces an animation of the beam's motion.  The frames option (default = 50)

                specifies the number of animation frames.  The refine option is passed to plot
                (see the description above. All other options are assumed to be plot options and
                are passed to plots:-display.

Note:

        In specifying the boundary conditions, the following reminder can be helpful.  If the beam

        is considered to be horizontal, then u is the vertical displacement, `u__x ` is the slope,  EI*u__xx

        is the bending moment, and EI*u__xxx is the transverse shear force.

> 

 

A single-span simply-supported beam with initial velocity

 

The function u(x, t) = sin(Pi*x)*sin(Pi^2*t) is an exact solution of a simply supported beam with

"u(x,0)=0,   `u__t`(x,0)=Pi^(2)sin(Pi x)."  The solution is periodic in time with period 2/Pi.

> 

sol := beamsolve(1, 25, 'T'=2/Pi, 'IC'=[0, Pi^2*sin(Pi*x)]):
sol:-animate(size=[600,250]);

The initial condition u(x, 0) = 0, u__t(x, 0) = 1  does not lead to a separable form, and

therefore the motion is more complex.

> 

sol := beamsolve(1, 25, 'T'=2/Pi, 'IC'=[0, 1]):
sol:-animate(frames=200, size=[600,250]);


> 

 

A single-span cantilever beam

 

A cantilever beam with initial condition "u(x,0)=g(x),  `u__t`(x,0)=0," where g(x) is the
first eigenmode of its free vibration (calculated in another spreadsheet).  The motion is
periodic in time, with period "1.787018777."

> 

g := 0.5*cos(1.875104069*x) - 0.5*cosh(1.875104069*x) - 0.3670477570*sin(1.875104069*x) + 0.3670477570*sinh(1.875104069*x):
sol := beamsolve(1, 25, 'T'=1.787018777, 'BC_left'=[u,u_x], 'BC_right'=[u_xx,u_xxx], 'IC'=[g, 0]):
sol:-animate(size=[600,250]);

If the initial condition is not an eigenmode, then the solution is rather chaotic.

> 

sol := beamsolve(1, 25, 'T'=3.57, 'BC_left'=[u,u_x], 'BC_right'=[u_xx,u_xxx], 'IC'=[-x^2, 0]):
sol:-animate(size=[600,250], frames=100);


> 

 

A single-span cantilever beam with a weight hanging from its free end

 
> 

sol := beamsolve(1, 25, 'T'=3.57, 'BC_left'=[u,u_x], 'BC_right'=[u_xx,u_xxx=1]):
sol:-animate(size=[600,250], frames=100);


> 

 

A single-span cantilever beam with oscillating support

 
> 

sol := beamsolve(1, 25, 'T'=Pi, 'BC_left'=[u=0.1*sin(10*t),u_x], 'BC_right'=[u_xx,u_xxx]):
sol:-animate(size=[600,250], frames=100);


> 

 

A dual-span simply-supported beam with moving load

 

Load moves across a dual-span beam.

The beam continues oscillating after the load leaves.

> 

d := 0.4:  T := 4:  nframes := 100:
myload := - max(0, -6*(x - t)*(d + x - t)/d^3):
sol := beamsolve([1,1], 20, 'T'=T, 'F'=myload):
sol:-animate(frames=nframes):
plots:-animate(plot, [2e-3*myload(x,t), x=0..2, thickness=1, filled=[color="Green"]], t=0..T, frames=nframes):
plots:-display([%%,%], size=[600,250]);


> 

 

A triple-span simply-supported beam with moving load

 

Load moves across a triple-span beam.

The beam continues oscillating after the load leaves.

> 

d := 0.4:  T := 6: nframes := 100:
myload := - max(0, -6*(x - t)*(d + x - t)/d^3):
sol := beamsolve([1,1,1], 20, 'T'=T, 'F'=myload):
sol:-plot3d(m=50);
sol:-animate(frames=nframes):
plots:-animate(plot, [2e-3*myload(x,t), x=0..3, thickness=1, filled=[color="Green"]], t=0..T, frames=nframes):
plots:-display([%%,%], size=[600,250]);

> 

z3d;


> 

 

A triple-span beam, moving load falling off the cantilever end

 

In this demo the load move across a multi-span beam with a cantilever section at the right.

As it skips past the cantilever end, the beam snaps back violently.

> 

d := 0.4:  T := 8: nframes := 200:
myload := - max(0, -6*(x - t/2)*(d + x - t/2)/d^3):
sol := beamsolve([1,1,1/2], 10, 'T'=T, 'F'=myload, BC_right=[u_xx, u_xxx]):
sol:-animate(frames=nframes):
plots:-animate(plot, [1e-2*myload(x,t), x=0..3, thickness=1, filled=[color="Green"]], t=0..T, frames=nframes):
plots:-display([%%,%], size=[600,250]);


> 

 


Download worksheet: euler-beam-with-method-of-lines.mw

 

The uploaded worksheet contains a contourplot whose display I can't understand. Can you explain it?

Countourplot.mw

From Wikipedia,

However, when I plug the formula of u(x, t) into Maple, it doesn't seem to satisfy the PDE and is stuck evaluating.
 

> 

restart

> 

eq := diff(u(x, t), t)-k*(diff(u(x, t), x, x)) = f(x, t)

diff(u(x, t), t)-k*(diff(diff(u(x, t), x), x)) = f(x, t)

(1)
> 

ic := u(x, 0) = 0

u(x, 0) = 0

(2)
> 

"G(x,t):=1/(sqrt(4*pi*k*t))exp(-(x^(2))/(4*k*t))"

proc (x, t) options operator, arrow, function_assign; exp(-(1/4)*x^2/(k*t))/sqrt(4*pi*k*t) end proc

(3)
> 

ans := u(x, t) = int(G(x-y, t-s)*f(y, s), y = -infinity .. infinity, s = 0 .. t)

u(x, t) = (1/2)*(int((int(exp(-(1/4)*(x-y)^2/(k*(t-s)))*f(y, s), y = -infinity .. infinity))/(pi*k*(t-s))^(1/2), s = 0 .. t))

(4)
> 

`assuming`([simplify(pdetest(ans, [eq, ic]))], [t > 0])

> 

``


 

Download InhomoHeat.mw

The uploaded worksheet contains two examples of the use of VectorCalculus[VectorSpace]. The first example seems explicable but the second does not.

I have tried and failed to find a full, clear explanation of how a vector describing a simple vector, spacecurve, or surface in the default vector space is transformed to appear in a user defined vector space.

Can anyone direct me to such an explanation, so that I can understand Maple's processing within the uploaded worksheet and enable me to use this Maple feature to future advantage?

VectorSpaceTest.mw

Hello again,

 

I have two functions f and h, that I want to show by pointplot as a diagramm (with a range from -10 to +10).

I know how to do it with "normal" functions, but what if for example the function f has 2 variables but h only has one?

f:= (sin(sqrt(x^2+y^2)))/(sqrt(x^2+y^2));

h:= f(x, 1.8);

These are the two functions.

 

So far I've made a list for each function for x  = -10 to x = 10

ListF:= [seq(f, x = -10..10, 1)];

ListH:= [seq(h, x = -10..10, 1)];

 

But I don't know how to go from there. Can I already use pointplot to connect the two lists? Or do I have to connect these two lists as one (which is what I originally planned) But I don't know how to do that, without always copying the results of one and then the other and putting both in a new list.

e.g Both lists show the values for the function for x = -10..10. If the first value of ListF is 5 and the first value of ListH is 1, then connecting both in a new list ListFH: = [[5,1]...], followed by the 9 other values.)

(5 and 1 aren't the actual results, I just used them for simplification.)

(Even if this isn't the right beginning for the solution of my original problem, I still want to know if it's even possible to take all values of one list of a function 'a' and all values of a second list of function 'b' and put them in a new list 'c' with c: = [a,b] without manually putting them together. So that the first value of list a is paired with the first of list b, the second of list a with the second of list b, etc... I hope you know what I mean)

In regards to my original problem I thought I could do something like this:

pointplot( [ [ListF], [ListH] ]);

 

I'm grateful for every answer :)

 

 

 

 

 

 

 

When making a function and using the return type, I found Maple is not catching the case when the function returns wrong type.

Actually there are two issues in this example I like to ask about. First, here is the example

restart;
kernelopts(assertlevel):=2;
TypeTools:-AddType(type_A, record(x::integer)):

foo:=proc( A::type_A ) ::type_A;  #this is what return type should be
     print( type(A,'type_A')); #this prints TRUE
     A:-x :=1.5; #opps, wrong type, changes type of record now!
     print( type(A,'type_A')); #This now prints FALSE
     return A;
end proc:

A:=Record('x' = 1);
B:=foo(A):
eval(B)

The call to foo() goes through as expected since type is correct.

First issue: Inside the function doing A:-x :=1.5;  changed the type of record now, since x is supposed to be integer and now become real. Is there a way to have Maple detect this at run time?

Second issue: The function was defined to return type_A  but due to this overwriting the field of the record by wrong value, it is no longer type_A  (the print now says false). But why Maple did not detect this? Since the function is defined to only return type_A ?

Is it possible to change the code above to catch these type mistakes I made?

given A := [[1, 0,9], [5, 0, 6], [4, 0, 0]]; and to remove 0 from each entry, I used map2(remove,has,A,0) which gives 

                   [[1, 9], [5, 6], [4]]

Is it possible to do the same thing using ~ notation for mapping?  (Element-wise Operator). Where now each element is each entry in A? I can't figure what the syntax would be if possible.  remove(has,??,0)~A

For exmaple in Mathematica one would use what is called slot number #. So the above mapping would look like in Maple as doing remove(has,#,0)~A  where now # acts as place holder to be filled by each entry taken one at a time from list A as the mapping runs.  But do not know if this is possible in Maple.   

map2 does the job, but can be tricky to use when the function to map takes number of different arguments.

One work around is to make seperate function, and use that for mapping, as follows

f:= x->remove(has,x,0);
f~(A)

which gives  [[1, 9], [5, 6], [4]] correctly. 

So I find the above easier to understand and more clear than map2, and I am not unhappy with it, and so I see no reason to use map2 vs. ~ in this case.

But wanted to see if it was possible to use ~ without making separate function, but do it in-line if you well (using what is called pure function, or unamed function in functional programming).

 

I am trying to decide which is better to use, a table or Record, to pass lots of information back and forth between functions inside say a personal Maple package.

So instead of passing each argument on its own, as normally one does when calling a function, instead all the input is put into one table, or into one record, and this is passed instead. So the function always takes as input one argument.

When the function returns result, it also does the same. It updates any fields it needs in the table or in the record, o rmay be add new field, which the caller would have to know about, (since same person has programmed both) and returns that.

I find this easier to handle, than passing each argument on its own.  And on return, rather than return many results in list or sequence and making sure they are in correct order, the function simply update the fields in the table or the record. No problems with wrong order here as caller will read these fields by name later on.

So I made same toy example, one using table and one using record. 2 numbers are passed to a function. The function returns the result of multiplication by adding new field into the table/record.  Here is version using table.

restart;
foo:=proc(input::table)
   local x,y,result,updated;

   x      := input["x"];
   y      := input["y"];
   result := x*y;

   #finished computation. Return result. Copy all content
   #of passed table first, then add new field to write result to

   updated := eval(input);
   updated["result"] := result;
   return updated;      

end proc:

Called using

input:=table(["x"=4,"y"=6]);
r:=eval(foo(input)):
print(r["result"]);

prints 24

 

Here is version using Record

foo2:=proc(input::record)
   local x,y,result,updated;

   x      := input:-x;
   y      := input:-y;
   result := x*y;

   #finished computation. Return result
   #could not update input Record in place, even after deep copy
   #since "result" is not an existing field. So have to make a new Record
   #this is a problem, since this function need to know all fields now in Record

   updated := Record("x"=x,"y"=y,"result"=result);
   return updated;      

end proc:

called as

input:=Record( "x" = 4, "y"=6);
r:=eval(foo2(input)):
print(r:-result);

prints 24

 

Some observations: I found the table more flexible. Because the function called can add a new field to the table, even if this field do not exist. If called function wants to add a new result back to the caller, it can do this.  With Record, all fields have to be decided on when the record is created. So the function has to create new record from all the old fields and add the new field to do this.

This might not be a big problem actually, as one can decide at top of package what all the possible fields that needs to be in the Record, and creates such Record, giving default values for al field. So now Record will have all possible fields in it.  Using this the above can now be written as

restart;
foo3:=proc(input::record)
   local x,y,result;

   x      := input:-x;
   y      := input:-y;
   result := x*y;

   #finished computation. Return result  
   input:-result := result; 
   return input;      

end proc:

And called as

input:=Record( 'x' = 4, 'y'=6, 'result'=NULL);
r:=eval(foo3(input)):
print(r:-result);

24

But it seems both methods suffer from the fact that type checking on the types of the arguments now inside is lost. (But I seem to remember one can do type checking on fields for a Record somehow). So if this is possible, this will be one advantage of Record over table.  But again, it is possible in both methods to check type of field by using whattype() or type() manually if needed.

But other than these two issues, and assuming one wants to pick between table and record, the question is, why choose Record over table?  If the main purpose is to just to pass information back and forth? Is there some hidden advatage to using Record I am overlooking that tables do not provide (again, for the purpose of passing arguments and results between functions and no more).

For me, I see table doing what I want from a record, but a little bit easier to work with as well (field names can be strings for example).  So with table one does  A["x"]=.....  While with Record one does A:-x=...

For me, I see a Maple table just like Python dictionary, so easy to underatand and work with.

Any thoughts from the experts on this subject? 

There are discrepancies between Maple's solution of Fourier transforms and the results printed in USA NIST Handbook of Mathematical Functions, page 30

fourier(exp(-a*abs(x))/sqrt(abs(x)),x,s) assuming a>0;
            /   /   (1/2)   (1/2)                (1/2)  
        1   |   |2 2      Pi      signum(s - _U1)       
       ---- |int|-------------------------------------,
       2 Pi |   |       /   2    \                      
            |   |       |_U1     |          (1/2)       
            |   |     a |---- + 1| (s - _U1)            
            |   |       |  2     |                      
            \   \       \ a      /                      

                                    \\
                                    ||
         _U1 = -infinity .. infinity||
                                    ||
                                    ||
                                    ||
                                    ||
                                    //


For this transform of
                 "exp(-a*abs(x))/sqrt(abs(x))"

 the result in the NIST table is
          "sqrt(a + sqrt(a^2 + s^2))/sqrt(a^2 + s^2)"

 .
fourier(sinh(a*t)/sinh(Pi*t),x,s) assuming a>-Pi, a<Pi;
                    2 sinh(a t) Pi Dirac(s)
                    -----------------------
                          sinh(Pi t)       

For this transform of sinh(a*x)/sinh(Pi*x)   the result in the NIST table is
                         "1/sqrt(2*Pi)"  "sin(a)/(cosh(s) + cos(a))"

 
fourier(cosh(a*t)/cosh(Pi*t),x,s) assuming a>-Pi, a<Pi;
                    2 cosh(a t) Pi Dirac(s)
                    -----------------------
                          cosh(Pi t)       

For this transform of cosh(a*x)/cosh(Pi*x) the result in the NIST table is  
                          "sqrt(2/Pi) cos(a/2)*cosh(s/2)/(cosh(s) + cos(a))"

These disparities are significant, apart from the fact that Maple failed to solve the first example above.

 

I've never noticed this before, becuase I do not like to with()  packages, but like to add the name of the package at each command and alwsys try to use explicit commands.

Compare this first example, which does not work, since the "." in the codeis the global "dot" command ?dot

restart;
VC:=VectorCalculus:
f:=y*sin(x)+y^2;
VC:-Nabla . VC:-Nabla(f,[x,y])

So the dot above is the one used for normal dot product, which is visible without loading any package as in: <1,2>.<3,4> gives 11

Now when I next loaded the VectorCalculus explicitly, but kept the same code the same, i.e. using the same ".", now it worked

restart;
with(VectorCalculus);
VC:=VectorCalculus:  #do not need this now, but to keep the code the same
f:=y*sin(x)+y^2;
VC:-Nabla . VC:-Nabla(f,[x,y])

It worked because Maple now used VectorCalculus:-`.`  ,Even though I did not tell it to do this, it searched the package since it is loaded) and now decided to use VectorCalculus:-`.` instead of the global `.` dot defined before loading this package.

But how did Maple know which dot to use? 

Did it say to itself, the dot in the middile must be VectorCalculus:-`.` and not the global `.` due to the type of the operands around the dot, it automatically found the correct match? 

So imagine if one has loaded more packages, and some might have its own `.` operator that takes the same operands, does Maple search them all to find the correct dot to use based on type of operands to bind it to? and how in this case it knows which to use, if there is more than one match among all loaded packages? I can see this causing confusion, becuase one is never sure in this case which operator was used. I do not know if Maple issues a warning in this case or not.

I do not use input 2D math in document mode. But was trying it now to see if I can enter this to check my hand solution

In worksheet, this is what I do

restart;
VC:=VectorCalculus:
f:=y*sin(x)+y^2;
VC:-DotProduct(VC:-Del,VC:-Gradient(f,[x,y]))

But I do not know how to enter   if possible, in document mode 2D math. THis is what I tried. Created document mode. Picked the DEL operator from the left side common symbols window. But I do not know how to do the rest.

Clearly I am doing something silly. This is why I prefer plain Maple code, becuase I can see what I am typing :)

But was wondering, how to do the above in docuemnt mode + 2D math? To make it look close to how it shows in the book. Does one need special palette for this?

 

 

Hello

In Section 14.5 - External Calling ..., page 489, the authors gave an example on how to use the command line in Unix to call maple with arguments. I followed the example but added a mpl-file to see if the arguments were sent to it.  

Example (Linux):

/opt/maple2020/bin/maple -c n:=4 -c 'path:="/home/eduardo/examples";' example.mpl > output.txt &

Maple sends out the following error message: "Error, incorrect syntax in parse: '/' unexpected (near 7th character of parsed string)".

The msg is clear where the error is but I could not figure out what to do. The single quotes were supposed to take care of that, weren't they?  

Many thanks.

Ed

Edit: I have added the double-quotes.   The problem still persists.  

I wanted to overwrite an existing variable of type table by the one returned from a call to a proc().

But it does not work, unless eval() is called on the returned result from the proc()

But if the variable that recieves the return value from the proc() was not already a table type, then it works without using eval. Why is that? 

Here is an example

restart;
foo:=proc(A::table)
  A["c"]:=3; #add new field
  return A;
end proc;

A:=table():
A["a"]:=1:
A["b"]:=2:
B:=foo(A):
whattype(B);
print(B)

           table(["a" = 1, "c" = 3, "b" = 2])

In the above, it worked. B is table now, and assigned the updated table from the proc.  But 

A:=table():
A["a"]:=1:
A["b"]:=2:
A:=foo(A): #without eval, it does not work.
whattype(A);
print(A)

             symbol
               A

I expected A to be overwritten, just like B was. To fix this, I have to change A:=foo(A): by A:=eval(foo(A)):

But why eval was not needed in the first example, and was needed for the second example?

btw, the same thing happens with Record(), not just table()

just like to understand the reasoning behind this. I expected both cases to work the same way. But it works different if the variable happend to be unassigned (like B above).

Hello all,

I was hoping to get some general tips for tackling numeric integrals. As someone with little experience in the subject, I find myself overwhelemed by the many different integration methods. 

Experts, what are the first steps you take when trying to find a numeric solution to an integral? How might you zero-in on a particular integration method? What about tweaking error parametrs, etc.? Is there a general framework for approaching these problems, or is it all guesswork?

Thanks!

How can I create an even function, g, in Maple? I want Maple to give g(x) - g(-x) as 0.

First 49 50 51 52 53 54 55 Page 51 of 57