Thermodynamics of bouncing grains & Büttiker-Landauer motor
Authors/Creators
Description
Experiments
Sand grains bouncing over a vibrated plate diffuse across the plate's surface. Their diffusivity depends on the plate's surface properties, on the frequency and amplitude of its vibration, and on the grains' shape. This phenomenon generates the well-known Chladni figure.
This archive contains the trajectories of bouncing grains recorded during the experiments presented in two articles:
- A granular Büttiker-Landauer motor, O. Devauchelle, P. Popović, P. Szymczak, A. Abramian & A. Lazarus, PRL (2026), DOI: 10.1103/1ggv-8f83;
- Thermodynamics of bouncing grains, O. Devauchelle, P. Popović, P. Szymczak, A. Abramian & A. Lazarus (2026), https://doi.org/10.1103/zlsc-w7m4.
File content
Codes
Python codes used to produce and analyse the trajectories.
Processed data
- Trajectory files
*.traj. Text files intended to be read with the BrownTrack library. See code sample below for illustration. - Background image
background.jpg. - Geometry of the setup in the corresponding movie frame
domains.json. - Parameter files
*.json.
Raw data
Data files created during the experiment.
- Tilt of the vibrating plate
angle_*.csv. - Metadata of all movies in specific run
movie_metadata_*.json. - Raw parameter file
parameters_*.json.
Names of experimental runs
The runs are referred to with letters in the articles, according to the following table.
|
Run name |
File |
|
A |
big_grains_40Hz |
|
B |
siloxane_multiple_quick_3 |
|
C |
siloxane_big_grains_large_swing |
|
D |
siloxane_small_low_freq |
|
E |
siloxane_multiple_quick |
|
F |
siloxane_multiple_quick_2 |
|
G |
siloxane_small_grains |
|
H |
siloxane_small_large_amp |
|
I |
diffusivity_hard_30Hz_1_5_mm |
|
J |
diffusivity_soft_30Hz_2_5_4_mm |
|
K |
diffusivity_hard_30Hz_2_5_4_mm |
|
L |
diffusivity_soft_30Hz_6_mm |
|
M |
diffusivity_hard_30Hz_6_mm |
|
N |
diffusivity_soft_30Hz_1_5_mm |
Example
The code sample below loads all the trajectories of a specific experimental movie, and plots some of them.
Methods
####################################
#
# A granular Büttiker-Landauer motor, O. Devauchelle, P. Popović, P. Szymczak, A. Abramian & A. Lazarus (2026)
# Thermodynamics of bouncing grains, O. Devauchelle, P. Popović, P. Szymczak, A. Abramian & A. Lazarus (2026)
# https://www.doi.org/10.5281/zenodo.18862698
# https://github.com/odevauchelle/BrownTrack
#
####################################
from pylab import *
from glob import glob
import json
sys.path.append('/home/olivier/git/BrownTrack/')
import BrownTrack as BT
####################
#
# parameters & data
#
###################
file_to_letter = {'big_grains_40Hz': 'A', 'siloxane_multiple_quick_3': 'B', 'siloxane_big_grains_large_swing': 'C', 'siloxane_small_low_freq': 'D', 'siloxane_multiple_quick': 'E', 'siloxane_multiple_quick_2': 'F', 'siloxane_small_grains': 'G', 'siloxane_small_large_amp': 'H', 'diffusivity_hard_30Hz_1_5_mm': 'I', 'diffusivity_soft_30Hz_2_5_4_mm': 'J', 'diffusivity_hard_30Hz_2_5_4_mm': 'K', 'diffusivity_soft_30Hz_6_mm': 'L', 'diffusivity_hard_30Hz_6_mm': 'M', 'diffusivity_soft_30Hz_1_5_mm': 'N'}
experiment = 'siloxane_small_low_freq'
movie_index = 4
data_path = '/home/olivier/no_backup/ganzeville/Bureau/bouncing_grains/BL_ratchet_paper_data/BLR_experiment/'
experiment_folder = data_path + experiment
processed_data_path = experiment_folder + '/processed_data/'
with open( processed_data_path + 'parameters.json' ) as the_file :
p = json.load( the_file )
with open( processed_data_path + 'domains.json' ) as the_file :
domains_data = json.load( the_file )
###############################
#
# show domains
#
###############################
fig = figure( figsize = (6,6), layout = 'tight' )
ax_phys = gca()
domains = {}
for name, domain in domains_data['domains'].items() :
patch_style = dict( facecolor = 'none', linestyle = '--' )
if name == 'main' :
patch_style['linestyle'] = '-'
domains[name] = BT.domain( **domain )
ax_phys.add_patch( domains[name].get_patch( label = name.capitalize(), **patch_style ) )
radius = domains['main'].boundary['radius']
disk_diameter = 8 # cm
disk_diameter *= 1e-2 #m
meters_per_pixel = disk_diameter/radius
###################
#
# draw scale bar
#
###################
px_per_cm = 1e-2/meters_per_pixel
bar_size = 1 #cm
bar_pos = array( domains['main'].boundary['xy'] ) + .92*array([1,-1])*radius
bar_points = array( [ bar_pos - array([bar_size*px_per_cm, 0]), bar_pos + array([bar_size*px_per_cm, 0]) ] )
bar_color = 'k'
ax_phys.plot( *bar_points.T, '-|', color = bar_color )
ax_phys.text( *( mean(bar_points, axis = 0) + array([0, 0]) ), str(bar_size) + r'$\,$cm' + '\n', color = bar_color, ha = 'center' )
####################
#
# plot trajectories
#
####################
def is_acceptable( traj, **criteria ) :
output = True
try :
output = output and len(traj.x) > criteria['min_length']
except :
pass
try :
output = output and norm( array( [ ( traj.x[-1] - traj.x[0] ), ( traj.y[-1] - traj.y[0] ) ] ) ) > criteria['min_displacement']*p['grain_size']
except :
pass
return output
data_file = glob( processed_data_path + 'bunch_*.traj' )[movie_index]
with open( data_file ) as the_file :
p_traj = json.loads( the_file.readline() )
traj_movie_name = p_traj['movie_file'].replace( 'diffusion_', '' )
trajectories = BT.load_trajectories( the_file )
plotted_length = 0
max_plotted_length = 3000
for traj in trajectories[::-1] :
if is_acceptable( traj, min_length = 5, min_displacement = 10 ) :
if plotted_length < max_plotted_length : # plot some trajectories
step = 1
i_max = max_plotted_length - plotted_length
ax_phys.plot( traj.x[:i_max], traj.y[:i_max], alpha = .5 )
plotted_length += len(traj.x)
index = arange( len( traj.x ) )
####################
#
# adjust plot
#
####################
ax_phys.axis('scaled')
ax_phys.set_xticks([])
ax_phys.set_yticks([])
ax_phys.axis('off')
ax_phys.set_title( 'Run ' + file_to_letter[experiment] )
fig.savefig('trajectories.pdf')
show()
Files
Additional details
Software
- Repository URL
- https://github.com/odevauchelle/BrownTrack
- Programming language
- Python