Granular.jl

Julia package for granular dynamics simulation
git clone git://src.adamsgaard.dk/Granular.jl # fast
git clone https://src.adamsgaard.dk/Granular.jl.git # slow
Log | Files | Refs | README | LICENSE Back to index

getting_started.md (18044B)


      1 # Getting started
      2 In this section, it is assumed that [Julia](https://julialang.org) and 
      3 [Granular.jl](https://github.com/anders-dc/Granular.jl) have been successfully 
      4 installed.  If not, please consult the [Installation](@ref Installation) 
      5 section of this manual.  If you are new to the [Julia](https://julialang.org) 
      6 programming language, the official manual has a useful guide to [getting 
      7 started with 
      8 Julia](https://docs.julialang.org/en/latest/manual/getting-started/).
      9 
     10 In the following, two simple examples are presented using some of the core 
     11 functionality of Granular.jl.  For more examples, see the scripts in the 
     12 [examples/](https://github.com/anders-dc/Granular.jl/tree/master/examples) 
     13 directory.
     14 
     15 The relevant functions are all contained in the `Granular` module, which can be 
     16 imported with `import Granular`.  *Note:* As per Julia conventions, functions 
     17 that contain an exclamation mark (!) modify the values of the arguments.
     18 
     19 All of the functions called below are documented in the source code, and their 
     20 documentation can be found in the [Public API Index](@ref main-index) in the 
     21 online documentation, or simply from the Julia shell by typing `?<function 
     22 name>`.  An example:
     23 
     24 ```julia-repl
     25 julia> ?Granular.createSimulation
     26   createSimulation([id])
     27 
     28   Create a simulation object to contain all relevant variables such as temporal 
     29   parameters, fluid grids, grains, and contacts. The parameter id is used to 
     30   uniquely identify the simulation when it is written to disk.
     31 
     32   The function returns a Simulation object, which you can add grains to, e.g. 
     33   with addGrainCylindrical!.
     34 
     35      Optional argument
     36     ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
     37 
     38     •    id::String="unnamed": simulation identifying string.
     39 ```
     40 
     41 You can go through the examples below by typing in the lines starting with 
     42 `julia>` into the Julia interactive shell, which comes up when you start the 
     43 Julia app or run `julia` from the command line in a terminal.  Do not include 
     44 the `julia> ` part, just the remaining text of that line.
     45 
     46 Alternatively, you can create a Julia script with the file name extension 
     47 `.jl`.  This file should contains all of the relevant commands in succession, 
     48 which is useful for quickly repeating runs.  Julia scripts can be evaluated 
     49 form the command line using `julia <scriptname>.jl`.
     50 
     51 ## Collision between two particles
     52 For the first example (`examples/two-grains.jl`), we will create two grains and 
     53 make the first grain bump in to the second grain.
     54 
     55 As the first step, we import all the Granular.jl functionality:
     56 
     57 ```julia-repl
     58 julia> import Granular
     59 ```
     60 
     61 ### Simulation setup
     62 Next, we create our simulation object which holds all information related to 
     63 the simulated grains.  In this documentation, we will use the name `sim` for 
     64 the simulation object:
     65 
     66 ```julia-repl
     67 julia> sim = Granular.createSimulation(id="two-grains")
     68 Granular.Simulation("two-grains", 0, 0.0, -1.0, -1.0, -1.0, 0, 0.0, 
     69 Granular.GrainCylindrical[], Granular.Ocean(false, [0.0], [0.0], [0.0], [0.0], 
     70 [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], Array{Int64,1}[#undef], 1, 1, 
     71 1, 1), Granular.Atmosphere(false, [0.0], [0.0], [0.0], [0.0], [0.0], [0.0], 
     72 [0.0], [0.0], Array{Int64,1}[#undef], 1, 1, 1, 1, false), 16)
     73 ```
     74 
     75 After creating the simulation object `sim`, we will be presented with some 
     76 output about the default contents of the `sim` simulation object.  This is of 
     77 minor importance as of now, and can safely be ignored.
     78 
     79 In the above `createSimulation` call, the `id` argument is optional. It is used 
     80 to name simulation output files that are written to the disk.
     81 
     82 ### Adding grains one by one
     83 We have now created a simulation object `sim`, which will be used during all of 
     84 the following commands.  Next, we can add grains to this object.  The first 
     85 grain is cylinder shaped, placed at the x-y position (0,0) m.  It has a radius 
     86 of 0.1 m, and has a thickness of 0.05 m.  As this call modifies the `sim` 
     87 object, the function contains an exclamation mark (!).  For further information 
     88 regarding this call, see the reference to [`addGrainCylindrical!`](@ref), found 
     89 in the [Public API documentation](@ref).
     90 
     91 ```julia-repl
     92 julia> Granular.addGrainCylindrical!(sim, [0.0, 0.0], 0.1, 0.05)
     93 INFO: Added Grain 1
     94 ```
     95 
     96 Let's add another grain, placed at some distance from the first grain:
     97 
     98 ```julia-repl
     99 julia> Granular.addGrainCylindrical!(sim, [0.5, 0.0], 0.1, 0.05)
    100 INFO: Added Grain 2
    101 ```
    102 
    103 We now want to prescribe a linear (not rotational or angular) velocity to the 
    104 first grain to make it bump into the second grain.
    105 
    106 The simulation object `sim` contains an array of all grains that are added to 
    107 it.  We can directly inspect the grains and their values from the simulation 
    108 object.  Let's take a look at the default value of the linear velocity, called 
    109 `lin_vel`:
    110 
    111 ```julia-repl
    112 julia> sim.grains[1].lin_vel
    113 2-element Array{Float64, 1}:
    114  0.0
    115  0.0
    116 ```
    117 
    118 The default value is a (0,0) vector, which means that it is not moving in 
    119 space.  With a similar call, we can modify the properties of the first grain 
    120 directly and prescribe a velocity to it:
    121 
    122 ```julia-repl
    123 julia> sim.grains[1].lin_vel = [1.0, 0.0]
    124 2-element Array{Float64, 1}:
    125  1.0
    126  0.0
    127 ```
    128 
    129 The first grain (index 1 in `sim.grains`) now has a positive velocity along `x` 
    130 with the value of 1.0 meter per second.
    131 
    132 ### Setting temporal parameters for the simulation
    133 Before we can start the simulation we need to set some more parameters vital to 
    134 the simulation, like what time step to use, how often to write output files to 
    135 the disk, and for how long to run the simulation.  To set the computational 
    136 time step, we call the following:
    137 
    138 ```julia-repl
    139 julia> Granular.setTimeStep!(sim)
    140 INFO: Time step length t=8.478741928617433e-5 s
    141 ```
    142 
    143 A suitable time step is automatically determined based on the size and elastic 
    144 properties of the grain.
    145 
    146 The two grains are 0.3 meters apart, as the centers are placed 0.5 meter from 
    147 each other and each grain has a radius of 0.1 m.  With a linear velocity of 1.0 
    148 m/s, the collision should occur after 0.3 seconds.  For that reason it seems 
    149 reasonable to run the simulation for a total of 1.0 seconds.  We choose to 
    150 produce an output file every 0.05 seconds, but this can be tweaked to taste.  
    151 We will later use the produced output files for visualization purposes.
    152 
    153 ```julia-repl
    154 julia> Granular.setOutputFileInterval!(sim, 0.05)
    155 
    156 julia> Granular.setTotalTime!(sim, 1.0)
    157 ```
    158 
    159 ### Running the simulation
    160 We are now ready to run the simulation.  For illustrative purposes, let us 
    161 compare the kinetic energy in the granular system before and after the 
    162 collision.  For now, we compute the initial value using the following call:
    163 
    164 ```julia-repl
    165 julia> Granular.totalGrainKineticTranslationalEnergy(sim)
    166 0.7335618846132168
    167 ```
    168 
    169 The above value is the total translational (not angular) kinetic energy in 
    170 Joules before the simulation is started.
    171 
    172 We have two choices for running the simulation; we can either run the entire 
    173 simulation length with a single call, which steps time until the total time is 
    174 reached and generates output files along the way.  Alternatively, we can run 
    175 the simulation for a single time step a time, and inspect the progress or do 
    176 other modifications along the way.
    177 
    178 Here, we will run the entire simulation in one go, and afterwards visualize the 
    179 grains from their output files using ParaView.
    180 
    181 ```julia-repl
    182 julia> Granular.run!(sim)
    183 
    184 INFO: Output file: ./two-grains/two-grains.grains.1.vtu
    185 INFO: wrote status to ./two-grains/two-grains.status.txt
    186   t = 0.04239370964308682/1.0 s
    187 INFO: Output file: ./two-grains/two-grains.grains.2.vtu
    188 INFO: wrote status to ./two-grains/two-grains.status.txt
    189 
    190 ...
    191 
    192 INFO: Output file: ./two-grains/two-grains.grains.20.vtu
    193 INFO: wrote status to ./two-grains/two-grains.status.txt
    194   t = 0.9920128056483803/1.0 s
    195 INFO: ./two-grains/two-grains.py written, execute with 'pvpython /Users/ad/code/Granular-ext/two-grains/two-grains.py'
    196 INFO: wrote status to ./two-grains/two-grains.status.txt
    197   t = 1.0000676104805686/1.0 s
    198 ```
    199 
    200 The code informs us of the simulation progress along the way.  It also and 
    201 notifies us as output files are generated.  This output can be disabled by 
    202 passing `verbose=false` to the `run!()` command.  Finally, the code tells us 
    203 that it has generated a ParaView python file for visualization, called 
    204 `two-grains.py`, located in the `two-grains/` directory.
    205 
    206 We are interested in getting an immediate idea of how the collision went, 
    207 before going further.  We can print the new velocities with the following 
    208 commands:
    209 
    210 ```julia-repl
    211 julia> sim.grains[1].lin_vel
    212 2-element Array{Float64, 1}:
    213  7.58343e-5
    214  0.0
    215 
    216 julia> sim.grains[2].lin_vel
    217 2-element Array{Float64, 1}:
    218  0.999924
    219  0.0
    220 ```
    221 
    222 The first grain has transferred effectively all of its kinetic energy to the 
    223 second grain during the cause of the simulation.  The total kinetic energy now 
    224 is the following:
    225 
    226 ```julia-repl
    227 julia> Granular.totalGrainKineticTranslationalEnergy(sim)
    228 0.7334506347624973
    229 ```
    230 
    231 The before and after values for total kinetic energy are reasonably close (to 
    232 less than 0.1 percent), which is what can be expected given the computation 
    233 accuracy of the algorithm.
    234 
    235 ### Visualizing the output
    236 To visualize the output we open [ParaView](https://www.paraview.org).  The 
    237 output files of the simulation are written using the VTK (visualization 
    238 toolkit) format, which is natively supported by ParaView.
    239 
    240 While the `.vtu` files produced during the simulation can be opened with 
    241 ParaView and visualized manually using *Glyph* filters, the simplest and 
    242 fastest way to visualize the data is to use the Python script generated for the 
    243 simulation by Granular.jl.
    244 
    245 Open ParaView and open the *Python Shell*, found under the menu *Tools > Python 
    246 Shell*.  In the pop-up dialog we select *Run Script*, which opens yet another 
    247 dialog prompting us to locate the visualization script 
    248 (`two-grains/two-grains.py` in our example).  We locate this file, which is 
    249 placed under the directory from where we launched the `julia` session with the 
    250 commands above.  If you are not sure what the current working directory for the 
    251 Julia session is, it can be displayed with the command `pwd()` in the Julia 
    252 shell.
    253 
    254 After selecting the `two-grains/two-grains.py` script, we can close the *Python 
    255 Shell* window to inspect our simulation visually.  Press the *Play* symbol in 
    256 the top toolbar, and see what happens.
    257 
    258 By default, the grains are colored according to their size.  Alternatively, you 
    259 can color the grains using different parameters, such as linear velocity, 
    260 number of contacts, etc.  These parameters can be selected by changing the 
    261 chosen field under the *Glyph1* object in the *Pipeline Browser* on the left, 
    262 and by selecting a parameter for *Coloring*.  Press the *Apply* button at the 
    263 top of  the panel on the left to see the changes in effect.
    264 
    265 **Tip:** If you have the command `pvpython` (ParaView Python) is available from 
    266 the command line, you can visualize the simulation directly from the Julia 
    267 shell without entering ParaView by the command `Granular.render(sim)`.  The 
    268 program `pvpython` is included in the ParaView download, and is in the macOS 
    269 application bundle located in 
    270 `/Applications/Paraview-5.4.0.app/Contents/bin/pvpython`.  Furthermore, if you 
    271 have the `convert` command from ImageMagick installed (`brew install 
    272 imagemagick` on macOS), the output images will be merged into an animated GIF.
    273 
    274 ### Exercises
    275 To gain more familiarity with the simulation procedure, I suggest experimenting 
    276 with the following exercises.  *Tip:* You can rerun an experiment after 
    277 changing one or more parameters by calling `Granular.resetTime!(sim); 
    278 Granular.run!(sim)`.  Changes in grain size, grain mass, or elastic properties 
    279 require a recomputation of the numerical time step 
    280 (`Granular.setTimeStep!(sim)`) before calling `Granular.run!(sim)`.  The output 
    281 files will be overwritten, and changes will be immediately available in 
    282 ParaView.
    283 
    284 - What effect does the grain size have on the time step?
    285 - Try to make an oblique collision by placing one of the grains at a different 
    286     `y` position.
    287 - What happens if the second grain is set to be fixed in space 
    288     (`sim.grains[2].fixed = true`)?
    289 - How is the relationship between total kinetic energy before and after 
    290     affected by the choice of time step length?  Try setting different time 
    291     step values, e.g. with `sim.time_step = 0.1234` and rerun the simulation.
    292 
    293 ## Sedimentation of grains
    294 Grains are known to settle under gravitation in water according to *Stoke's 
    295 law*, where resistive drag acts opposite of gravity and with a magnitude 
    296 according to the square root of velocity difference between water and grain.
    297 Granular.jl offers simple fluid grids with prescribed velocity fields, and the 
    298 grains are met with drag in this grid.
    299 
    300 In this example (`examples/sedimentation.jl`) we will initialize grains with a 
    301 range of grain sizes in a loose configuration, add gravity and a surrounding 
    302 fluid grid, and let the grains settle towards the bottom.
    303 
    304 As in the previous example, we start by creating a fluid grid:
    305 
    306 ```julia-repl
    307 julia> import Granular
    308 julia> sim = Granular.createSimulation(id="sedimentation")
    309 ```
    310 
    311 ### Creating a pseudo-random grain packing
    312 Instead of manually adding grains one by one, we can use the 
    313 `regularPacking!()` function to add a regular grid of random-sized grains to 
    314 the simulation.  Below, we specify that we want the grid of grains to be 7 
    315 grains wide along x, and 25 grains tall along y.  We also specify the grain 
    316 radii to fall between 0.02 and 0.2 m.  The sizes will be drawn from a power-law 
    317 distribution by default.
    318 
    319 ```julia-repl
    320 julia> Granular.regularPacking!(sim, [7, 25], 0.02, 0.2)
    321 ```
    322 
    323 Since we haven't explicitly set the grain sizes for this example, we can 
    324 inspect the values by plotting a histogram of sizes (only works if the `PyPlot` 
    325 package is installed with `Pkg.add("PyPlot")`):
    326 
    327 ```julia-repl
    328 julia> Granular.plotGrainSizeDistribution(sim)
    329 INFO: sedimentation-grain-size-distribution.png
    330 ```
    331 
    332 The output informs us that we have the plot saved as an image with the file 
    333 name `sedimentation-grain-size-distribution.png`.
    334 
    335 ### Creating a fluid grid
    336 We can now create a fluid (ocean) grid spanning the extent of the grains 
    337 created above:
    338 
    339 ```julia-repl
    340 julia> Granular.fitGridToGrains!(sim, sim.ocean)
    341 INFO: Created regular Granular.Ocean grid from [0.06382302477946442, 
    342 0.03387419706945263] to [3.0386621000253293, 10.87955941983313] with a cell 
    343 size of 0.3862075959573571 ([7, 28]).
    344 ```
    345 
    346 The code informs us of the number of grid cells in each dimension (7 by 28 
    347 cells), and the edge positions (x = 0.0638 to 3.04 m, y = 0.0339 to 10.9 m).
    348 
    349 We want the boundaries of the above grid to be impermeable for the grains, so 
    350 they stack up at the bottom.  Granular.jl acknowledges the boundary types with 
    351 a confirmation message:
    352 
    353 ```julia-repl
    354 julia> Granular.setGridBoundaryConditions!(sim.ocean, "impermeable")
    355 West  (-x): impermeable (3)
    356 East  (+x): impermeable (3)
    357 South (-y): impermeable (3)
    358 North (+y): impermeable (3)
    359 ```
    360 
    361 ### Adding gravitational acceleration
    362 If we start the simulation now nothing would happen as gravity is disabled by 
    363 default.  We can enable gravitational acceleration as a constant body force for 
    364 each grain (`Force = mass * acceleration`):
    365 
    366 ```julia-repl
    367 julia> g = [0.0, -9.8];
    368 julia> for grain in sim.grains
    369        Granular.addBodyForce!(grain, grain.mass*g)
    370        end
    371 ```
    372 
    373 ### Setting temporal parameters
    374 As before, we ask the code to select a suitable computational time step based 
    375 on grain sizes and properties:
    376 
    377 ```julia-repl
    378 julia> Granular.setTimeStep!(sim)
    379 INFO: Time step length t=1.6995699879716792e-5 s
    380 ```
    381 
    382 We also, as before, set the total simulation time as well as the output file 
    383 interval:
    384 
    385 ```julia-repl
    386 julia> Granular.setTotalTime!(sim, 10.0)
    387 julia> Granular.setOutputFileInterval!(sim, 0.2)
    388 ```
    389 
    390 ### Running the simulation
    391 We are now ready to run the simulation:
    392 
    393 ```julia-repl
    394 julia> Granular.run!(sim)
    395 INFO: Output file: ./sedimentation/sedimentation.grains.1.vtu
    396 INFO: Output file: ./sedimentation/sedimentation.ocean.1.vts
    397 INFO: wrote status to ./sedimentation/sedimentation.status.txt
    398   t = 0.19884968859273294/10.0 s
    399 INFO: Output file: ./sedimentation/sedimentation.grains.2.vtu
    400 INFO: Output file: ./sedimentation/sedimentation.ocean.2.vts
    401 INFO: wrote status to ./sedimentation/sedimentation.status.txt
    402   t = 0.3993989471735396/10.0 s
    403 
    404 ...
    405 
    406 INFO: Output file: ./sedimentation/sedimentation.grains.50.vtu
    407 INFO: Output file: ./sedimentation/sedimentation.ocean.50.vts
    408 INFO: wrote status to ./sedimentation/sedimentation.status.txt
    409   t = 9.998435334626701/10.0 s
    410 INFO: ./sedimentation/sedimentation.py written, execute with 'pvpython /Users/ad/code/Granular-ext/examples/sedimentation/sedimentation.py'
    411 INFO: wrote status to ./sedimentation/sedimentation.status.txt
    412   t = 10.00001593471549/10.0 s
    413 ```
    414 
    415 The output can be plotted in ParaView as described in the `two-grain` example 
    416 above, or, if `pvpython` is available from the command line, directly from 
    417 Julia with the following command:
    418 
    419 ```julia-repl
    420 julia> Granular.render(sim, trim=false)
    421 ```
    422 
    423 ### Exercises
    424 - How are the granular contact pressures distributed in the final result?  You 
    425     can visualize this by selecting "Contact Pressure [Pa]" in the *Coloring* 
    426     field inside ParaView.
    427 - Try running the above example, but without fluid drag.  Disable the drag by 
    428     including the call `Granlar.disableOceanDrag!(grain)` in the `for` loop 
    429     where gravitational acceleration is set for each grain.
    430 - How does the range of grain sizes affect the result?  Try making all grains 
    431     bigger or smaller.
    432 - How is the model performance effected if the grain-size distribution is 
    433     wide or narrow?
    434 - Create a landslide by turning the gravitational acceleration vector (set the 
    435     `y` component to a non-zero value, and setting the side boundaries to be 
    436     periodic with `Granular.setGridBoundaryConditions!(sim.ocean, "periodic", 
    437     "east west")`.