Click here to Skip to main content
Click here to Skip to main content

A Pendulum and its Corresponding Oscillations Shown in WPF

By , 4 Nov 2010
Rate this:
Please Sign up or sign in to vote.

Preface

This focus of this article is to illustrate how to build a WPF application that simulates a Physics phenomena, namely a pendulum. To do this, we will have to build a C# library to reference as a DLL in our project. The code presented is meant to illustrate a mathematical technique to enable this pendulum simulation. So, if you can bear with a brief explanation about Physics, we will proceed and build the application.

The student of Physics will have inevitably tackled differential equations, solving both partial and ordinary equations. Many Physics phenomena can be described in terms of ordinary differential equations (ODEs). For instance, if a projectile is flying through the air, it will be subject to the force of aerodynamic drag, which is a function of the object's velocity. The sounds of certain musical instruments are a function of the tension of the string. Also consider a spring-mass system. In this system, there are two forces acting on the mass: elastic recovery force, which is proportional to the displacement of the mass, and the damping force, which is proportional to its velocity. The equations of motion describing this system are also a set of ordinary differential equations, which can't be directly solved either. There are, however, a number of techniques that can be used to solve ODEs when an analytically closed form of solution is impossible. One technique is called the Runge-Kutta method. Now, without going into a long, drawn-out explanation of how this technique works, we can look at some C# code that first defines a delegate function that takes a double array x and a double time variable t as its input parameters.

using System;
using System.Windows;
namespace Swing
{
public class ODESolver
{
  public delegate double Function(
  double[] x, double t);
  public static double[] RungeKutta4(
  Function[] f, double[] x0, double t0, double dt)
  {
   int n = x0.Length;
   double[] k1 = new double[n];
   double[] k2 = new double[n];
   double[] k3 = new double[n];
   double[] k4 = new double[n];
   double t = t0;
   double[] x1 = new double[n];
   double[] x = x0;
   for (int i = 0; i < n; i++)
   k1[i] = dt * f[i](x, t);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k1[i] / 2;
   for (int i = 0; i < n; i++)
   k2[i] = dt * f[i](x1, t + dt / 2);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k2[i] / 2;
   for (int i = 0; i < n; i++)
   k3[i] = dt * f[i](x1, t + dt / 2);
   for (int i = 0; i < n; i++)
   x1[i] = x[i] + k3[i];

   for (int i = 0; i < n; i++)
   k4[i] = dt * f[i](x1, t + dt);
   for (int i = 0; i < n; i++)
   x[i] +=
   (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]) / 6;
    return x;
   }
  }
}

This file can be compiled on the command line using the /t:library switch or as a class file in a C# Class Library project using Visual Studio. We are going to reference this DLL when we build our WPF app. We want to simulate the motion of a pendulum. When a pendulum is displaced from its resting equilibrium position, it is subject to a restoring force due to gravity that will accelerate it back toward the equilibrium position. When released, the restoring force combined with the pendulum's mass causes it to oscillate about the equilibrium position, swinging back and forth. The time for one complete cycle, a left swing and a right swing, is called the period. A pendulum swings with a specific period which depends (mainly) on its length. This means that we are going to use it to simulate this model.

Examine the image below. A string with a mass hanging on one end is displayed in the bottom-left pane. The bottom-right pane shows how the swing angle changes with time. In addition, there are several TextBox fields that allow you to input the mass, string length, damping coefficient, initial angle, and initial angle velocity. A Start button begins the pendulum simulator, a Stop button stops the simulation, and a Reset button stops the simulation and returns the pendulum to its initial position. So let's fire up either Expression Blend or Visual Studio, start a new project called Swing, add a reference to ODESolver.dll, add a new WPF window, or rename the MainWindow to Pendulum, and take a look at the XAML:

Capture.JPG

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="Swing.Pendulum"
    x:Name="Window"
    Title="Swing
    Out"
    Width="640"
    Height="480" Background="MediumPurple">
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Width" Value="50"/>
<Setter Property="Height" Value="20"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="Margin" Value="2"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5,2,2,5"/>
<Setter Property="Width" Value="70"/>
<Setter Property="TextAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="2"/>
<Setter Property="Width" Value="75"/>
<Setter Property="Height" Value="25"/>
</Style>
</Window.Resources>
<Window.Foreground>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Window.Foreground>
<StackPanel Margin="10">
<StackPanel Orientation="Horizontal">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="14.667" FontFamily="Times New Roman"
   FontWeight="Bold">Mass:</TextBlock>
<TextBox Name="tbMass" Text="1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" 
  FontWeight="Bold" FontSize="14.667">Length:</TextBlock>
<TextBox Name="tbLength" Text="1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
  FontSize="14.667">Damping:</TextBlock>
<TextBox Name="tbDamping" Text="0.1"/>
</StackPanel>
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
   FontSize="14.667">Theta0:</TextBlock>
<TextBox Name="tbTheta0" Text="45"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontFamily="Times New Roman" FontWeight="Bold"
  FontSize="14.667">Alpha0:</TextBlock>
<TextBox Name="tbAlpha0" Text="0"/>
</StackPanel>
</StackPanel>
<StackPanel Margin="70,0,0,10">
<Button Click="btnStart_Click" Content="Start"/>
<Button Click="btnStop_Click" Content="Stop"/>
<Button Click="btnReset_Click" Content="Reset"/>
</StackPanel>
<StackPanel Margin="70,40,0,0">
<TextBlock Name="tbDisplay" FontSize="16"
  Foreground="Black" FontFamily="Tahoma"
  FontWeight="Bold">Stopped
</TextBlock>
</StackPanel>
</StackPanel>
<Separator Margin="0,10,0,10"></Separator>
<Viewbox Stretch="Fill">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas Name="canvasLeft" Grid.Column="0"
   Width="280" Height="170">
<Rectangle Fill="DarkGoldenrod" Width="50"
  Height="10" Canvas.Left="115"
  Canvas.Top="10"/>
<Line Name="line1" X1 ="140" Y1="20"
  X2="140" Y2="150" Stroke="Red"/>
<Path Fill="Blue">
<Path.Data>
<EllipseGeometry x:Name="ball" RadiusX="10"
  RadiusY="10" Center="140,150"/>
</Path.Data>
</Path>
</Canvas>
<Canvas Name="canvasRight" Grid.Column="1"
  ClipToBounds="True" Width="280"
  Height="170">
<Line X1="10" Y1="0" X2="10" Y2="170"
  Stroke="Gray" StrokeThickness="1"/>
<Line X1="10" Y1="85"
   X2="280" Y2="85"
  Stroke="Gray" StrokeThickness="1"/>
<TextBlock TextAlignment="Left"
 Canvas.Left="10" FontFamily="Times New Roman" 
 FontWeight="Bold" FontSize="14.667">theta
</TextBlock>
<TextBlock TextAlignment="Left" Canvas.Left="248.51"
  Canvas.Top="89.5" FontFamily="Times New Roman" 
  FontWeight="Bold" FontSize="14.667"
  Margin="0">time
</TextBlock>
</Canvas>
</Grid>
</Viewbox>
</StackPanel>
</Window>

When the Start button is pressed, the input values for the mass, string length, damping coefficient, and initial position and velocity are obtained from the values inside their corresponding TextBox fields. At the same time, the event handler StartAnimation is attached to the static CompositionTarget.Rendering event. Here is the code-behind file:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Swing
{
public partial class Pendulum : Window
{
    private double PendulumMass = 1;
    private double PendulumLength = 1;
    private double DampingCoefficient = 0.5;
    private double Theta0 = 45;
    private double Alpha0 = 0;
    double[] xx = new double[2];
    double time = 0;
    double dt = 0.03;
    Polyline pl = new Polyline();
    double xMin = 0;
    Double yMin = -100;
    double xMax = 50;
    double yMax = 100;
    public Pendulum()
    {
        InitializeComponent();
    }
    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
        PendulumMass = Double.Parse(tbMass.Text);
        PendulumLength = Double.Parse(tbLength.Text);
        DampingCoefficient = Double.Parse(tbDamping.Text);
        Theta0 = Double.Parse(tbTheta0.Text);
        Theta0 = Math.PI * Theta0 / 180;
        Alpha0 = Double.Parse(tbAlpha0.Text);
        Alpha0 = Math.PI * Alpha0 / 180;
        tbDisplay.Text = "Starting...";
        if (canvasRight.Children.Count > 4)
        canvasRight.Children.Remove(pl);

        pl = new Polyline();
        pl.Stroke = Brushes.Red;
        canvasRight.Children.Add(pl);
        time = 0;
        xx = new double[2] { Theta0, Alpha0 };
        CompositionTarget.Rendering += StartAnimation;
    }
    private void StartAnimation(object sender, EventArgs e)
    {
        // Invoke ODE solver:
        ODESolver.Function[] f =
        new ODESolver.Function[2] { f1, f2 };
        double[] result = ODESolver.RungeKutta4(
        f, xx, time, dt);
        // Display moving pendulum on screen:
        Point pt = new Point(
        140 + 130 * Math.Sin(result[0]),
        20 + 130 * Math.Cos(result[0]));
        ball.Center = pt;
        line1.X2 = pt.X;
        line1.Y2 = pt.Y;
        // Display theta - time curve on canvasRight:
        if (time < xMax)
        pl.Points.Add(new Point(XNormalize(time) + 10,
        YNormalize(180 * result[0] / Math.PI)));
        // Reset the initial values for next calculation:
        xx = result;
        time += dt;
        if (time > 0 && Math.Abs(result[0]) < 0.01 &&
            Math.Abs(result[1]) < 0.001)
        {
            tbDisplay.Text = "Stopped";
            CompositionTarget.Rendering -= StartAnimation;
        }
    }
    private void btnReset_Click( object sender, RoutedEventArgs e)
    {
        PendulumInitialize();
        tbDisplay.Text = "Stopped";
        if (canvasRight.Children.Count > 4)
            canvasRight.Children.Remove(pl);
        CompositionTarget.Rendering -= StartAnimation;
    }

    private void PendulumInitialize()
    {
        tbMass.Text = "1";
        tbLength.Text = "1";
        tbDamping.Text = "0.1";
        tbTheta0.Text = "45";
        tbAlpha0.Text = "0";
        line1.X2 = 140;
        line1.Y2 = 150;
        ball.Center = new Point(140, 150);
    }
    private void btnStop_Click( object sender, RoutedEventArgs e)
    {
        line1.X2 = 140;
        line1.Y2 = 150;
        ball.Center = new Point(140, 150);
        tbDisplay.Text = "Stopped";
        CompositionTarget.Rendering -= StartAnimation;
    }
    private double f1(double[]xx, double t)
    {
        return xx[1];
    }
    private double f2(double[] xx, double t)
    {
        double m = PendulumMass;
        double L = PendulumLength;
        double g = 9.81;
        double b = DampingCoefficient;
        return -g * Math.Sin(xx[0]) / L - b * xx[1] / m;
    }
    private double XNormalize(double x)
    {
        double result = (x - xMin) *
        canvasRight.Width / (xMax - xMin);
        return result;
    }
    private double YNormalize(double y)
    {
        double result = canvasRight.Height - (y - yMin) *
        canvasRight.Height / (yMax - yMin);
        return result;
    }
}
}

Once the new values of angle and velocity are obtained, you update the screen that shows the moving pendulum and the swing angle as a function of time on the screen. Next, you set the current solution as the initial values for the next round simulation. When the swing angle and angle velocity are so small that the pendulum almost doesn't swing, you can stop the animation by detaching the StartAnimation event handler using the statement:

CompositionTarget.Rendering -= StartAnimation;

You can play around with the Pendulum Simulator by changing the values of the mass, damping coefficient, initial string angle, and initial angle velocity, and watch their effects on the motion of the pendulum.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

logicchild
Other Pref. Trust
United States United States
I started electronics training at age 33. I began studying microprocessor technology in an RF communications oriented program. I am 43 years old now. I have studied C code, opcode (mainly x86 and AT+T) for around 3 years in order to learn how to recognize viral code and the use of procedural languages. I am currently learning C# and the other virtual runtime system languages. I guess I started with the egg rather than the chicken. My past work would indicate that my primary strength is in applied mathematics.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberReza Ahmadi9-Jun-12 7:04 
Questiona question Pinmemberyakkka928-Apr-12 5:57 
QuestionWould appreciate an honest answer... Pinmembersuchit_ust4-Oct-11 6:46 
Questionphysic formular Pinmembertwintaha14-Apr-11 16:39 
AnswerRe: physic formular Pinmvplogicchild15-Apr-11 12:24 
QuestionQuestion PinmemberJonathan201027-Nov-10 22:05 
AnswerRe: Question Pinmvplogicchild29-Nov-10 11:46 
GeneralMy vote of 5 Pinmembersam.hill5-Nov-10 7:07 
GeneralOscillate Whitespace Pinmemberaspdotnetdev4-Nov-10 19:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 5 Nov 2010
Article Copyright 2010 by logicchild
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid