Granular.jl

Julia package for granular dynamics simulation
git clone git://src.adamsgaard.dk/Granular.jl
Log | Files | Refs | README | LICENSE

commit a86d2ec05f76ac24aabe853af5b7da9296365c23
parent f8aa00babd9ab63c2f2454e05f18043c6f62a1d8
Author: Anders Damsgaard <andersd@riseup.net>
Date:   Sat,  3 Jun 2017 10:08:38 -0400

add atmosphere tests, fix many grid-related issues

Diffstat:
Msrc/atmosphere.jl | 12++++--------
Msrc/contact_search.jl | 26++++++++++++++++----------
Msrc/grid.jl | 22+++++++++++-----------
Msrc/simulation.jl | 2+-
Atest/atmosphere.jl | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/contact-search-and-geometry.jl | 6+++---
Mtest/grid.jl | 41+++++++++++++++++++++++++++++++++++++++++
Mtest/runtests.jl | 1+
8 files changed, 204 insertions(+), 33 deletions(-)

diff --git a/src/atmosphere.jl b/src/atmosphere.jl @@ -114,19 +114,16 @@ function createRegularAtmosphereGrid(n::Array{Int, 1}, yh = repmat(linspace(origo[2] + .5*dx[2], L[2] - .5*dx[2], n[2])', n[1], 1) zl = -linspace(.5*dx[3], L[3] - .5*dx[3], n[3]) - zi = -linspace(0., L[3], n[3] + 1) u = zeros(n[1] + 1, n[2] + 1, n[3], length(time)) v = zeros(n[1] + 1, n[2] + 1, n[3], length(time)) - h = zeros(n[1] + 1, n[2] + 1, n[3], length(time)) - e = zeros(n[1] + 1, n[2] + 1, n[3], length(time)) return Atmosphere(name, time, xq, yq, xh, yh, - zl, zi, - u, v, h, e, + zl, + u, v, Array{Array{Int, 1}}(size(xh, 1), size(xh, 2))) end @@ -140,8 +137,7 @@ function addAtmosphereDrag!(simulation::Simulation) error("no atmosphere data read") end - u, v, h, e = interpolateAtmosphereState(simulation.atmosphere, - simulation.time) + u, v = interpolateAtmosphereState(simulation.atmosphere, simulation.time) for ice_floe in simulation.ice_floes @@ -203,7 +199,7 @@ function applyAtmosphereVorticityToIceFloe!(ice_floe::IceFloeCylindrical, rho_a = 1.2754 # atmosphere density ice_floe.torque += - pi*ice_floe.areal_radius^4.*rho_o* + pi*ice_floe.areal_radius^4.*rho_a* (ice_floe.areal_radius/5.*ice_floe.atmosphere_drag_coeff_horiz + freeboard*ice_floe.atmosphere_drag_coeff_vert)* abs(.5*atmosphere_curl - ice_floe.ang_vel)* diff --git a/src/contact_search.jl b/src/contact_search.jl @@ -24,10 +24,10 @@ function findContacts!(simulation::Simulation; findContactsAllToAll!(simulation) elseif method == "ocean grid" - findContactsOceanGrid!(simulation) + findContactsInGrid!(simulation, simulation.ocean) elseif method == "atmosphere grid" - error("not yet implemented") + findContactsInGrid!(simulation, simulation.atmosphere) else error("Unknown contact search method '$method'") @@ -79,19 +79,25 @@ function findContactsAllToAll!(simulation::Simulation) end end -export findContactsOceanGrid! +export findContactsInGrid! """ - findContactsOceanGrid!(simulation) + findContactsInGrid!(simulation) Perform an O(n*log(n)) cell-based contact search between all ice floes in the -`simulation` object. Contacts between fixed ice floes are ignored. +`simulation` object. Contacts between fixed or disabled ice floes are ignored. """ -function findContactsOceanGrid!(simulation::Simulation) +function findContactsInGrid!(simulation::Simulation, grid::Any) for idx_i = 1:length(simulation.ice_floes) - grid_pos = simulation.ice_floes[idx_i].ocean_grid_pos - nx, ny = size(simulation.ocean.xh) + if typeof(grid) == Ocean + grid_pos = simulation.ice_floes[idx_i].ocean_grid_pos + elseif typeof(grid) == Atmosphere + grid_pos = simulation.ice_floes[idx_i].atmosphere_grid_pos + else + error("grid type not understood") + end + nx, ny = size(grid.xh) for i=(grid_pos[1] - 1):(grid_pos[1] + 1) for j=(grid_pos[2] - 1):(grid_pos[2] + 1) @@ -101,7 +107,7 @@ function findContactsOceanGrid!(simulation::Simulation) continue end - for idx_j in simulation.ocean.ice_floe_list[i, j] + for idx_j in grid.ice_floe_list[i, j] checkAndAddContact!(simulation, idx_i, idx_j) end end @@ -109,7 +115,7 @@ function findContactsOceanGrid!(simulation::Simulation) end end -export addContact! +export checkAndAddContact! """ checkAndAddContact!(simulation, i, j) diff --git a/src/grid.jl b/src/grid.jl @@ -158,26 +158,26 @@ end export findCellContainingPoint """ - findCellContainingPoint(ocean, point[, method]) + findCellContainingPoint(grid, point[, method]) -Returns the `i`, `j` index of the ocean grid cell containing the `point`. +Returns the `i`, `j` index of the grid cell containing the `point`. The function uses either an area-based approach (`method = "Area"`), or a conformal mapping approach (`method = "Conformal"`). The area-based approach is more robust. This function returns the coordinates of the cell. If no match is found the function returns `(0,0)`. # Arguments -* `ocean::Ocean`: object containing ocean data. +* `grid::Any`: grid object containing ocean or atmosphere data. * `point::Array{float, 1}`: two-dimensional vector of point to check. * `method::String`: approach to use for determining if point is inside cell or not, can be "Conformal" (default) or "Areal". """ -function findCellContainingPoint(ocean::Ocean, point::Array{float, 1}; +function findCellContainingPoint(grid::Any, point::Array{float, 1}; method::String="Conformal") - for i=1:size(ocean.xh, 1) - for j=1:size(ocean.yh, 2) - if isPointInCell(ocean, i, j, point, method=method) + for i=1:size(grid.xh, 1) + for j=1:size(grid.yh, 2) + if isPointInCell(grid, i, j, point, method=method) return i, j end end @@ -188,22 +188,22 @@ end export getNonDimensionalCellCoordinates """ Returns the non-dimensional conformal mapped coordinates for point `point` in -cell `i,j`, based off the coordinates in the `ocean` grid. +cell `i,j`, based off the coordinates in the grid. This function is a wrapper for `getCellCornerCoordinates()` and `conformalQuadrilateralCoordinates()`. """ -function getNonDimensionalCellCoordinates(ocean::Ocean, i::Int, j::Int, +function getNonDimensionalCellCoordinates(grid::Any, i::Int, j::Int, point::Array{float, 1}) - sw, se, ne, nw = getCellCornerCoordinates(ocean, i, j) + sw, se, ne, nw = getCellCornerCoordinates(grid, i, j) x_tilde, y_tilde = conformalQuadrilateralCoordinates(sw, se, ne, nw, point) return [x_tilde, y_tilde] end export isPointInCell """ -Check if a 2d point is contained inside a cell from the ocean grid. +Check if a 2d point is contained inside a cell from the supplied grid. The function uses either an area-based approach (`method = "Area"`), or a conformal mapping approach (`method = "Conformal"`). The area-based approach is more robust. This function returns `true` or `false`. diff --git a/src/simulation.jl b/src/simulation.jl @@ -111,7 +111,7 @@ function run!(simulation::Simulation; zeroForcesAndTorques!(simulation) if typeof(simulation.atmosphere.input_file) != Bool - sortIceFloesInGrid!(sim, sim.atmosphere) + sortIceFloesInGrid!(simulation, simulation.atmosphere) end if typeof(simulation.ocean.input_file) != Bool diff --git a/test/atmosphere.jl b/test/atmosphere.jl @@ -0,0 +1,127 @@ +#!/usr/bin/env julia + +# Check if atmosphere-specific functions and grid operations are functioning +# correctly + +info("#### $(basename(@__FILE__)) ####") + +info("Testing regular grid generation") +sim = SeaIce.createSimulation() +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([6, 6, 6], [1., 1., 1.]) +@test size(sim.atmosphere.xq) == (7, 7) +@test size(sim.atmosphere.yq) == (7, 7) +@test size(sim.atmosphere.xh) == (6, 6) +@test size(sim.atmosphere.yh) == (6, 6) +@test sim.atmosphere.xq[1, :, 1] ≈ zeros(7) +@test sim.atmosphere.xq[4, :, 1] ≈ .5*ones(7) +@test sim.atmosphere.xq[end, :, 1] ≈ 1.*ones(7) +@test sim.atmosphere.yq[:, 1, 1] ≈ zeros(7) +@test sim.atmosphere.yq[:, 4, 1] ≈ .5*ones(7) +@test sim.atmosphere.yq[:, end, 1] ≈ 1.*ones(7) +@test size(sim.atmosphere.u) == (7, 7, 6, 1) +@test size(sim.atmosphere.v) == (7, 7, 6, 1) +@test sim.atmosphere.u ≈ zeros(7, 7, 6, 1) +@test sim.atmosphere.v ≈ zeros(7, 7, 6, 1) + +info("Testing velocity drag interaction (static atmosphere)") +SeaIce.addIceFloeCylindrical(sim, [.5, .5], .25, .1) +SeaIce.setTotalTime!(sim, 5.) +SeaIce.setTimeStep!(sim) +sim_init = deepcopy(sim) +sim.ice_floes[1].lin_vel[1] = 0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test E_kin_rot_init ≈ E_kin_rot_final # no rotation before or after +@test E_kin_lin_init > E_kin_lin_final # linear velocity lost due to atmos drag + +info("Testing velocity drag interaction (static ice floe)") +sim = deepcopy(sim_init) +sim.atmosphere.v[:, :, 1, 1] = 0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test E_kin_rot_init ≈ E_kin_rot_final # no rotation before or after +@test E_kin_lin_init < E_kin_lin_final # linear vel. gained due to atmos drag + +info("Testing vortex interaction (static atmosphere)") +sim = deepcopy(sim_init) +sim.ice_floes[1].ang_vel = 0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test E_kin_rot_init > E_kin_rot_final # energy lost to atmosphere +@test sim.ice_floes[1].ang_vel > 0. # check angular velocity orientation +@test sim.ice_floes[1].ang_pos > 0. # check angular position orientation +@test E_kin_lin_init ≈ E_kin_lin_final # no linear velocity gained + +info("Testing vortex interaction (static ice floe)") +sim = deepcopy(sim_init) +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([1, 1, 1], [1., 1., 1.]) +sim.ice_floes[1].lin_pos[1] = 0.5 +sim.ice_floes[1].lin_pos[2] = 0.5 +sim.atmosphere.v[1, :, 1, 1] = -0.1 +sim.atmosphere.v[2, :, 1, 1] = 0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test sim.ice_floes[1].ang_vel > 0. # check angular velocity orientation +@test sim.ice_floes[1].ang_pos > 0. # check angular position orientation +@test E_kin_rot_init < E_kin_rot_final # rotation after due to atm vortex +@test E_kin_lin_init ≈ E_kin_lin_final # no linear velocity gained + +sim = deepcopy(sim_init) +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([1, 1, 1], [1., 1., 1.]) +sim.ice_floes[1].lin_pos[1] = 0.5 +sim.ice_floes[1].lin_pos[2] = 0.5 +sim.atmosphere.v[1, :, 1, 1] = 0.1 +sim.atmosphere.v[2, :, 1, 1] = -0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test sim.ice_floes[1].ang_vel < 0. # check angular velocity orientation +@test sim.ice_floes[1].ang_pos < 0. # check angular position orientation +@test E_kin_rot_init < E_kin_rot_final # rotation after due to atm vortex +@test E_kin_lin_init ≈ E_kin_lin_final # no linear velocity gained + +sim = deepcopy(sim_init) +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([1, 1, 1], [1., 1., 1.]) +sim.ice_floes[1].lin_pos[1] = 0.5 +sim.ice_floes[1].lin_pos[2] = 0.5 +sim.atmosphere.u[:, 1, 1, 1] = -0.1 +sim.atmosphere.u[:, 2, 1, 1] = 0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test sim.ice_floes[1].ang_vel < 0. # check angular velocity orientation +@test sim.ice_floes[1].ang_pos < 0. # check angular position orientation +@test E_kin_rot_init < E_kin_rot_final # rotation after due to atm vortex +@test E_kin_lin_init ≈ E_kin_lin_final # no linear velocity gained + +sim = deepcopy(sim_init) +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([1, 1, 1], [1., 1., 1.]) +sim.ice_floes[1].lin_pos[1] = 0.5 +sim.ice_floes[1].lin_pos[2] = 0.5 +sim.atmosphere.u[:, 1, 1, 1] = 0.1 +sim.atmosphere.u[:, 2, 1, 1] = -0.1 +E_kin_lin_init = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_init = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +SeaIce.run!(sim, verbose=false) +E_kin_lin_final = SeaIce.totalIceFloeKineticTranslationalEnergy(sim) +E_kin_rot_final = SeaIce.totalIceFloeKineticRotationalEnergy(sim) +@test sim.ice_floes[1].ang_vel > 0. # check angular velocity orientation +@test sim.ice_floes[1].ang_pos > 0. # check angular position orientation +@test E_kin_rot_init < E_kin_rot_final # rotation after due to atm vortex +@test E_kin_lin_init ≈ E_kin_lin_final # no linear velocity gained diff --git a/test/contact-search-and-geometry.jl b/test/contact-search-and-geometry.jl @@ -126,7 +126,7 @@ info("Testing findContactsGrid(...)") sim = deepcopy(sim_copy) sim.ocean = SeaIce.createRegularOceanGrid([4, 4, 2], [80., 80., 2.]) SeaIce.sortIceFloesInGrid!(sim, sim.ocean) -SeaIce.findContactsOceanGrid!(sim) +SeaIce.findContactsInGrid!(sim, sim.ocean) @test 2 == sim.ice_floes[1].contacts[1] for ic=2:32 @@ -145,7 +145,7 @@ sim = deepcopy(sim_copy) sim.ocean = SeaIce.createRegularOceanGrid([4, 4, 2], [80., 80., 2.]) sim.ice_floes[1].fixed = true SeaIce.sortIceFloesInGrid!(sim, sim.ocean) -SeaIce.findContactsOceanGrid!(sim) +SeaIce.findContactsInGrid!(sim, sim.ocean) @test 2 == sim.ice_floes[1].contacts[1] for ic=2:32 @@ -165,7 +165,7 @@ sim.ocean = SeaIce.createRegularOceanGrid([4, 4, 2], [80., 80., 2.]) sim.ice_floes[1].fixed = true sim.ice_floes[2].fixed = true SeaIce.sortIceFloesInGrid!(sim, sim.ocean) -SeaIce.findContactsOceanGrid!(sim) +SeaIce.findContactsInGrid!(sim, sim.ocean) for ic=1:32 @test 0 == sim.ice_floes[1].contacts[ic] diff --git a/test/grid.jl b/test/grid.jl @@ -152,3 +152,44 @@ ocean.u[2, 2, 1, 1] = 1.0 ocean.u[1, 2, 1, 1] = 1.0 ocean.v[:, :, 1, 1] = 0.0 @test SeaIce.curl(ocean, .5, .5, 1, 1, 1, 1) < 0. + +info("Testing atmosphere drag") +sim = SeaIce.createSimulation() +sim.atmosphere = SeaIce.createRegularAtmosphereGrid([4, 4, 2], [4., 4., 2.]) +atmosphere = SeaIce.createRegularAtmosphereGrid([4, 4, 2], [4., 4., 2.]) +sim.atmosphere.u[:,:,1,1] = 5. +SeaIce.addIceFloeCylindrical(sim, [2.5, 3.5], 1., 1., verbose=verbose) +SeaIce.addIceFloeCylindrical(sim, [2.6, 2.5], 1., 1., verbose=verbose) +SeaIce.sortIceFloesInGrid!(sim, sim.atmosphere, verbose=verbose) +sim.time = ocean.time[1] +SeaIce.addAtmosphereDrag!(sim) +@test sim.ice_floes[1].force[1] > 0. +@test sim.ice_floes[1].force[2] ≈ 0. +@test sim.ice_floes[2].force[1] > 0. +@test sim.ice_floes[2].force[2] ≈ 0. +sim.atmosphere.u[:,:,1,1] = -5. +sim.atmosphere.v[:,:,1,1] = 5. +SeaIce.addIceFloeCylindrical(sim, [2.5, 3.5], 1., 1., verbose=verbose) +SeaIce.addIceFloeCylindrical(sim, [2.6, 2.5], 1., 1., verbose=verbose) +SeaIce.sortIceFloesInGrid!(sim, sim.atmosphere, verbose=verbose) +sim.time = ocean.time[1] +SeaIce.addAtmosphereDrag!(sim) +@test sim.ice_floes[1].force[1] < 0. +@test sim.ice_floes[1].force[2] > 0. +@test sim.ice_floes[2].force[1] < 0. +@test sim.ice_floes[2].force[2] > 0. + +info("Testing curl function") +atmosphere.u[1, 1, 1, 1] = 1.0 +atmosphere.u[2, 1, 1, 1] = 1.0 +atmosphere.u[2, 2, 1, 1] = 0.0 +atmosphere.u[1, 2, 1, 1] = 0.0 +atmosphere.v[:, :, 1, 1] = 0.0 +@test SeaIce.curl(atmosphere, .5, .5, 1, 1, 1, 1) > 0. + +atmosphere.u[1, 1, 1, 1] = 0.0 +atmosphere.u[2, 1, 1, 1] = 0.0 +atmosphere.u[2, 2, 1, 1] = 1.0 +atmosphere.u[1, 2, 1, 1] = 1.0 +atmosphere.v[:, :, 1, 1] = 0.0 +@test SeaIce.curl(atmosphere, .5, .5, 1, 1, 1, 1) < 0. diff --git a/test/runtests.jl b/test/runtests.jl @@ -12,3 +12,4 @@ include("netcdf.jl") include("vtk.jl") include("grid.jl") include("ocean.jl") +include("atmosphere.jl")