MaplePrimes Posts

MaplePrimes Posts are for sharing your experiences, techniques and opinions about Maple, MapleSim and related products, as well as general interests in math and computing.

Latest Post
  • Latest Posts Feed
  • We recently published a paper on multiscale-multidomain simulation of battery models.

    Some challenges are listed at

    Maple and symbolic math can play a critical role in solving many challenging problems. For example, consider a seemingly-trivial problem

    uxx+uyy = 0

    x = 0 and x = 1, ux = 0 for all y

    y = 1, u = 0, for all x

    y = 0, u =1, 0<=x<=0.5

    y = 0, uy = 0, 0.5<x<=1

    There is a singularity at (0.5,0) and most numerical methods will have trouble there. In these equations, uxx means the second derivative of u with respect to x.

    Maple can help solve this problem with conformal mapping to achieve arbitrary precision. As of today, machine precision is not possible with any numerical method even with millions of Degrees of Freedom. The Maple code is given below. A FEM code is given below as well. Models like this can benefit from Maple adding parallel sparse direct and iterative solvers.





    Digits := 15


     The domain is tranformed from Z = X+IY to w. The points tranformed are


    Zdomain := [[0, 0], [1, 0], [1, 1], [0, 1]]



    wdomain := [0, 1, 1+a, a+2]



    eq1 := diff(Z(w), w) = -I*K1/(w^(1/2)*(w-1)^(1/2)*(w-a-1)^(1/2)*(w-a-2)^(1/2))


     a is not known apriori. The value of a should be found to make sure [1,1] in the Z coordinate is transformed to [1+a] in the w coordinate.


    a := 2^(1/2)-1


     Value of K1 is found using the transformation of [1,0] to 1 in the w coordinate


    eq11 := 1 = -K1*2^(1/2)*EllipticK(2^(1/2)/2)



    K1 := -(1/2)*(2^(1/2)/EllipticK(2^(1/2)/2))


     The height Y in the Z coordinate is found by integrating from 1 to 1+a in the w coordinate.







     The choice of a = sqrt(2)-1 gives the height of 1 for Z coordinate.







     integration from 0 to 1+a in the w coordinate gives 1,1 in the Z coordinate.


    EllipticF(2^(1/2)/(2+2^(1/2))^(1/2), 2^(1/2)/2)/EllipticK(2^(1/2)/2)





     Integrating w from 0 to wmid =1/sqrt(2) gives the point 0.5,0 in the Z coordinate


    wmid := 2^(1/2)/2


     Next w domain is transformed to Z2 domain Z2 = X2+IY2


    Z2domain := [[0, 0], [1, 0], [1, H], [0, H]]



    wdomain2 := [0, 2^(1/2)/2, 2^(1/2), 2^(1/2)+1]



    eq2 := diff(Z2(w), w) = -(2*I)*K2/(w^(1/2)*(4*w-2*2^(1/2))^(1/2)*(w-2^(1/2))^(1/2)*(w-2^(1/2)-1)^(1/2))


     K2 is found based on the transformation of wmid to [1,0] in Z2 coordinate.


    eq21 := 1 = -2*K2*EllipticK(1/(((2+2^(1/2))^(1/2))))/(2^(1/2)+1)^(1/2)



    K2 := -(1/2)*((2^(1/2)+1)^(1/2)/EllipticK(1/(((2+2^(1/2))^(1/2)))))


     The corner 0,1 in the Z coordinate is mapped by integrating eq2 from 0 to 1 in the w coordinate




    1+EllipticF((2-2^(1/2))^(1/2), ((2^(1/2)+1)/(2+2^(1/2)))^(1/2))*I/EllipticK(1/(((2+2^(1/2))^(1/2))))



    corner := 1.+.559419351518322*I


     This is the point 1,0 in the original coordinate.

     The height in the Z2 coorinate is found by integrating eq2 from wmid to 1+a.


    ytot := EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))*I/EllipticK(1/(((2+2^(1/2))^(1/2))))





     The magnitude in the Y direction is given by the coefficient of I, the imaginary number


    Ytot := EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))/EllipticK(1/(((2+2^(1/2))^(1/2))))


     The analytial solution in the Z2 corordinate is a line in Y2 to satisfy simple zero flux conditions at X2 = 0 and X2 = 1.


    phianal := 1+b*Y2


     The value of phi is zero at Y2 = Ytot (originally the cathode domain in the Z domain)


    bc := 1+b*EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))/EllipticK(1/(((2+2^(1/2))^(1/2)))) = 0



    b := -EllipticK(1/(((2+2^(1/2))^(1/2))))/EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))


     The analytical solution is given by







     The potential at the corner is given by substituting the imaginary value of corner for Y2 in phinanal)


    phicorner := 1-.559419351518322*EllipticK(1/(((2+2^(1/2))^(1/2))))/EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))





    local flux/current density calculation, written in terms of w is


    curr := -(2^(1/2)+1)^(1/2)*2^(1/2)*EllipticK(2^(1/2)/2)*(w-1)^(1/2)/(EllipticK(((2^(1/2)+1)/(2+2^(1/2)))^(1/2))*(4*w-2*2^(1/2))^(1/2))


    average flux/current density calculation for the anode


    currave := -(1/2)*((2^(1/2)+1)^(1/2)*(2^(1/2)-1)*EllipticK(2^(1/2)/2)*(2^(3/4)+2^(1/4)+arctanh(2^(3/4)/2))*2^(1/2)/EllipticK(2^(3/4)/2))



     The average current density at Y =0, local current density at X = 0,Y=0 and potential at X=1,Y=0 (Corner) can be used to study convergence of FEM and other numerical methods


    -1.656507648777793388522396, -1.161311530233258689567781, .5414751796044734741869534




    Download Conformalmapping.mws


    This FEM code is for solving Laplace's equation with primary current distribution considered in Model 1.
    This code is based on FEM weak-form. Biquadratic Lagrange shape functions (9nodes in an element) are used.



    Lx:=1: #length in X

    Ly:=1: #length in Y

    nx:=10: #number of elements in X (even numbers only)

    ny:=10: #number of elements in Y, to be kept same as nx in this version

    hx:=Lx/nx: #element size x

    hy:=Ly/ny: #element size y

    Procedure to perform numerical integration on shape functions to obtain local matrices (can be replaced with analytical integration for this particular problem)
      -Shape functions are also used as weight functions in applying weak formulation. Numerical integration is done using Simpson's rule.
      -Local cartesian coordinates x,y are converted to natural coordinates zeta and eta. This transformation is not required for this simple geomerty but useful in general. zeta and eta are obtained by scaling x and y with hx/2 and hy/2, respectively, in this code.


    global Kx,Ky,Nx,Ny,zeta,eta,c;
    local A,dAdzeta,dAdeta,y,x,J,terms,i,j,k,l,dx,dy,fx,fy,fxy,fyy,dzeta,deta,J1,J2;

    A:=[(1-zeta)*zeta*(1-eta)*eta/4,-(1-(zeta)^2)*(1-eta)*eta/2,-(1+zeta)*zeta*(1-eta)*eta/4,(1-(eta)^2)*(1+zeta)*zeta/2,(1+zeta)*zeta*(1+eta)*eta/4,(1-(zeta)^2)*(1+eta)*eta/2,-(1-zeta)*zeta*(1+eta)*eta/4,-(1-(eta)^2)*(1-zeta)*(zeta)/2,(1-(zeta)^2)*(1-(eta)^2)]; #bi quadratic langrange shape functions









    terms:=20:#number of terms for numerical integration

    for i from 1 to nops(Nx) do #loop to obtain local matrices      

    for j from 1 to nops(Ny) do

    for k from 0 to terms do #outer loop double integration, integration in zeta

    if k = 0 then fx[k]:= subs(zeta=-1,Nx[i]*Nx[j]*J); fy[k]:= subs(zeta=-1,Ny[i]*Ny[j]*J);  
    elif k = terms then
    fx[k]:= subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J);
    fy[k]:= subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J);  
    elif irem(k,2) = 0 then
    fx[k]:= 2*subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J);
    fy[k]:=     2*subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J);
    else fx[k]:= 4*subs(zeta=-1+(k*dzeta),Nx[i]*Nx[j]*J);
    fy[k]:=     4*subs(zeta=-1+(k*dzeta),Ny[i]*Ny[j]*J);  end if;

    for l from 0 to terms do #inner loop double integration, integration in eta

    if l = 0 then fxy[l]:= subs(eta=-1,fx[k]); fyy[l]:= subs(eta=-1,fy[k]);
    elif l = terms then fxy[l]:= subs(eta=-1+(l*deta),fx[k]); fyy[l]:= subs(eta=-1+(l*deta),fy[k]);
    elif irem(l,2) = 0 then fxy[l]:= 2*subs(eta=-1+(l*deta),fx[k]); fyy[l]:= 2*subs(eta=-1+(l*deta),fy[k]);
    else fxy[l]:=4*subs(eta=-1+(l*deta),fx[k]); fyy[l]:=4*subs(eta=-1+(l*deta),fy[k]); end if;

    end do;
    end do;
    end do;
    end do:

    end proc:

    n:=nx*ny; #total number of elements

    n := 100


    Nx1:=nx*2+1: #number of nodes in x in one row

    N:=Nx1*(2*ny+1); # total number of nodes/equations

    N := 441


    K:=Matrix(N,N,storage=sparse): # global K matrix

    C:=Vector(N,storage=sparse): # global c matrix

    L2G:=Matrix(n,9):  #mapping matrix - each row has node numbers for each element

    localmatrices(hy/2,hy/2,hy/2,hx/2,hx/2,hx/2,0,0): kx:=copy(Kx):ky:=copy(Ky):c0:=copy(c):

    for i from 1 to n do #modifying,adding and assembling matrices to get global matrix
    if i<=nx/2 then  
    a1:=copy(kx); a2:=copy(ky); a3:=0; a1[1..3,1..9]:=IdentityMatrix(3,9); a2[1..3,1..9]:=Matrix(3,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[1..3]:=1.0;

    elif i=nx/2+1 then  a1:=copy(kx); a2:=copy(ky); a3:=0; a1[1,1..9]:=IdentityMatrix(1,9); a2[1,1..9]:=Matrix(1,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[1]:=1.0;

    elif i>nx*(ny-1) then a1:=copy(kx); a2:=copy(ky); a3:=0; a1[5..7,5..7]:=IdentityMatrix(3,3);
    a1[5..7,8..9]:=ZeroMatrix(3,2); a2[5..7,1..9]:=Matrix(3,9,shape=zero); a4:=a1+a2; c:=copy(c0); c[5..7]:=0;

    else a1:=kx; a2:=ky; a3:=0;a4:=a1+a2; c:=c0;  end if;


    if k>nx then k:=1; l:=l+Nx1+3;

    else l:=l+2; end if:



    for i1 from 1 to 9 do
    end do:

    end do:

    phi:=LinearSolve(K,C,method=SparseLU): #linear set of equations solved using Sparse LU solver


    phi_at(1, 0) := .546587799122513





    current_at(0, 0) := -1.15773815354626


    if irem(nx/2,2)=0
    then current_at(0,0.25):=add(dNdy_bottom_left[i]*phi[L2G[nx/4+1,i]],i=1..nops(Ny));
    end if;

    dNdy_bottom_center := [0, -30, 0, 0, 0, -10, 0, 0, 40]

    current_at(0, .25) := -1.26989097821724





    Download FEM_2D.mws

    DataFrames: An example from the 2020 U.S. Presidential election

    (Or why DataFrames are more powerful and readable than spreadsheets.)


    In this example of working with DataFrames, the goal is to use a spreadsheet from a website, which contains polling data, to estimate the probability each of the two candidates from the major parties will win the US Presidential election in November.  I first tried doing the calculations with a spreadsheet, but I discovered DataFrames was far more powerful. Warning: This worksheet uses live data. Hence the outcome at the end of the worksheet is likely to change daily. A more extensive example with even more common DataFrame operations should be available soon.


    How the US Presidential election works - highly simplified version: In the US there are only two parties for which their candidate could win the election:  the Democratic party and Republican party. The Republican party is often referred to as the "Grand Old Party", or GOP. Each state executes its own election. The candidate who receives the most votes wins the states "electoral votes" (EV). The number of the electoral votes for each state is essentially proportional to the population of the state. A candidate who receives a total of 270 or more EVs out of 538, is declared the president of the US for the next term, which starts January 20 of 2021.


    Creating DataFrame from web based data:

    First I download the data from the website. It is a CSV spreadsheet.


    restart; interface(displayprecision = 3); interface(rtablesize = [6, 8]); web_data := Import("")


    Each row contains information about a poll conducted in one of the states.  The first poll starts on row 2, hence the number of polls are:

    Npolls := upperbound(web_data, 1)-1


    Now I want to create a new DataFrame containing only the most useful information. In web_data, many are the columns are not important. However I do want to keep the column label names from those columns I wish to retain.


    web_data_cols := [1, 3, 4, 5, 6]; column_labels := convert(web_data[1, web_data_cols], list)

    ["Day", "State", "EV", "Dem", "GOP"]


    Because  the first poll in web_data is labeled 2, I would like to relabel all the polls starting from 1

    row_labels := [seq(1 .. Npolls)]


    Creating a DataFrame from a Matrix or another DataFrame:  (with row labels and column labels)


    Now I can build the DataFrame that I will be working with:


    poll_data := DataFrame(web_data[2 .. (), web_data_cols], 'columns' = column_labels, 'rows' = row_labels)


    What each column means

    * "Day" - day of the year in 2020 when the poll within the state was halfway completed. The larger the value, the more recent the poll.

    * "State" - the state in the US where the poll was conducted. The candidate that receives the most votes "wins the state".

    * "EV" - the number of electoral votes given to the candidate who receives the most votes within the state.

    * "Dem" - the percentage of people who said they are going to vote for the candidate from the Democratic party.

    * "GOP" - the percentage of people who said they are going to vote for the candidate from the Republican party.


    By using the sort function, using the `>` operator, I can see which polls are the more recent. (If you run the worksheet yourself, the outcome will change as more polls are added to the website spreadsheet.)

    poll_data := sort(poll_data, "Day", `>`)



    Selecting Unique entries - by column values:

    For the my simple analysis, I will use only the most recent poll, one from each state. Hence, using AreUnique, I can pull the first row that matches a state name. This new DataFrame called states.


    states := poll_data[AreUnique(poll_data["State"])]


    (Note, one of the "states" is the District of Columbia, D.C., which is why there are 51 rows.)


    Removing a column: (and relabeling rows)

    This next example isn't necessary, but shows some of the cool features of DataFrames.


    Since there is only 1 entry per state, I'm going to remove the "State" column and relabel all the rows with the state names

    state_names := convert(states["State"], list); states := DataFrame(Remove(states, "State"), 'rows' = state_names)




    Indexing by row labels:

    This allow me to to display information by individual states. What is the data for California, Maine and Alaska?

    states[["California", "Maine", "Alaska"], () .. ()]



    Mathematics with multiple-columns:


    My preference is to work with fractions, rather than percentages. Hence I want all the values in the "Dem" and "GOP" to be divided by 100 (or multiplied by 1/100).  Treating each column like a vector, the multiplication is performed individually on each cell. This is what the tilda, "~", symbol performs.

    states[["Dem", "GOP"]] := `~`[`*`](states[["Dem", "GOP"]], 1/100.); states



    Mathematics: using a function to calculate a column


    For the next action, I want to use the power of the Statistics package to create a "probability of winning the state" function.


    For simplicity, I will assume the outcome of the voting in a state is purely random, but is conditional to popularity of each candidate as measured by the polls. I'll assume the likelihood of an outcome follows a normal (Gaussian) distribution with the peak being at point where the difference of the polling of the two candidates is zero. (Note, other than 2016, where there was an unusually larger percentage of undecided voters on election day, this simple model is reasonable accurate. For example, in 2012, of the states which appeared to be the "closest", the winner over-performed his polling in half of them, and under-performed in the other half with a mean difference of nearly zero.)  From previous elections, the standard deviation of differences between polling values and the actual outcome is at most 0.05, however, it does increase with the fraction of undecided voters.


    To mathematically model this situation, I have chosen to use the "Cumulative Density Function" CDF in the Statistics package. It will calculate the probability that a candidate polling with fraction f1 wins the election if the other candidate is polling with fraction f2.  The variable u is the fraction of undecided voters. It is included in the calculation to increase the spread of the possible outcomes.


    win_prob := Statistics:-CDF(Statistics:-RandomVariable(Normal(0., 0.5e-1+(1/4)*u)), f1-f2)



    Converting this expression into a function using the worst named function in Maple, unapply:

    win_prob_f := unapply(evalf(win_prob), [f1, f2, u])

    proc (f1, f2, u) options operator, arrow; .5000000000+.5000000000*erf(.7071067810*(f1-1.*f2)/(0.5e-1+.2500000000*u)) end proc


    Now I can calculate a DataFrames column of the "win probability", in this case, for the candidate from the Democratic platy. By apply the function, individually, using the columns "Dem" and "GOP", I produce:

    dem_win_prob := `~`[win_prob_f](states["Dem"], states["GOP"], `~`[`-`](1, `~`[`+`](states["Dem"], states["GOP"])))


    Appending a column:


    I can add this column to the end of the states with the label "DemWinProb":


    states := Append(states, dem_win_prob, label = "DemWinProb")



    Mathematics of adding the entries of a column:


    How many electoral votes are available? add them up.

    Total_EV := add(states["EV"])



    While the number of EV a candidate wins is discrete, I can use the "win probability" from each state to estimate the total number of EV each of the candidates might win. This means adding up number of EV in each state times, individually, the probability of winning that state:

    Dem_EV := round(add(`~`[`*`](states["EV"], states["DemWinProb"])))


    Currently, the candidate from the Democratic party is likely to win more then 300 electoral vtes.


    What about for the candidate from the Republican / "GOP" party?

    gop_win_prob := `~`[win_prob_f](states["GOP"], states["Dem"], `~`[`-`](1, `~`[`+`](states["Dem"], states["GOP"]))); GOP_EV := round(add(`~`[`*`](states["EV"], gop_win_prob)))


    Summing the two EV values, we obtain the total number of electoral votes.






    Some people using the Windows platform have had problems installing MapleCloud packages, including the Maplesoft Physics Updates. This problem does not happen in Macintosh or Linux/Unix, also does not happen with all Windows computers but with some of them, and is not a problem of the MapleCloud packages themselves, but a problem of the installer of packages.

    I understand that a solution to this problem will be presented within an upcoming Maple dot release.

    Meantime, there is a solution by installing a helper library; after that, MapleCloud packages install without problems in all Windows machines. So whoever is having trouble installing MapleCloud packages in Windows and prefers not to wait until that dot release, instead wants to install this helper library, please email me at

    Edgardo S. Cheb-Terrab
    Physics, Differential Equations and Mathematical Functions, Maplesoft

    Caution, certain kinds of earlier input can affect the results from using the 2D Input syntax for operator assignment.


    `Maple 2020.1, X86 64 LINUX, Jun 8 2020, Build ID 1474533`


    f := proc (x) options operator, arrow; sqrt(x) end proc

    The following now produces a remember-table assignment,
    instead of assigning a procedure to name f, even though by default
    is set to true.





    f(1.4), 1.4^2*sin(1.4)

    1.183215957, 1.931481471



    With the previous line of code commented-out the following
    line assigns a procedure to name f, as expected.

    If you uncomment the previous line, and re-execute the whole
    worksheet using !!! from the menubar, then the following will
    do a remember-table assignment instead.


    proc (x) options operator, arrow, function_assign; x^2*sin(x) end proc



    f(1.4), 1.4^2*sin(1.4)

    1.931481471, 1.931481471




    This post concerns the simulation of a physical system whose behavior is governed by ODEs.
    It is likely that some people will think that all which follows is nothing but embellishments  or a waste of time.
    And in some sense they will be right.
    I was thinking the same until I received some sharp remarks at the occasion of a few presentations of my works. 
    So experience has proven me that doing a presentation in front of project managers with only 2D curves often leads to smiles, not to speak about those who raise their eyes to heaven in front of the poverty of the slides. 
    Tired of this attitude, I decided to replace these 2D curves with a short film, which of course does not reveal more than what these 2D curves were already revealing, but which is pretty enough for the financing keeps going on.

    For those of you who might regret this situation, just consider this work as a demonstration of the capabilities of Maple in 3D rendering.

    PS: all the display outputs have been removed to avoid loading an unnecessary huge file.
          The two last commands must be uncommented to play the animation.




    Newton raphson method is used for optimization of functions and is based on taylor series expansion. Here is the code for a three level newton raphson method.

    restart; with(Student[MultivariateCalculus]); ff := proc (xx) xx^3-2*xx+2 end proc; ii := 0; XX[ii] := 2; while ii < 25 do GR := Gradient(ff(xx), [xx] = [XX[ii]]); GR1 := evalf(GR[1]); HESS := Student[VectorCalculus]:-Hessian(ff(xx), [xx] = [XX[ii]]); HESS1 := evalf(HESS[1]); YY[ii] := XX[ii]-GR1[1]/HESS1[1]; GR := Gradient(ff(xx), [xx] = [YY[ii]]); GR1 := evalf(GR[1]); HESS := Student[VectorCalculus]:-Hessian(ff(xx), [xx] = [YY[ii]]); HESS1 := evalf(HESS[1]); ZZ[ii] := YY[ii]-GR1[1]/HESS1[1]; GR := Gradient(ff(xx), [xx] = [ZZ[ii]]); GR1 := evalf(GR[1]); HESS := Student[VectorCalculus]:-Hessian(ff(xx), [xx] = [ZZ[ii]]); HESS1 := evalf(HESS[1]); ii := ii+1; XX[ii] := ZZ[ii-1]-GR1[1]/HESS1[1]; printf("%a\n", XX[ii]) end do






    with(Optimization); Minimize(xx^3-2*xx+2, xx = -2 .. 2)

    [HFloat(0.9113378920963653), [xx = HFloat(0.8164965785244629)]]





    What are the things you most like to see improved/add to next version of Maple? 

    This is my list for a starter:

    1.  Improve the debugger. Debugger is very useful but needs more work. At least be able to see code listing in larger view as one steps in for example. See Matlab debugger for inspiration.

    2.  Improve Latex. It still does not do fractions well. Posted about this before.

    3. Eliminate hangs when using timelimit(). On long runs, random hangs happen when timelimit() do not expire as requested. Posted about this before.



    In the present work we are going to demonstrate the importance of the study of vector analysis, with modeling and simulation criteria, using the MapleSim scientific software from MapleSoft. Nowadays, the majority of higher education centers direct their teaching of vector analysis in an abstract way and there are few or no teachers who carry out applications using modeling and simulation. (In spanish)



    We have just released an update to Maple, Maple 2020.1.1. This update includes the following:

    • Correction to a problem that occurred when printing or exporting documents to PDF. If the document included a 3-D plot, nearby text was sometimes missing from the printed/exported document.
    • Correction to an issue that prevented users from installing between-release updates to the Physics package

    This update is available through Tools>Check for Updates in Maple, and is also available from our website on the Maple 2020.1.1 download page. If you are a MapleSim user, you can obtain this update from the corresponding MapleSim menu or MapleSim 2020.1.1  download page.

    In particular, please note that this update fixes the problems reported on MaplePrimes in Maple 2020.1 issue with print to PDF  and installing the Maplesoft Physics updates. As always, we appreciate the feedback.

    The strandbeest is a walking machine developed by Theo Jansen. Its cleverly designed legs consist of single-degree-of-freedom linkage mechanisms, actuated by the turning of a wind-powered crankshaft.

    His working models are generally large - something of the order of the size of a bus. Look for videos on YouTube.  Commercially made small toy models are also available.  This one sells for under $10 and it's fun to assemble and works quite well. Beware that the kit consists of over 100 tiny pieces - so assembling it is not for the impatient type.

    Here is a Maple worksheet that produces an animated strandbeest. Link lengths are taken from Theo Jansen's video (go to his site above and click on Explains) where he explains that he calculated the optimal link lengths by applying a genetic algorithm.

    Here is a Maple animation of a single leg.  The yellow disk represents the crankshaft.

    And here are two legs working in tandem:

    Here is the complete beest, running on six legs. The crankshaft turns at a constant angular velocity.

    The toy model noted above runs on twelve legs for greater stability.

    Download the worksheet:


    This may be of interest to anyone curious about why the effective area of an isotropic antenna is λ^2/4π.


    Friis Transmission Equation





    The Hertzian Dipole antenna


    The Hertzian Dipole is a conceptual antenna that carries a constant current along its length.



    By laying a number of these small current elements end to end, it is possible to model a physical antenna (such as a half-wave dipole for example).  But since we are only interested in obtaining an expression for the effective area of an Isotropic Antenna (in order to derive The Friis Transmission Equation) the Hertzian Dipole will be sufficient for our needs.``



    Maxwell's Equations


    Since the purpose of a radio antenna is to either launch or to receive radio waves, we know that both the antenna, and the space surrounding the antenna, must satisfy Maxwell's Equations. We define Maxwell's Equations in terms of vector functions using spherical coordinates:


    Maxwell–Faraday equation:

    Maxwell_1 := Curl(E_(r, theta, `&varphi;`, t)) = -mu*(diff(H_(r, theta, `&varphi;`, t), t))

    Physics:-Vectors:-Curl(E_(r, theta, varphi, t)) = -mu*(diff(H_(r, theta, varphi, t), t))


    Ampère's circuital law (with Maxwell's addition):

    Maxwell_2 := Curl(H_(r, theta, `&varphi;`, t)) = J_(r, theta, `&varphi;`, t)+epsilon*(diff(E_(r, theta, `&varphi;`, t), t))

    Physics:-Vectors:-Curl(H_(r, theta, varphi, t)) = J_(r, theta, varphi, t)+varepsilon*(diff(E_(r, theta, varphi, t), t))


    Gauss' Law:

    Maxwell_3 := Divergence(E_(r, theta, `&varphi;`, t)) = rho/epsilon

    Physics:-Vectors:-Divergence(E_(r, theta, varphi, t)) = rho/varepsilon


    Gauss' Law for Magnetism:

    Maxwell_4 := Divergence(H_(r, theta, `&varphi;`, t)) = 0

    Physics:-Vectors:-Divergence(H_(r, theta, varphi, t)) = 0



            E is the electric field strength [Volts/m]

            H is the magnetic field strength [Amperes/m]

            J is the current density (current per unit area) [Amperes/m2]

            ρ is the charge density (charge per unit volume) [Coulombs/m3]

            ε is Electric Permittivity

            μ is Magnetic Permeability


    Helmholtz decomposition


    The Helmholtz Decomposition Theorem states that providing a vector field, (F) satisfies appropriate smoothness and decay conditions, it can be decomposed as the sum of components derived from a scalar field, (Φ) called the "scalar potential", and a vector field (A) called the "vector potential".


    F = -VΦ + V×A


    And that the scalar (Φ) and vector (A) potentials can be calculated from the field (F) as follows (image from



            r is the vector from the origin to the observation point (P) at which we wish to know the scalar or vector potential.

            r' is the vector from the origin to the source of the scalar or vector potential (i.e. a point on the Hertzian Dipole antenna).

            V'·F(r')  is the Divergence of the vector field (F) at source position r'.

            V'×F(r')  is the Curl of the vector field (F) at source position r'.



    Calculating the Scalar Potential for the magnetic Field, H


    We know that the Divergence of the magnetic field (H) is zero:


    Physics:-Vectors:-Divergence(H_(r, theta, varphi, t)) = 0


    And so the magnetic field (H) must have a scalar vector potential of zero:

    `&Phi;__H` := 0




    Calculating the Vector Potential for the magnetic Field, H


    We know that the Curl of the magnetic field (H) is equal to the sum of current density (J) and the rate of change of the electric filled (E):


    Physics:-Vectors:-Curl(H_(r, theta, varphi, t)) = J_(r, theta, varphi, t)+varepsilon*(diff(E_(r, theta, varphi, t), t))


    Since the Hertzian Dipole is a conductor, we need only concern ourselves with the current density (J) when calculating the vector potential (A). Integrating current density (J) over the volume of the antenna, is equivalent to integrating current along the length of the antenna (L).


    We know that Maxwell's Equations can be solved for single frequency (monochromatic) fields, so we will excite our antenna with a single frequency current:


    proc (t) options operator, arrow, function_assign; Physics:-`*`(I__0, exp(Physics:-`*`(I, omega, t))) end proc


    We can simplify the integral for the vector potential (A) by recognising that:



    Our observation point (P) will be a long way from the antenna and so (r) will be very large.


    The length of the antenna (L) will be very small and so (r') will be very small.


    Since |r|>>|r'|, we can substitute |r-r'| with r.


    Because we have decided that the observation point at r will be a long way from the antenna, we must allow for the fact that the observed antenna current will be delayed.  The delay will be equal to the distance from the antenna to the observation point |r-r'| (which we have simplified to r), divided by the speed of light (c).  The time delay will therefore be approximately equal to r/c and so the observed antenna current becomes:


    proc (t) options operator, arrow, function_assign; Physics:-`*`(I__0, exp(Physics:-`*`(I, omega, Physics:-Vectors:-`+`(t, -Physics:-`*`(r, Physics:-`^`(c, -1)))))) end proc



    Since the length, L of the antenna will be very small, we can assume that the current is in phase at all points along its length.  Working in the Cartesian coordinate system, the final integral for the vector potential for the magnetic field is therefore:

    A__H_ := (int(I__0*exp(I*omega*(t-r/c))*_k/r, z = -(1/2)*L .. (1/2)*L))/(4*Pi)




    We will now convert to the spherical coordinate system, which is more convenient when working with radio antenna radiation patterns:

    The radial component of the observed current (and therefore vector potential), will be at a maximum when the observer is on the z-axis (that is when θ=0 or θ=π) and will be zero when the observer is in the x-y-plane:

    A__Hr := (A__H_._k)*cos(theta)



    The angular component of the observed current (and therefore vector potential), in the θ direction will be zero when the observer is on the z-axis (that is when θ=0 or θ=π) and will be at a maximum when the observer is in the x-y-plane:

    `A__H&theta;` := -(A__H_._k)*sin(theta)



    Since the observed current (and therefore vector potential) flows along the z-axis, there will be no variation in the ϕ direction.  That is to say, that varying ϕ will have no impact on the observed vector potential.

    `A__H&varphi;` := 0



    And so the vector potential for the magnetic field (H) expressed using spherical coordinate system is:

    A__H_ := A__Hr*_r+_theta*`A__H&theta;`+`A__H&varphi;`*`_&varphi;`




    Calculating the Magnetic Field components


    The Helmholtz Decomposition Theorem states that providing a vector field (F) satisfies appropriate smoothness and decay conditions, it can be decomposed as the sum of components derived from a scalar field (Φ) called "scalar potential", and a vector field (A) called the vector potential.


    F = -VΦ + V×A


    And so the magnetic field, H will be:


    H_ = -(Gradient(`&Phi;__H`))+Curl(A__H_)

    H_ = ((1/4)*I)*I__0*L*exp(I*omega*t-I*omega*r/c)*sin(theta)*omega*_phi/(c*Pi*r)+(1/4)*I__0*L*exp(I*omega*t-I*omega*r/c)*sin(theta)*_phi/(Pi*r^2)


    We see that the magnetic field comprises two components, one is inversely proportional to the distance from the antenna (r) and the other falls off with r2.  Since we are interested in the far-field radiation pattern for the antenna, we can ignore the r2 component and so the expression for the magnetic field reduces to:

    H_ := I*omega*I__0*L*exp(I*omega*(t-r/c))*sin(theta)*_phi/(4*Pi*c*r)



    We can further simplify by substituting ω/c for 2π/λ:``

    H_ := (I*2)*Pi*L*I__0*exp(I*omega*t-(I*2)*Pi*r/lambda)*sin(theta)*_phi/(4*Pi*lambda*r)