Want to create an animation based on a function that generates plots - python

I want to create an animation based on running a bunch of plots together. I just can't figure out how to get it to work for my purposes, this is the code I'm trying to work with. It generates a bunch of plots, but I want it to create an animation.
pi = 3.14159
velocity = 220 #kilometers per second
def dtheta(r): #creating a function that gives angular velocity based on distance from galactic center
dtheta = velocity/r #This function comes from the equation for angular velocity, ω=v/r, and ω =
dtheta/dt, which is what our function represents
return dtheta
#Creating frames at specific times for a set of distances
velocity = 220 #in km/s or pc/My
frames = 11
tstart = 0 #in units of Million Years
tfinal = 1
Stars= 25 #The number of stars being observed, equally spaced from 2 to 20 parsecs from the galactic center
t = np.linspace(tstart,tfinal,frames)
r = np.linspace(2,20,Stars)
TimeMatrix = []
for k in t:
snapshot = list([k*dtheta(r) for r in r]) # creating a list of the positions of a set of stars for a given time = k
print()
print('t =', k, 'Million Years')
plt.axes(projection = 'polar')
plt.ylim(0,22)
plt.plot(snapshot, r, 'ok')
plt.show()
TimeMatrix.append(list(snapshot))
def plotfunction(n):
plt.axes(projection = 'polar')
plt.ylim(0,22)
return plt.plot(TimeMatrix[n],r,'ok')
plotfunction(1) #needs integer input, pulls out the nth frame of the above series of plots
Anything would help, thanks!

Here's one way to do it:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
pi = 3.14159
velocity = 220 #kilometers per second
def dtheta(r): #creating a function that gives angular velocity based on distance from galactic center
dtheta = velocity/r #This function comes from the equation for angular velocity, ω=v/r, and ω =
#dtheta/dt, which is what our function represents
return dtheta
#Creating frames at specific times for a set of distances
velocity = 220 #in km/s or pc/My
frames = 11
tstart = 0 #in units of Million Years
tfinal = 1
Stars= 25 #The number of stars being observed, equally spaced from 2 to 20 parsecs from the galactic center
t = np.linspace(tstart,tfinal,frames)
r = np.linspace(2,20,Stars)
plt.figure(figsize=(12,4))
plt.axes(projection = 'polar')
plt.ylim(0,22)
snapi = plt.plot([t[0]*dtheta(i) for i in r] , r, 'ok', lw=1.5)
plt.ion() # set interactive mode
plt.show()
for i,snap in enumerate(t):
# for l in snapi:
# l.remove()
# del l
snapp=[snap*dtheta(k) for k in r]
snapi = plt.plot(snapp, r, 'ok', lw=1.5)
plt.legend()
plt.gcf().canvas.draw()
plt.pause(2)

Related

Animating Brownian particle movement with Python?

I am attempting to create a Langevin simulation using python. Currently, I have code which updates the x and y coordinates of a single point based on the Langevin equations, and returns all of these positions in two arrays (x array and y array). I want to plot these changes in position as a moving scatter plot to show how the particle moves over time. How can I do this? Below is what I have so far:
# IMPORT STATEMENTS
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# CONSTANTS
v = 3.12e-5 # swimming speed of B. Subtilis [m/s]
M = 1 # moment of B. Subtilis
k = 1.38e-23 # Boltzmann constant [m^2kg/s^2K]
T = 293 # Room temperature [K]
eta = 0.1 # viscosity of water [Pa s]
a = 2e-6 # spherical cell radius [m]
Dr = k*T/8*np.pi*eta*a**3 # rotational diffusion coefficient of B. Subtilis
# ADJUSTABLE PARAMETERS
B = 1 # strength of the magnetic field [T]
t = 100 # time over which motion is observed [s]
dt = 1 # time step between recorded positions
N = 1000 # number of cells
#INITIAL CONDITIONS
theta_i = 0 # initial swimming orientation [radians]
xi = 0.001 # initial x position [m]
yi = 0.001 # initial y position [m]
x = []
y = []
# MAIN SCRIPT
for i in range (0,t,dt):
theta_j = (theta_i + M*B*np.sin(theta) + np.sqrt(2*Dr)*ksi)*dt
xj = (xi + v*np.cos(theta))*dt
yj = (yi + v*np.sin(theta))*dt
x.append(xj)
y.append(yj)
theta_i = theta_j
xi = xj
yi = yj
take a look at this:
https://github.com/mohammadjafariph/Brownian-Motion-Simulation
animated versions are available

Problem with earth orbit plot using python

I am trying to write a code for the orbit of the earth in SI using a symplectic integrator, my attempt is as follows:
import numpy as np
import matplotlib.pyplot as plt
#Set parameters
G = 6.67348e-11
mEar = 5.972e24
mSun = 1.989e30
def earth_orbit(x0, y0, vx0, vy0, N):
dt = 1/N #timestep
pos_arr = np.zeros((N,2)) #empty array to store position
vel_arr = np.zeros((N,2)) #empty array to store velocities
#Initial conditions
# x0 = x
# y0 = y
# vx0 = vx
# vy0 = vy
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k]*dt #update positions
force = -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
vel_arr[k+1] = vel_arr[k] + (force/mEar) * dt #update velocities
#Plot:
plt.plot(pos_arr, 'go', markersize = 1, label = 'Earth trajectory')
# plt.plot(0,0,'yo', label = 'Sun positon') # yellow marker
# plt.plot(pos_arr[0],'bo', label = 'Earth initial positon') # dark blue marker
plt.axis('equal')
plt.xlabel ('x')
plt.ylabel ('y')
return pos_arr, vel_arr
earth_orbit(149.59787e9, 0, 0, 29800, 1000)
The output is 2 dots and I can't figure out if this is a unit issue or a calculation issue?
Display the trajectory
pos_arr contains the x and y coordinates in its columns. To display the whole trajectory, plt.plot(pos_arr[:,0], pos_arr[:,1]) can thus be used. I would prefer to use plt.plot(*pos_arr.T) as a shorter alternative. The line that displays the trajectory must be replaced by:
plt.plot(*pos_arr.T, 'g', label = 'Earth trajectory')
Change the timestep
Here the timestep (in second) is chosen as 1/N, where N is the number of iterations. So, the total duration of the simulation is equal to timestep * N = 1 second ! For N=1000, you can instead try with timestep = 3600*12 (half-day), so that the total duration is a little less than 1.5 years. I suggest passing the duration as a parameter of the function earth_orbit and then setting timestep as duration / N.
def earth_orbit(x0, y0, vx0, vy0, N=1000, duration=3.15e7):
dt = duration / N
...
As said in the comments, this is not the Verlet algorithm, but the symplectic Euler algorithm. The difference is in the initialization, but in comparing against a more exact reference solution and with several step sizes, the difference in the orders, 2 vs. 1, will be quite visible.
A short change to the time loop ensuring that the velocities are at the half-time steps as required for Leapfrog Verlet could look like this:
def force(pos): return -G * mSun * mEar * pos_arr[k+1] / (np.linalg.norm(pos_arr[k+1])**3) #force calculation
pos_arr[0] = (x0,y0) #set the intial positions in the array
vel_arr[0] = (vx0,vy0) #set the initial velocities in the array
vel_arr[0] += (force(pos_arr[0])/mEar) * (0.5*dt) #correct for velocity at half-time
#Implement Verlet Algorithm
for k in range (N-1):
pos_arr[k+1] = pos_arr[k] + vel_arr[k] * dt #update positions
vel_arr[k+1] = vel_arr[k] + (force(pos_arr[k+1])/mEar) * dt #update velocities

How to animate motion of a dynamical system in Python?

Here's the how cart pendulum looks like
Imagine you have a 4 differantial equations which represents the motion of a dynamic system (pendulum on a cart) and you solved these equations using scipy.integrate.odeint for 10 seconds with interval of 0.01 seconds.
Finally you get the solution matrix with size (1000,4). For each diff eqs you get 1000 data points. Everything is ok so far. For example, If I plot one of the motion I can get beautiful graphics.(Below image shows the motion of the pendulum rod(oscillating))
Here's Graph of theta angle
But, instead of boring graphics and I want to make an animation that shows the motion of the cart as Steve Brunton did it as below link with using Matlab.
Here's link of the cart-pend video!
====================================================================
To animate the figures I actually tried to do what Steve Brunton did in Matlab, with Python. But the result is just a frozen figure instead of moving one. Actually If I run this script from Spyder IDE, I get 1000 figures in the IPython console.(Each figure represents a snapshot of the system's instantaneous motion which is good. But I want just one figure with 1000 sequantial frames on it.)
Here's the snap of frozen cart-pend
I've written two python scripts. One for only plotting the other is for solving the diff eqs and feed the results to the other one.
~~~~~~~~~~~~~~~~~~~~~~~~~
This code is for plotting the animated figures.
from math import sqrt, sin, cos
import matplotlib.pyplot as plt
from matplotlib import animation
def draw_cart(states, m, M, L):
x = states[0] # Position of the center of the cart
theta = states[3] # Angle of the pendulum rod
#Dimensions
W = 1*sqrt(M/5) # Cart width
H = .5*sqrt(M/5) # Cart Height
wr = .2 # Wheel radius
mr = .3*sqrt(m) # Mass Radius
#Positions
y = wr/2+ H/2 # Cart Vertical Position
w1x = x-.9*W/2 # Left Wheel x coordinate
w1y = 0 # Left wheel y coordinate
w2x = x+(.9*W/2) # Right Wheel x coordinate
w2y = 0 # Right Wheel y coordinate
# Pendulum Mass x-y coordinates
px = x+(L*sin(theta))
py = y-(L*cos(theta))
#Identfying Figure
plt.figure()
plt.axes(xlim=(-5, 5), ylim=(-2, 2.5))
# Plotting the base line
line = plt.Line2D((-10, 10), (0, 0), color='k', linewidth=2)
plt.gca().add_line(line)
plt.hold(True)
# Shapes
rectangle1 = plt.Rectangle((x-(W/2), (y-H/2)), W, H, fill=True, color='b') # Cart
rectangle2= plt.Rectangle((px-(mr/2), py-(mr/2)), mr, mr, fill=True, color='r') # Pendulum mass
circle2 = plt.Circle((w1x, w1y), wr/2, fill=True, color='g') #Left whell
circle3 = plt.Circle((w2x, w2y), wr/2, fill=True, color='g') #Right whell
plt.plot((x, px), (y, py), 'k', lw=2) #Pendulum rod
#Adding shapes to the figure
plt.gca().add_patch(rectangle1)
plt.gca().add_patch(rectangle2)
plt.gca().add_patch(circle2)
plt.gca().add_patch(circle3)
# Showing the figure
plt.show()
plt.hold(False)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the other code for solving the diff eqs and feeding the solution to the above code.
from math import pi, sin, cos
import numpy as np
from scipy.integrate import odeint
import draw_cart_pend_rt
import matplotlib.pyplot as plt
# System Parameters
m = 1
M = 5
L = 2
g = -10
d = 1
u = 0
def cart_pend_dynamics(states, tspan):
Sy = sin(states[2])
Cy = cos(states[2])
D = m*L*L*(M+(m*(1-(Cy**2))))
state_derivatives = np.zeros_like(states)
state_derivatives[0] = states[1]
state_derivatives[1] = ((1/D)*(((-m**2)*(L**2)*g*Cy*Sy)+(m*(L**2)*(m*L*(states[3]**2)*Sy-d*(states[1])))))+(m*L*L*(1/D)*u)
state_derivatives[2] = states[3]
state_derivatives[3] = ((1/D)*((m+M)*m*g*L*Sy-m*L*Cy*(m*L*(states[3])**2*Sy-d*states[1])))-(m*L*Cy*(1/D)*u)+(0.01*1)
return state_derivatives
def solution_of_cartpend(dt):
# Initial conditions to solve diff eqs
states = np.array([0.0, 0.0, pi, 0.5]) # Left to right, cart; position-velocity, pend mass; angle-angular velocity
tspan = np.arange(0, 10, dt)
state_sol = odeint(cart_pend_dynamics, states, tspan)
return state_sol
# Time Interval
dt = 0.01
solution = solution_of_cartpend(dt)
x_den, y_den = solution.shape
# Validating the solution
plt.axes(xlim=(0,10), ylim=(-10,10))
t = np.arange(0, 10, dt)
plt.gca().plot(t, (solution[:, 2]), 'b', label='theta1')
# Animating the figures
for i in range(x_den):
draw_cart_pend_rt.draw_cart(solution[i,:], m, M, L)

Numpy: Generate grid according to density function

linspace generates a linear space. How can I generate a grid using an arbitrary density function?
Say, I would like to have a grid from 0 to 1, with 100 grid points, and where the density of points is given by (x - 0.5)**2 - how would I create such a grid in Python?
That is, I want many grid-points where the function (x - 0.5)**2) is large, and few points where the function is small. I do not want a grid that has values according to this function.
For example like this:
x = (np.linspace(0.5,1.5,100)-0.5)**2
The start and end values have to be chosen so that f(start) = 0 and f(end)=1.
In that case the following solution should work. Be sure that func is positive throughout the range...
import numpy as np
from matplotlib import pyplot as plt
def func(x):
return (x-0.5)**2
start = 0
end = 1
npoints = 100
x = np.linspace(start,end,npoints)
fx = func(x)
# take density (or intervals) as inverse of fx
# g in [0,1] controls how much warping you want.
# g = 0: fully warped
# g = 1: linearly spaced
g = 0
density = (1+g*(fx-1))/fx
# sum the intervals to get new grid
x_density = np.cumsum(density)
# rescale to match old range
x_density -= x_density.min()
x_density/= x_density.max()
x_density *= (end-start)
x_density += start
fx_density = func(x_density)
plt.plot(x,fx,'ok',ms = 10,label = 'linear')
plt.plot(x_density,fx_density,'or',ms = 10,label = 'warped')
plt.legend(loc = 'upper center')
plt.show()

