Message Boards Message Boards

GROUPS:

[WSC19] Visualizing Fourier Series For Curves

Posted 12 days ago
360 Views
|
7 Replies
|
6 Total Likes
|

Visualizing the Fourier Series

Link To GitHub

Introduction

The goal of the project is to generate Visualizations for how the Fourier series creates approximation of curves. The end result is a function that is able to generate two different graphics to show using Circles to represent the trigonometric waves used in the Fourier Series.The Fourier Series in Calculus is the summation of trigonometric waves in order to represent an approximation of a curve. Since the Fourier Series uses trigonometric functions which are created by the rotation of a circle, it is possible to link together multiple rotating circles and recreate the curve.

Development

Understanding the General Idea

First,I had to learn Calculus and trigonometry in order to understand how the Fourier Series works. At first I wanted a proof of concept that I was able to visualize how the addition of sinusoidal could result in an approximation of the curve. So I spent some time to hard code rotating radii of circles and orbiting circles. I created orbiting circles by setting the centers of successive circles as a sinusoidal functions so that they would be following a track built by the previous circle's circumference.

 Animate[
           Graphics[
             {
                Line[{{0, 0}, {5*Cos[time], 5*Sin[time]}}],
              Circle[{0, 0}, 5],
             Circle[{5*Cos[time], 5*Sin[time]}, 3],
             Line[{{5*Cos[time], 5*Sin[time]}, {3*Cos[2 time] + 5*Cos[time], 3*Sin[2 time] +     5*Sin[time]}}],
             Circle[{3*Cos[2 time] + 5*Cos[time], 3*Sin[2 time] + 5*Sin[time]},2],
             Line[{{3*Cos[2 time] + 5*Cos[time], 3*Sin[2 time] + 5*Sin[time]}, {5*Cos[time] + 3*Cos[2 time] + 2 Cos[3 time], 5*Sin[time] + 3*Sin[2 time] + 2 Sin[3 time]}}],
          }
       ],
    {time, 0, 2*Pi}, AnimationRunning -> False]

The lines are the rotating arms and connect one center of a circle to the next circle's center. The code above shows the nesting of coordinates to create the moving parts using the "Animate" function. If the x-coordinates use "Cosine" and the y-coordinates use "Sine" functions, then the rotating parts will move counter-clockwise.If you switch the functions, then the arms rotate clockwise. (This information proves to be useful later on in the developing process)

Creating the Functions

I went through many iterations of my code an restarted on a new approach three times. The hardest part of this project was getting the information form the functions to be in a certain format so that it could be displayed in a cohesive manner. This issue rose form creating a generalized function that would work for most if not all scenarios.

Approach One

At first I tried to create multiple helper functions to manipulate the information and create it modular. The code below takes in a radius list, frequency list, and the number of circles needed, and then creates the nested list that is used for the moving objects in the graphics. The function is then called multiple times (I at that time had not optimized by code to run quickly) and used them to create list of Graphics Primitives. This one of the more developed versions of this method where the user physically inputs the amplitude and radii of the waves used to generate the desired function. In earlier versions I had an issue with getting animate to work; when I was not using slots in order to input lists and other information. I later found out that the function "Animate" had a "HoldAll" attribute which meant that it would not evaluate anything that is inputted directly in the Animate. Therefore, I had to adapt my code to evaluate outside the Animate and then be inserted into the Animate function via Slot Machines.

