aboutsummaryrefslogtreecommitdiff
path: root/hw5/9-14.jl
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2024-04-27 04:25:23 -0400
committersotech117 <michael_foiani@brown.edu>2024-04-27 04:25:23 -0400
commite650ed1e1e908e51c78c1b047bec0da7c4fea366 (patch)
tree1fe238de7ca199b7fdee9bc29395080b3c4790e7 /hw5/9-14.jl
parent02756d17bca6f2b3bafa3f7b9fb6e5af438e94a0 (diff)
testing
Diffstat (limited to 'hw5/9-14.jl')
-rw-r--r--hw5/9-14.jl846
1 files changed, 846 insertions, 0 deletions
diff --git a/hw5/9-14.jl b/hw5/9-14.jl
new file mode 100644
index 0000000..7829e40
--- /dev/null
+++ b/hw5/9-14.jl
@@ -0,0 +1,846 @@
+# molecular dynamics 2d.
+
+# usage:
+# at the end of this script, under the header "DEMOS",
+# you'll see some functions which implement demos from GN chapter 9.
+# simply load the script in your development environment
+# (I strongly recommend not using jupiter)
+# and in the console/REPL run
+# demo_0()
+# etc.
+
+# demos 0,1,3 can optionally make an animated gif
+# if you call it with the optional argument demo_3(gif=1)
+
+# lmk if this script is giving you grief or if you find any bugs
+# kian@brown.edu
+
+using Statistics
+using StatsPlots
+
+mutable struct ParticleSystem
+ N::Int64 # number of particles
+ L::Float64 # square box side length
+ T₀::Float64 # initial temperature
+ t::Float64 # system time
+ dt::Float64 # time step
+ state::Vector{Float64} # state space array
+ steps::Int64 # number of steps
+ sampleInterval::Int64 # interval for sampling data
+ timeData::Vector{Float64} # array of sampled time points
+ energyData::Vector{Float64} # array of sampled energy values
+ tempData::Vector{Float64} # array of sampled temperature values
+ tempAccumulator::Float64 # temperature accumulator
+ squareTempAccumulator::Float64 # T^2 accumulator
+ virialAccumulator::Float64 # virial accumulator
+ xData::Vector{Vector{Float64}} # array of sampled position data
+ vData::Vector{Vector{Float64}} # array of sampled velocity data
+ forceType::String # force
+end
+
+function ParticleSystem(N::Int64=64, L::Float64=10.0, T₀::Float64=1.0)
+ t = 0.0
+ dt = 0.001
+ state = zeros(4N) # state space array, [x1,y1,x2,y2,...,vx1,vy1,...]
+ steps = 0
+ timeData = Float64[]
+ energyData = Float64[]
+ sampleInterval = 100
+ tempData = Float64[]
+ tempAccumulator = 0.0
+ squareTempAccumulator = 0.0
+ virialAccumulator = 0.0
+ xData = Vector{Float64}[]
+ vData = Vector{Float64}[]
+ forceType = "lennardJones"
+
+ return ParticleSystem(
+ N,
+ L,
+ T₀,
+ t,
+ dt,
+ state,
+ steps,
+ sampleInterval,
+ timeData,
+ energyData,
+ tempData,
+ tempAccumulator,
+ squareTempAccumulator,
+ virialAccumulator,
+ xData,
+ vData,
+ forceType
+ )
+end
+
+# some useful "views" of the state array
+# (read the performance tips chapter of the julia manual)
+@views positions(state) = state[ 1:Int64(length(state)/2) ]
+@views velocities(state) = state[ (Int64(length(state)/2)+1):end ]
+@views xcomponent(vector) = vector[ 1:2:end ]
+@views ycomponent(vector) = vector[ 2:2:end ]
+@views particle(n, vector) = [ vector[2n-1], vector[2n] ]
+
+# INITIALIZATION
+################################################################################
+
+function set_random_positions!(sys::ParticleSystem)
+ println("\tposition configuration: random")
+ positions(sys.state) .= rand(2*sys.N) .* sys.L
+ cool!(sys)
+end
+
+function set_square_lattice_positions!(sys::ParticleSystem)
+ println("\tposition configuration: square lattice")
+
+ n = Int64(floor(sqrt(sys.N))) # num lattice points per axis
+ latticeSpacing = sys.L / n
+
+ if sys.N != n^2
+ println("\t\toops... your chosen N=$(sys.N) is not a square number")
+ println("\t\t-> resetting N to $(n^2).")
+ sys.N = n^2
+ sys.state = zeros(4 * sys.N)
+ end
+
+ for i in 0:(n-1)
+ for j in 0:(n-1)
+ sys.state[2*(i*n+j)+1] = (i + 0.5) * latticeSpacing
+ sys.state[2*(i*n+j)+2] = (j + 0.5) * latticeSpacing
+ end
+ end
+end
+
+function set_triangular_lattice_positions!(sys::ParticleSystem)
+end
+
+function add_position_jitter!(sys::ParticleSystem, jitter::Float64=0.5)
+ println("\tadding a wee bit of random jitter to particle positions...")
+
+ for i = 1:length(positions(sys.state))
+ sys.state[i] += rand() - jitter
+ end
+end
+
+function set_random_velocities!(sys::ParticleSystem)
+ println("\tvelocity configuration: random")
+
+ velocities(sys.state) .= rand(2*sys.N) .- 0.5
+ zero_total_momentum!(sys)
+ velocities(sys.state) .*= sqrt(sys.T₀/temperature(sys))
+end
+
+function zero_total_momentum!(sys::ParticleSystem)
+ xcomponent(velocities(sys.state)) .-=
+ mean(xcomponent(velocities(sys.state)))
+ ycomponent(velocities(sys.state)) .-=
+ mean(ycomponent(velocities(sys.state)))
+end
+
+
+# FORCES / POTENTIALS
+################################################################################
+
+function force(sys::ParticleSystem)
+ if sys.forceType == "lennardJones"
+ force, virial = lennard_jones_force(sys)
+ elseif sys.forceType == "powerLaw"
+ force, virial = power_law_force(sys)
+ end
+
+ sys.virialAccumulator += virial
+
+ return force
+end
+
+# the minimum image approximation
+# (periodic boundary conditions)
+function minimum_image(xij::Float64, L::Float64)
+ if xij > (L/2)
+ xij -= L
+ elseif xij < -(L/2)
+ xij += L
+ end
+ return xij
+end
+
+function lennard_jones_force(sys::ParticleSystem)
+ x = xcomponent(positions(sys.state))
+ y = ycomponent(positions(sys.state))
+ virial = 0.0
+ force = zeros(2*sys.N)
+
+ Threads.@threads for i = 1:(sys.N-1)
+ for j = (i+1):sys.N
+ dx = minimum_image(x[i] - x[j], sys.L)
+ dy = minimum_image(y[i] - y[j], sys.L)
+
+ r2inv = 1.0 / (dx^2 + dy^2)
+ f = 48.0 * r2inv^7 - 24.0 * r2inv^4
+ fx = dx * f
+ fy = dy * f
+
+ force[2*i-1] += fx
+ force[2*i] += fy
+ force[2*j-1] -= fx
+ force[2*j] -= fy
+
+ virial += fx * dx + fy * dy
+ end
+ end
+
+ return force, 0.5 * virial
+end
+
+function lennard_jones_potential(sys::ParticleSystem)
+ x = xcomponent(positions(sys.state))
+ y = ycomponent(positions(sys.state))
+ U = 0.0
+
+ Threads.@threads for i in 1:(sys.N-1)
+ for j in (i+1):sys.N
+ dx = minimum_image(x[i] - x[j], sys.L)
+ dy = minimum_image(y[i] - y[j], sys.L)
+
+ r2inv = 1.0 / (dx^2 + dy^2)
+ U += r2inv^6 - r2inv^3
+ end
+ end
+ return 4.0 * U
+end
+
+function power_law_force(sys::ParticleSystem)
+end
+
+function power_law_potential(sys::ParticleSystem)
+end
+
+# TIME EVOLUTION
+################################################################################
+
+function keep_particles_in_box!(sys::ParticleSystem)
+ for i in 1:(2*sys.N)
+ if positions(sys.state)[i] > sys.L
+ positions(sys.state)[i] -= sys.L
+ elseif positions(sys.state)[i] < 0.0
+ positions(sys.state)[i] += sys.L
+ end
+ end
+
+# # another way of doing this: use the ternary operator
+# for i in 1:(2 * sys.N)
+# positions(sys.state)[i] < 0.0 ?
+# positions(sys.state)[i] % sys.L + sys.L :
+# positions(sys.state)[i] % sys.L
+# end
+end
+
+function verlet_step!(sys::ParticleSystem)
+ # compute acceleration at current time
+ acceleration = force(sys)
+
+ # compute positions at t + dt
+ positions(sys.state) .+=
+ velocities(sys.state) .* sys.dt .+
+ 0.5 .* acceleration .* (sys.dt)^2
+
+ # enforce boundary conditions
+ # (basically check if any particles left the box and put them back)
+ # see function implementation for deets
+ keep_particles_in_box!(sys)
+
+ # compute velocities at t + dt
+ velocities(sys.state) .+=
+ 0.5 * sys.dt .* (acceleration + force(sys))
+end
+
+function update_histogram!(histogram_data, bins, t_interval, sys)
+ # for problem 9-14
+ # if sys.t % t_interval != 0
+ # return # only update histogram at t = n * t_interval
+ # end
+
+ # take the first particle as the origin
+ origin = particle(1, sys.state)
+ # println("origin: ", origin)
+
+ # for each particle, calculate the distance from the origin
+ # and update the histogram
+ for i in 2:sys.N
+ pcl = particle(i, sys.state)
+ dx = minimum_image(pcl[1] - origin[1], sys.L)
+ dy = minimum_image(pcl[2] - origin[2], sys.L)
+ r = sqrt(dx^2 + dy^2)
+
+ # find the bin that r belongs to
+ for j in 1:length(bins)-1
+ if r >= bins[j] && r < bins[j+1]
+ histogram_data[j] += 1
+ break
+ end
+ end
+ end
+end
+
+function evolve!(sys, runtime, histogram_data, bins, t_interval)
+ numsteps = Int64(abs(runtime/sys.dt) + 1)
+
+ print_evolution_message(runtime, numsteps)
+
+ @time for step in 1:numsteps
+ verlet_step!(sys)
+ zero_total_momentum!(sys)
+
+ if (step % sys.sampleInterval == 1)
+ # for problem, 9-14 update historgram here
+ update_histogram!(histogram_data, bins, t_interval, sys)
+
+ push!(sys.timeData, sys.t)
+ push!(sys.energyData, energy(sys))
+ push!(sys.xData, positions(sys.state))
+ push!(sys.vData, velocities(sys.state))
+
+ T = temperature(sys)
+ push!(sys.tempData, T)
+ sys.tempAccumulator += T
+ sys.squareTempAccumulator += T^2
+ end
+
+ sys.t += sys.dt
+ sys.steps += 1
+ end
+ println("done.")
+end
+
+function reverse_time!(sys)
+ sys.dt *= -1
+ println("\ntime reversed! dt = $(sys.dt)")
+end
+
+function cool!(sys::ParticleSystem, cooltime::Float64=1.0)
+ numsteps = Int64(cooltime/sys.dt)
+ for step in 1:numsteps
+ verlet_step!(sys)
+ velocities(sys.state) .*= (1.0 - sys.dt)
+ end
+ reset_statistics!(sys)
+end
+
+# MEASUREMENTS
+################################################################################
+
+function kinetic_energy(sys::ParticleSystem)
+ return 0.5 * sum(velocities(sys.state) .* velocities(sys.state))
+end
+
+function potential_energy(sys::ParticleSystem)
+ return lennard_jones_potential(sys)
+end
+
+function temperature(sys::ParticleSystem)
+ return kinetic_energy(sys) / sys.N
+end
+
+function energy(sys::ParticleSystem)
+ return potential_energy(sys) + kinetic_energy(sys)
+end
+
+# STATISTICS
+################################################################################
+
+function reset_statistics!(sys::ParticleSystem)
+ sys.steps = 0
+ sys.tempAccumulator = 0.0
+ sys.squareTempAccumulator = 0.0
+ sys.virialAccumulator = 0.0
+ sys.xData = []
+ sys.vData = []
+end
+
+function mean_temperature(sys::ParticleSystem)
+ return sys.tempAccumulator / sys.steps
+end
+
+function mean_square_temperature(sys::ParticleSystem)
+ return sys.squareTempAccumulator / sys.steps
+end
+
+function mean_pressure(sys::ParticleSystem)
+ # factor of half because force is calculated twice each step
+ meanVirial = 0.5 * sys.virialAccumulator / sys.steps
+ return 1.0 + 0.5 * meanVirial / (sys.N * mean_temperature(sys))
+end
+
+function heat_capacity(sys::ParticleSystem)
+ meanTemperature = mean_temperature(sys)
+ meanSquareTemperature = mean_square_temperature(sys)
+ σ2 = meanSquareTemperature - meanTemperature^2
+ denom = 1.0 - σ2 * sys.N / meanTemperature^2
+ return sys.N / denom
+end
+
+function mean_energy(sys::ParticleSystem)
+ return mean(sys.energyData)
+end
+
+function std_energy(sys::ParticleSystem)
+ return std(sys.energyData)
+end
+
+# MATH / ADDITIONAL FUNCTIONS
+################################################################################
+
+function dot(v1::Vector{Float64}, v2::Vector{Float64})
+ return sum(v1 .* v2)
+end
+
+# GRAPHS
+################################################################################
+
+function initialize_plot()
+ plot(
+ size=(800,800),
+ titlefontsize=12,
+ guidefontsize=12,
+ )
+end
+
+function plot_positions_t(sys::ParticleSystem, t::Int64)
+ initialize_plot()
+ for n = 1:sys.N
+ scatter!(
+ [ sys.xData[t][2n-1] ],
+ [ sys.xData[t][2n] ],
+ markersize = 4.0,
+ markercolor = n,
+ markerstrokewidth = 0.4,
+ grid = true,
+ framestyle = :box,
+ legend = false,
+ )
+ end
+end
+
+function animate(sys::ParticleSystem, interval::Int64=1)
+ println("\ngenerating gif...")
+
+ scatter!()
+ animation = @animate for t in 1:length(sys.xData)
+ scatter()
+ for n = 1:sys.N
+ scatter!(
+ [ sys.xData[t][2n-1] ],
+ [ sys.xData[t][2n] ],
+ #markersize = 4.0,
+ markercolor = n,
+ #markerstrokewidth = 0.4,
+ grid = true,
+ framestyle = :box,
+ legend = false,
+ )
+ end
+ xlims!(0, sys.L)
+ ylims!(0, sys.L)
+ xlabel!("x")
+ ylabel!("y")
+ end every interval
+
+ gif(animation, "./animation.gif")
+ println("done.")
+end
+
+function plot_positions(sys::ParticleSystem)
+ initialize_plot()
+ for n = 1:sys.N
+ scatter!(
+ [ xcomponent(positions(sys.state))[n] ],
+ [ ycomponent(positions(sys.state))[n] ],
+ markersize = 4.0,
+ markercolor = n,
+ markerstrokewidth = 0.4,
+ grid = true,
+ framestyle = :box,
+ legend = false,
+ )
+ end
+ xlims!(0, sys.L)
+ ylims!(0, sys.L)
+ xlabel!("x")
+ ylabel!("y")
+ title!("positions at t=$(round(sys.t, digits=4))")
+end
+
+function plot_trajectories(sys::ParticleSystem, particles::Vector{Int64}=[ 1 ])
+ initialize_plot()
+ for n = 1:sys.N
+ scatter!(
+ [ xcomponent(positions(sys.state))[n] ],
+ [ ycomponent(positions(sys.state))[n] ],
+ markersize = 4.0,
+ markercolor = n,
+ markerstrokewidth = 0.4,
+ grid = true,
+ framestyle = :box,
+ legend = false,
+ )
+ end
+
+ for n in collect(particles)
+ xdata = [ sys.xData[i][2n-1] for i in 1:length(sys.xData) ]
+ ydata = [ sys.xData[i][2n] for i in 1:length(sys.xData) ]
+
+ # plot trajectory line for nth particle
+ scatter!(
+ xdata,
+ ydata,
+ color = n,
+ #markerstrokewidth = 0,
+ markerstrokecolor = n,
+ markersize = 0.7,
+ markeralpha = 0.5,
+ label = false,
+ widen = false,
+ )
+
+ # plot initial position for nth particle
+ scatter!(
+ [ sys.xData[1][2n-1] ],
+ [ sys.xData[1][2n] ],
+ markersize = 4.0,
+ markercolor = n,
+ markerstrokewidth = 0.4,
+ markeralpha = 0.3,
+ #label = "pcl. $n @t=t₀",
+ widen = false,
+ )
+
+ # plot final position for nth particle
+ scatter!(
+ [ sys.xData[end][2n-1] ],
+ [ sys.xData[end][2n] ],
+ markersize = 4.0,
+ markercolor = n,
+ markerstrokewidth = 0.4,
+ markeralpha = 1.0,
+ #label = "pcl $n @t=t",
+ widen = false,
+ )
+ end
+ title!("positions & trajectories at time t=$(round(sys.t, digits=2))")
+ plot!()
+end
+
+function plot_temperature(sys::ParticleSystem)
+ initialize_plot()
+ plot!(
+ sys.timeData,
+ sys.tempData,
+ #widen = true,
+ )
+ ylims!(
+ mean(sys.tempData) - std(sys.tempData) * 20,
+ mean(sys.tempData) + std(sys.tempData) * 20,
+ )
+ xlabel!("t")
+ ylabel!("T(t)")
+ title!("temperature vs time")
+
+end
+
+function plot_energy(sys::ParticleSystem, ylimit::Float64=1.0)
+ initialize_plot()
+ plot!(
+ sys.timeData,
+ sys.energyData,
+ #widen = true,
+ )
+ ylims!(
+ #ylimit * (mean(sys.energyData) - 1),
+ #ylimit * (mean(sys.energyData) + 1)
+ mean(sys.energyData) - std(sys.energyData) * 10,
+ mean(sys.energyData) + std(sys.energyData) * 10,
+ )
+ xlabel!("t")
+ ylabel!("E(t)")
+ title!("energy vs time")
+end
+
+function plot_speed_distribution(sys::ParticleSystem, numSamples::Int64=5)
+ initialize_plot()
+
+ numDataPoints = Int64(length(sys.vData))
+ interval = Int64(floor(numDataPoints / numSamples))
+
+ samples = collect(1:interval:numDataPoints)
+ for s in samples
+ speed = sqrt.(
+ xcomponent(sys.vData[s]).^2 .+
+ ycomponent(sys.vData[s]).^2
+ )
+ density!(
+ sys.vData[s],
+ normalize = :pdf,
+ label = "t = $(round(sys.timeData[s], digits=2))",
+ )
+ end
+ xlabel!("speed")
+ title!("speed distribution")
+end
+
+# CONSOLE PRINT DATA
+################################################################################
+
+function print_hello()
+ println("\nmolecular dynamics!")
+ println("number of threads: ", Threads.nthreads())
+end
+
+function print_bonjour()
+ println("\nbonjour")
+end
+
+function print_system_parameters(sys::ParticleSystem)
+ println("\nsystem parameters:")
+ println("\tN = $(sys.N) (number of particles)")
+ println("\tL = $(sys.L) (side length of square box)")
+ println("\tDT = $(sys.dt) (time step)")
+end
+
+function print_system_data(sys::ParticleSystem)
+ println("\nsystem data at time t=$(round(sys.t, digits=4))")
+
+ if sys.steps == 0
+ println("\ttemperature: $(temperature(sys))")
+ println("\tenergy: $(energy(sys))")
+ else
+ println("\tsteps evolved: $(sys.steps)")
+ println("\ttemperature: $(temperature(sys))")
+ println("\tenergy: $(energy(sys))")
+ println("\tmean energy: $(mean_energy(sys))")
+ println("\tstd energy: $(std_energy(sys))")
+ println("\theat capacity: $(heat_capacity(sys))")
+ println("\tPV/NkT: $(mean_pressure(sys))")
+ end
+end
+
+function print_evolution_message(runtime, numsteps)
+ println("\nevolving...")
+end
+
+# DEMOS
+################################################################################
+
+
+# DEMO 0: APPROACH TO EQUILIBRIUM
+function demo_0(;gif=0)
+ println("\nDEMO 0: APPROACH TO EQUILIBRIUM")
+ println("----------------------------------------")
+
+ sys = ParticleSystem(64, 120.0, 1.0)
+ print_system_parameters(sys)
+
+ set_square_lattice_positions!(sys)
+ set_random_velocities!(sys)
+ print_system_data(sys)
+ p1 = plot_positions(sys)
+
+ evolve!(sys, 20.0)
+ print_system_data(sys)
+
+ p2 = plot_trajectories(sys, collect(1:64))
+ p3 = plot_energy(sys)
+ p4 = plot_temperature(sys)
+
+ # make gif
+ if gif == 1
+ animate(sys, 1)
+ end
+
+ plot(
+ p1, p2, p3, p4,
+ layout = grid(2,2, heights=[0.7,0.3]),
+ size = (1280,720)
+ )
+end
+
+# DEMO 1: TIME REVERSAL TEST
+function demo_1(;gif=0)
+ println("\nDEMO 1: TIME REVERSAL TEST")
+ println("----------------------------------------")
+
+ sys = ParticleSystem(64, 120.0, 1.0)
+ print_system_parameters(sys)
+
+ set_square_lattice_positions!(sys)
+ set_random_velocities!(sys)
+ print_system_data(sys)
+ p1 = plot_positions(sys)
+
+ evolve!(sys, 50.0)
+ #p2 = plot_trajectories(sys, collect(1:64))
+ p2 = plot_positions(sys)
+
+ reverse_time!(sys)
+ evolve!(sys, 50.0)
+ print_system_data(sys)
+ #p3 = plot_trajectories(sys, collect(1:64))
+ p3 = plot_positions(sys)
+
+ # make gif
+ if gif == 1
+ animate(sys, 4)
+ end
+
+ plot(
+ p1, p2, p3,
+ layout = (1,3),
+ size = (1200,400)
+ )
+end
+
+# DEMO 2: SPEED DISTRIBUTION
+function demo_2()
+ println("\nDEMO 2: SPEED DISTRIBUTION")
+ println("----------------------------------------")
+
+ sys = ParticleSystem[]
+
+ # array for speed distribution plots
+ ps = Plots.Plot{Plots.GRBackend}[]
+
+ # array for trajectory plots
+ pt = Plots.Plot{Plots.GRBackend}[]
+
+ # initialize three systems with different initial conditions
+ # but same KE and PE, evolve, and save plots
+ for i = 1:3
+ push!(sys, ParticleSystem(64, 120.0, 1.0))
+
+ println("\nSYSTEM $i")
+ print_system_parameters(sys[i])
+
+ set_square_lattice_positions!(sys[i])
+ add_position_jitter!(sys[i])
+ set_random_velocities!(sys[i])
+ print_system_data(sys[i])
+
+ evolve!(sys[i], 40.0)
+ print_system_data(sys[i])
+ push!(ps, plot_speed_distribution(sys[i], 5))
+ push!(pt, plot_trajectories(sys[i], collect(1:64)) )
+ end
+
+
+ # plot speed distribution and trajectory plots
+ plot(
+ ps[1], ps[2], ps[3],
+ pt[1], pt[2], pt[3],
+ layout = (2,3),
+ size = (1920,1080)
+ )
+end
+
+# DEMO 3: MELTING TRANSITION
+function demo_3(;gif=0)
+ println("\nDEMO 3: MELTING TRANSITION")
+ println("----------------------------------------")
+
+ # initialize system of particles on square lattice with zero velocity
+ sys = ParticleSystem(100, 10.0, 5.0)
+ set_square_lattice_positions!(sys)
+ print_system_data(sys)
+ p1 = plot_positions(sys)
+
+ # evolve the system and watch them "crystallize"
+ # into a triangular lattice formation
+ evolve!(sys, 20.0)
+ print_system_data(sys)
+ p2 = plot_trajectories(sys, collect(1:100))
+
+ # now, increase the temperature of the system by giving the particles
+ # some velocity. evolve the system and plot the trajectories.
+ set_random_velocities!(sys)
+ evolve!(sys, 60.0)
+ print_system_data(sys)
+ p3 = plot_trajectories(sys, collect(1:100))
+
+ # some more plots
+ p4 = plot_energy(sys, 0.0)
+ p5 = plot_temperature(sys)
+ p6 = plot_speed_distribution(sys, 20)
+
+ # make gif
+ if gif == 1
+ animate(sys, 1)
+ end
+
+ plot(
+ p1, p2, p3, p4, p5, p6,
+ layout = (2,3),
+ size = (1280,720)
+ )
+end
+
+function normalize_histogram(histogram)
+ println("normalizing histogram...")
+ println(histogram)
+ return histogram ./ sum(histogram)
+end
+
+# problem 9-14
+function problem_9_14(temp, bins, t_interval)
+ println("\nPROBLEM 9-14: RADIAL DISTRIBUTION FUNCTION")
+ println("----------------------------------------")
+
+ # initialize system of particles on square lattice with zero velocity
+ sys = ParticleSystem(16, 4.0, temp)
+ set_square_lattice_positions!(sys)
+ set_random_velocities!(sys)
+ print_system_data(sys)
+ p1 = plot_positions(sys)
+
+ # evolve the system and watch them "crystallize"
+ # into a triangular lattice formation
+ histogram_data = zeros(length(bins) - 1)
+ evolve!(sys, 500, histogram_data, bins, t_interval)
+ print_system_data(sys)
+
+ # normalize the histogram
+ histogram_data = normalize_histogram(histogram_data)
+ # get the max y value for the plot
+ y_max = maximum(histogram_data)
+
+ # center the bins
+ centered_bins = bins .+ (bins[2] - bins[1]) / 2
+
+ # plot the histogram
+ return plot(
+ centered_bins[2:end], histogram_data,
+ marker = :circle,
+ xlabel = "r",
+ ylabel = "g(r)",
+ title = "Pair Correlation (T=$temp)",
+ label="pdf(r)"
+ ), y_max
+end
+
+bins = range(.5, stop=3, length=40)
+p_05, y_max_1 = problem_9_14(.5, bins, 10.0)
+# ont this plot, plot vertical lines at 1.1, 1.9, 2.8
+y_max_1 += 0.025
+plot!(p_05, [1.1, 1.1], [0, y_max_1], line = :dash, label = "r = 1.1", color = :green, ylim=(0, y_max_1), lw=1.5, legend=:topright)
+plot!(p_05, [1.9, 1.9], [0, y_max_1], line = :dash, label = "r = 1.9", color = :orange, lw=1.5)
+plot!(p_05, [2.8, 2.8], [0, y_max_1], line = :dash, label = "r = 2.8", color = :red, lw=1.5)
+
+p_5, y_max = problem_9_14(5.0, bins, 10.0)
+plot!(p_5, [1.1, 1.1], [0, y_max_1], line = :dash, label = "r = 1.1", color = :green, ylim=(0, y_max_1), lw=1.5, legend=:topright)
+plot!(p_5, [1.9, 1.9], [0, y_max_1], line = :dash, label = "r = 1.9", lw=1.5, color = :orange)
+plot!(p_5, [2.8, 2.8], [0, y_max_1], line = :dash, label = "r = 2.8", lw=1.5, color = :red)
+
+using Plots.PlotMeasures # for padding
+
+plot(p_05, p_5, layout = (1,2), size = (1280,600), bottom_margin = 10mm, left_margin = 10mm, right_margin = 10mm, top_margin = 10mm)
+
+savefig("9-14.png") \ No newline at end of file