Method to uniformly randomly populate a disk with points in python

I have an application that requires a disk populated with 'n' points in a quasi-random fashion. I want the points to be somewhat random, but still have a more or less regular density over the disk.
My current method is to place a point, check if it's inside the disk, and then check if it is also far enough away from all other points already kept. My code is below:
import os
import random
import math
# ------------------------------------------------ #
# geometric constants
center_x = -1188.2
center_y = -576.9
center_z = -3638.3
disk_distance = 2.0*5465.6
disk_diam = 5465.6
# ------------------------------------------------ #
pts_per_disk = 256
closeness_criteria = 200.0
min_closeness_criteria = disk_diam/closeness_criteria
disk_center = [(center_x-disk_distance),center_y,center_z]
pts_in_disk = []
while len(pts_in_disk) < (pts_per_disk):
potential_pt_x = disk_center[0]
potential_pt_dy = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_y = disk_center[1]+potential_pt_dy
potential_pt_dz = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_z = disk_center[2]+potential_pt_dz
potential_pt_rad = math.sqrt((potential_pt_dy)**2+(potential_pt_dz)**2)
if potential_pt_rad < (disk_diam/2.0):
far_enough_away = True
for pt in pts_in_disk:
if math.sqrt((potential_pt_x - pt[0])**2+(potential_pt_y - pt[1])**2+(potential_pt_z - pt[2])**2) > min_closeness_criteria:
pass
else:
far_enough_away = False
break
if far_enough_away:
pts_in_disk.append([potential_pt_x,potential_pt_y,potential_pt_z])
outfile_name = "pt_locs_x_lo_"+str(pts_per_disk)+"_pts.txt"
outfile = open(outfile_name,'w')
for pt in pts_in_disk:
outfile.write(" ".join([("%.5f" % (pt[0]/1000.0)),("%.5f" % (pt[1]/1000.0)),("%.5f" % (pt[2]/1000.0))])+'\n')
outfile.close()
In order to get the most even point density, what I do is basically iteratively run this script using another script, with the 'closeness' criteria reduced for each successive iteration. At some point, the script can not finish, and I just use the points of the last successful iteration.
So my question is rather broad: is there a better way to do this? My method is ok for now, but my gut says that there is a better way to generate such a field of points.
An illustration of the output is graphed below, one with a high closeness criteria, and another with a 'lowest found' closeness criteria (what I want).
A simple solution based on Disk Point Picking from MathWorld:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
r = np.random.uniform(low=0, high=1, size=n) # radius
theta = np.random.uniform(low=0, high=2*np.pi, size=n) # angle
x = np.sqrt(r) * np.cos(theta)
y = np.sqrt(r) * np.sin(theta)
# for plotting circle line:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'-', alpha=.5) # draw unit circle line
ax.plot(x, y, '.') # plot random points
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives.
Alternatively, you also could create a regular grid and distort it randomly:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
n = 20
tt = np.linspace(-1, 1, n)
xx, yy = np.meshgrid(tt, tt) # create unit square grid
s_x, s_y = xx.ravel(), yy.ravel()
ii = np.argwhere(s_x**2 + s_y**2 <= 1).ravel() # mask off unwanted points
x, y = s_x[ii], s_y[ii]
triang = tri.Triangulation(x, y) # create triangluar grid
# distort the grid
g = .5 # distortion factor
rx = x + np.random.uniform(low=-g/n, high=g/n, size=x.shape)
ry = y + np.random.uniform(low=-g/n, high=g/n, size=y.shape)
rtri = tri.Triangulation(rx, ry, triang.triangles) # distorted grid
# for circle:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'k-', alpha=.2) # circle line
ax.triplot(triang, "g-", alpha=.4)
ax.triplot(rtri, 'b-', alpha=.5)
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives
The triangles are just there for visualization. The obvious disadvantage is that depending on your choice of grid, either in the middle or on the borders (as shown here), there will be more or less large "holes" due to the grid discretization.
If you have a defined area like a disc (circle) that you wish to generate random points within you are better off using an equation for a circle and limiting on the radius:
x^2 + y^2 = r^2 (0 < r < R)
or parametrized to two variables
cos(a) = x/r
sin(a) = y/r
sin^2(a) + cos^2(a) = 1
To generate something like the pseudo-random distribution with low density you should take the following approach:
For randomly distributed ranges of r and a choose n points.
This allows you to generate your distribution to roughly meet your density criteria.
To understand why this works imagine your circle first divided into small rings of length dr, now imagine your circle divided into pie slices of angle da. Your randomness now has equal probability over the whole boxed area arou d the circle. If you divide the areas of allowed randomness throughout your circle you will get a more even distribution around the overall circle and small random variation for the individual areas giving you the psudo-random look and feel you are after.
Now your job is just to generate n points for each given area. You will want to have n be dependant on r as the area of each division changes as you move out of the circle. You can proportion this to the exact change in area each space brings:
for the n-th to n+1-th ring:
d(Area,n,n-1) = Area(n) - Area(n-1)
The area of any given ring is:
Area = pi*(dr*n)^2 - pi*(dr*(n-1))
So the difference becomes:
d(Area,n,n-1) = [pi*(dr*n)^2 - pi*(dr*(n-1))^2] - [pi*(dr*(n-1))^2 - pi*(dr*(n-2))^2]
d(Area,n,n-1) = pi*[(dr*n)^2 - 2*(dr*(n-1))^2 + (dr*(n-2))^2]
You could expound this to gain some insight on how much n should increase but it may be faster to just guess at some percentage increase (30%) or something.
The example I have provided is a small subset and decreasing da and dr will dramatically improve your results.
Here is some rough code for generating such points:
import random
import math
R = 10.
n_rings = 10.
n_angles = 10.
dr = 10./n_rings
da = 2*math.pi/n_angles
base_points_per_division = 3
increase_per_level = 1.1
points = []
ring = 0
while ring < n_rings:
angle = 0
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + da*math.random()
rr = r*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
base_points_per_division = base_points_per_division*increase_per_level
ring += 1
I tested it with the parameters:
n_rings = 20
n_angles = 20
base_points = .9
increase_per_level = 1.1
And got the following results:
It looks more dense than your provided image, but I imagine further tweaking of those variables could be beneficial.
You can add an additional part to scale the density properly by calculating the number of points per ring.
points_per_ring = densitymath.pi(dr**2)*(2*n+1)
points_per_division = points_per_ring/n_angles
This will provide a an even better scaled distribution.
density = .03
points = []
ring = 0
while ring < n_rings:
angle = 0
base_points_per_division = density*math.pi*(dr**2)*(2*ring+1)/n_angles
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + min(da,da*random.random())
rr = ring*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
ring += 1
Giving better results using the following parameters
R = 1.
n_rings = 10.
n_angles = 10.
density = 10/(dr*da) # ~ ten points per unit area
With a graph...
and for fun you can graph the divisions to see how well it is matching your distriubtion and adjust.
Depending on how random the points need to be, it may be simple enough to just make a grid of points within the disk, and then displace each point by some small but random amount.
It may be that you want more randomness, but if you just want to fill your disc with an even-looking distribution of points that aren't on an obvious grid, you could try a spiral with a random phase.
import math
import random
import pylab
n = 300
alpha = math.pi * (3 - math.sqrt(5)) # the "golden angle"
phase = random.random() * 2 * math.pi
points = []
for k in xrange(n):
theta = k * alpha + phase
r = math.sqrt(float(k)/n)
points.append((r * math.cos(theta), r * math.sin(theta)))
pylab.scatter(*zip(*points))
pylab.show()
Probability theory ensures that the rejection method is an appropriate method
to generate uniformly distributed points within the disk, D(0,r), centered at origin and of radius r. Namely, one generates points within the square [-r,r] x [-r,r], until a point falls within the disk:
do{
generate P in [-r,r]x[-r,r];
}while(P[0]**2+P[1]**2>r);
return P;
unif_rnd_disk is a generator function implementing this rejection method:
import matplotlib.pyplot as plt
import numpy as np
import itertools
def unif_rnd_disk(r=1.0):
pt=np.zeros(2)
while True:
yield pt
while True:
pt=-r+2*r*np.random.random(2)
if (pt[0]**2+pt[1]**2<=r):
break
G=unif_rnd_disk()# generator of points in disk D(0,r=1)
X,Y=zip(*[pt for pt in itertools.islice(G, 1, 1000)])
plt.scatter(X, Y, color='r', s=3)
plt.axis('equal')
If we want to generate points in a disk centered at C(a,b), we have to apply a translation to the points in the disk D(0,r):
C=[2.0, -3.5]
plt.scatter(C[0]+np.array(X), C[1]+np.array(Y), color='r', s=3)
plt.axis('equal')

Categories

Resources