ClearAll[fourierSeriesGraphicsV3]
fourierSeriesCoordinates[numberCircles_,radiusList_List,frequencyList_List]:=
    With[
       {protoList=MapThread[
          {#1*Cos[#2*time],#1*Sin[#2*time]}&,{radiusList,frequencyList}]
          },
    Plus@@@Table[protoList[[;;n]],
    {n,1,Length[protoList]}]//Prepend[{0,0}]
]

fourierSeriesGraphicsV3[numberCircles_,radiusList_List,frequencyList_List]:=
    With[
    {
       coordinateList=fourierSeriesCoordinates[numberCircles,radiusList,frequencyList],
       protoList=fourierSeriesCoordinates[numberCircles,radiusList,frequencyList],
       lastCoordinate=Drop[Apply[List,
         MapThread[
          Circle,{
              Drop[
                 fourierSeriesCoordinates[10,Reverse[Range[10]],Range[10]],-1],
                 Reverse[Range[10]]}
              ]//Last],-1][[1,2]],
       circleList=Evaluate[
          MapThread[
              Circle,{
                 Drop[fourierSeriesCoordinates[numberCircles,radiusList,frequencyList],-1],
                 radiusList}
              ]
          ]
    },

Animate[
    Graphics[{#1,#2,Line[Last[coordinateList],{Total[radiusList]+10,lastCoordinate}]}],
{time,0,2*Pi},
AnimationRunning->False]&@Evaluate[
    (Line[coordinateList]),(circleList)
]
]

Approach Two

Although the previous method works, it is hard to know exactly if you are creating the desired function,Which means that you need to know what the radii/amplitudes and frequency where before you used this function. Hence my second approach. This approach was more of a function that would be built as an addition to the previous method. The new addition included a function built around the inbuilt function "FourierSeries" that took in function, variable, and number of terms as inputs and returned the equation of the approximation of the function as complex numbers.I had to use the inbuilt function "ComplexExpand" to get the equation in terms of Sine and Cosine. If the input function was even, then the Sine terms would cancel, and if the function is odd, then the Cosine terms canceled out.

fourierInputV2[inputFunction_,variableUsing_,numberOfTerms_Integer]:=
With[
    {protoSeries=FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand,
    manipulateSeries=Drop[Apply[List,FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand],1],
    protoCoordinateSeries={
       {
Select[Drop[Apply[List,FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand],1],!FreeQ[#,Cos]&],
          Select[Drop[Apply[List,FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand],1],!FreeQ[#,Cos]&]/.Cos->Sin
         },
         {
          Select[Drop[Apply[List,FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand],1],!FreeQ[#,Sin]&],
          Select[Drop[Apply[List,FourierSeries[inputFunction,variableUsing,numberOfTerms]//ComplexExpand],1],!FreeQ[#,Sin]&]/.Sin->Cos
         }
       } 
    },

       Join[
         MapThread[List,{
          Select[manipulateSeries,!FreeQ[#,Cos]&],
          Select[manipulateSeries,!FreeQ[#,Cos]&]/.Cos->Sin
         }],

         MapThread[List,
         {
          Select[manipulateSeries,!FreeQ[#,Sin]&],
          Select[manipulateSeries,!FreeQ[#,Sin]&]/.Sin->Cos
         }
       ]
    ]

]

Final Approach And Why it worked

Creating a New Visual

For my final approach, I used a pair of functions called "FourierCosCoefficient" and "FourierSinCoefficient" in order separately get the radii of the circles. Then the many variables are used to artificially stitch together the coefficients and the frequencies; the code is using a descriete Fourier Transform, thus allowing me to artificially generate the frequencies used. Then the artificially generated Fourier series is used to generate coordinates. In the beginning I had the x coordinates to be in terms of Cosine and y coordinates in terms of Sine which caused the rotating parts to move in the counter clockwise direction. This function then creates individual moving plots which are then combined with the circles through the "Show" function to give the illusion that the circles are creating the plots. This function is named "unblendedCosCircleSmoothie" because this function returns the separate waves that were added to create the approximation, hence the "ingredients" in the "smoothie." I created this function as a demonstration to show how separate waves and circles can create function when combined together; the different steps of the process would be a good visual aid in understanding the concept of what the Fourier Series does. Below there is an example of the function that is used for the even functions. For the odd functions I replaced a few operations and switched wherever it Cos to Sine and vise-versa.

unblend circle

ClearAll[unblendedCosCircleSmoothie]

unblendedCosCircleSmoothie[function_,variable_Symbol,numCir_Integer]:=
Block[
    {
       fourierCoeff=Table[FourierCosCoefficient[function,variable,p],{p,1,numCir}],
       color=Drop[Hue[#]&/@Range[0,1,(1/numCir)],-1],
       circleList,basicCoord,manipulatedCoord,maxRadius,lineList,plotList,styledLines,prePlot
    },
    circleList=Circle[{-maxRadius-5,0},#1]&/@Abs@fourierCoeff;

    basicCoord=MapThread[Times,{fourierCoeff,Cos[Range[numCir]*variable]}];
    manipulatedCoord=Transpose[{basicCoord-maxRadius-5,basicCoord/.Cos->Sin}];
    maxRadius=Max[Abs@fourierCoeff];
    lineList=Line[{{-maxRadius-5,0},#1,{0,#1[[2]]}}]&/@manipulatedCoord/.variable->$time;
    styledLines=MapThread[Style,{lineList,color}];

    prePlot=MapThread[Times,{fourierCoeff,Cos[(Range[numCir]*(variable+$time))-(Pi/2)]}];
    plotList=Plot[#1,{variable,0,10},PlotStyle->#2]&@@@(Transpose[{prePlot,color}]);

    Animate[
       Show[
         Graphics[{Sequence@@#1,Sequence@@#2},Axes->True],
         Plot[#3,{variable,0,10},PlotStyle->#4,PlotRange->All],PlotRange->All
         ],
    {$time,0,-2Pi},AnimationRunning->False]&@@Evaluate[{circleList,styledLines,prePlot,color}]

Creating the Nested Visual

The following function is structured similar to the previous algorithm. However, the difference is that now the centers of the circles are not static and are changed so that they create a chain. There is an algorithm that takes the set of coordinates and nests them together so that the circles follow an orbit. Another difference in this function is that instead of creating different rotating arms form the origin in order to color them, I was able to insert the entire nested function list into the line which created a line that appeared to be segmented.

circle chain

ClearAll[circleCosSmoothie2]
circleCosSmoothie2[function_,variable_Symbol,numCir_Integer]:=
    Block[{
       fourierConstant=FourierCosCoefficient[function,variable,0]//ComplexExpand,
       fourierCoeff=Table[FourierCosCoefficient[function,variable,p],{p,1,numCir}],
       basicCoord,manipulatedCoord,lastYCoord,totalRadius,combinedCir,cirCenter,
       cirArguments,finalPlot,lineSeries,centerOfFirstCir,listOfCoord,lastLinetoYaxis,
       lastXCoord,plotCoord,plotAllign


},

totalRadius=Total[Abs@fourierCoeff];
basicCoord=MapThread[Times,{fourierCoeff,Cos[Range[numCir]*$time]}];
manipulatedCoord=Join[
    Take[
       Transpose[
       {(basicCoord-totalRadius-5)/.Cos->Sin,basicCoord}],1],
    Drop[Transpose[{basicCoord/.Cos->Sin,basicCoord}],1]];
manipulatedCoord=(Plus@@@Table[
         manipulatedCoord[[;;n]],
          {n,1,Length[manipulatedCoord]}]//Prepend[{-totalRadius-5,0}]);

lastYCoord={0,Take[Take[manipulatedCoord,-1]//Flatten,-1]};
plotCoord=Total[MapThread[Times,{fourierCoeff,Cos[((Range[numCir])*(variable+$time))]}]];
cirCenter=Drop[manipulatedCoord,-1];
cirArguments=Transpose[{cirCenter,Abs@fourierCoeff}];

lastLinetoYaxis=Prepend[Take[Take[manipulatedCoord,-1]//Flatten,-1],0];

combinedCir={Circle[#1,#2]}&@@@(#1&/@cirArguments);
lineSeries= Line[#1]&@(Append[manipulatedCoord,lastLinetoYaxis]);

Animate[
       Show[
         Graphics[{Sequence@@#1,#2},Axes->True],
         Plot[#3,{variable,0,3*#4},PlotRange->Full],
         PlotRange->{{-3*#4,3#4},{-#4,#4}}
                   ],
    {$time,0,-2Pi},AnimationRunning->False]&@@Evaluate[{combinedCir,lineSeries,plotCoord,totalRadius}]


]

Making a Funciton that works for all curves

a non symetirc function

Unlike the other functions, this function uses the FourierSeries function and then extracts data from the equation itself (There are more detailed comment on what each step does in the code ). I turn the equation into a list and then separate the sine and cosine terms into different lists. Then a little bit more modification is needed to extract the coefficients which was done by replacing all the terms in a specific pattern to 1 so that it would multiply out and only leave me with the coefficients. Then I have functions similar to the previous ones which create a list of circle and line Graphics Primitives

ClearAll[circleSmoothietheRest]

circleSmoothietheRest[function_,variable_,numCir_]:=
Block[
{proto=FourierSeries[function,variable,numCir]//ComplexExpand,protoList2,cosList,sinList,cosCoeff,sinCoeff,
basicCosCoord,basicSinCoord,totalRadius,manipulatedCosCoord,manipulatedSinCoord,manipulatedCoord,
lastYCoord,plotCoord,cirCenter,cirArguments,lastLinetoYaxis,combinedCir,lineSeries,range,fourierCoeff},

protoList2=Drop[Apply[List,proto],1];
cosList=Take[protoList2,numCir];
sinList=(Apply[Plus,Partition[Drop[protoList2,numCir],2],{1}]);
cosCoeff=ReplaceAll[#1,Cos[x_]->1]&/@cosList;
sinCoeff=(ReplaceAll[#1,Sin[x_]->1]&/@sinList);
totalRadius=Echo@(Abs@(Total[Abs@cosCoeff]+Total[Abs@sinCoeff]));
fourierCoeff=Abs@Join[cosCoeff,sinCoeff];
    manipulatedCosCoord=Join[
       Take[
         Transpose[
       {(cosList-10)/.Cos->Sin,cosList}],1],
    Drop[Transpose[{cosList/.Cos->Sin,cosList}],1]];
manipulatedSinCoord=Join[
    Take[
       Transpose[
       {(sinList-10)/.Sin->Cos,sinList}],1],
    Drop[Transpose[{sinList/.Sin->Cos,sinList}],1]];
manipulatedCoord=(Join[manipulatedCosCoord,manipulatedSinCoord]/.x->$time);

manipulatedCoord=(Plus@@@Table[
                   manipulatedCoord[[;;n]],
                   {n,1,Length[manipulatedCoord]}]//Prepend[{-10,0}]);

lastYCoord={0,Take[Take[manipulatedCoord,-1]//Flatten,-1]};
plotCoord=(Total[cosList]+Total[sinList])/.variable->(variable+$time);
cirCenter=Drop[manipulatedCoord,-1];
(*I need to find a way to extract the radii of the Circles*)
cirArguments=Transpose[{cirCenter,fourierCoeff}];

lastLinetoYaxis=Prepend[Take[Take[manipulatedCoord,-1]//Flatten,-1],0];

combinedCir={Circle[#1,#2]}&@@@(#1&/@cirArguments);
lineSeries= (Line[#1]&@Evaluate[Append[manipulatedCoord,lastLinetoYaxis]]);

range=(2Pi)*totalRadius;

Animate[
       Show[
         Graphics[{Sequence@@#1,#2},Axes->True],
         Plot[#3,{variable,0,30},PlotRange->Full]
                   ],
    {$time,0,-2Pi},AnimationRunning->False]&@@Evaluate[{combinedCir,lineSeries,plotCoord}]

]

Putting it All Together

This code was written to separate the odd and even functions and use the corresponding function to them; for even functions I used the cosine functions and for the odd functions I used sine functions. I also added a plot of the original function to show how the approximation compares.

ClearAll[fourierCircleCookBook]
fourierCircleCookBook[function_,variable_Symbol,numCir_Integer]:=
Which[
    PossibleZeroQ[function-(function/.variable->(-variable))],
       Row[
         {unblendedCosCircleSmoothie[function,variable,numCir],
         circleCosSmoothie2[function,variable,numCir],
         Plot[function,{variable,-30,30},PlotStyle->Black,Frame->True,ImageSize->Medium,Axes->False]}
       ],
    PossibleZeroQ[function+(function/.variable->(-variable))],
       Row[
         {unblendedSinCircleSmoothie[function,variable,numCir],
         circleSinSmoothie2[function,variable,numCir],
         Plot[function,{variable,-30,30},PlotStyle->Black,Frame->True,ImageSize->Medium,Axes->False]}
       ],
True,
circleSmoothietheRest[function,variable,numCir]
]

Further Improvements

In the future, when I have more time, I hope to get the function to create a visualization for any function even if it is neither odd nor even. By doing so I will be able to draw any curve that is provided using rotating arm segments. The end goal is to create a function, such that if given any picture of a curve, it will recreate the curve using a series of circle linkages. Finally, I will cloud deploy the functions so that there will be a mini site that is easy for users to access and utilize.

7 Replies

Wow! Love the animations

Wow! Love the animations

Cool post :>

Posted 12 days ago

really cool!

Posted 12 days ago

Awesome project!

Posted 11 days ago

Really good project!

Thank You, I will be adding a new part of the project that I worked on soon. Will update, so check back soon.

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract