Version: Pizza3 v.0.99
Maintained by: INRAE\olivier.vitrac@agroparistech.fr
-
misc
-
post
- Billy_results_template
- Billy_results_template_PBC
- Billy_results_template_PBC_alt
- Billy_results_template_PBC_relative_velocity
- Billy_results_template_cross_section
- Bily_suspension_interpretation
- KE_t
- MDunidrnd
- PBCgrid
- PBCgridshift
- PBCimages
- PBCimages0
- PBCimagesshift
- PBCincell
- PBCoutcell
- SetQuiverColor
- SetQuiverLength
- Yao_template
- add_bead
- arrow
- buildVerletList
- buildVerletList3
- checkfiles
- color_line3
- curve2tangent
- defgradSPH
- dispb
- dispf
- example1
- example2
- example2bis
- example3
- explore
- fileinfo
- fillstreamline2
- fitCircleFromPoints
- forceHertz
- forceHertzAB
- forceLandshoff
- hourglassSPH
- interp2SPH
- interp2SPHVerlet
- interp3SPH
- interp3SPHVerlet
- interp3cauchy
- kernelSPH
- lamdumpread2
- lastdir
- nearestpoint
- packSPH
- packing
- packing_WJbranch
- particle_flux
- partitionVerletList
- plot3D
- quiverc
- rootdir
- selfVerletList
- shapeSPH
- trajunwrap
- unwrapPBC
- updateVerletList
- wallstress
- yao_initialization
- yao_sedimentation
- yao_stress
- yao_stress_vertical_xz
- yao_stress_vertical_yz
-
utils/obsolete
Welcome to Pizza3 Matlab Documentation
Select a function in the left menu to view its documentation.
When no function is selected, you see this welcome page.
Generated on: 2024-12-12 12:43:06Code to explain the displacement of SPH particles (amorphous) along a rigid wall made with beads of the same size (organized)
INRAE\Olivier Vitrac, Han Chen - 2023-02-23
%% Code to explain the displacement of SPH particles (amorphous) along a rigid wall made with beads of the same size (organized)
% INRAE\Olivier Vitrac, Han Chen - 2023-02-23
% [ SYNOPSIS ] This code simulates the displacement of spherical particles in a rigid wall made with beads of the
% same size, by calculating the normal force and contact radius between the particles % and the wall. The contact
% depth between each pair of particles and the wall is calculated using the Hertz model.
% [ DESCRIPTION ] The code simulates the displacement of amorphous fluid particles along a
% rigid wall made of beads of the same size that are organized in a fixed pattern. The code
% defines the initial configuration of the system, with the wall and fluid beads arranged in their
% respective layers. The fluid beads are then redistributed with some randomness along the x-axis.
% The code then finds the closest wall bead to each fluid bead and calculates the contraction
% required for the fluid bead to move towards the wall bead. The final configuration of the fluid
% beads is then computed with the required contraction, and the physical interpretation of the
% contraction is calculated using Hertz contact mechanics. The dynamic configuration is then plotted,
% showing the motion of the fluid beads towards the wall beads, with arrows indicating the force
% direction and magnitude. The code also allows the user to record a video of the dynamic configuration.
%% layer definitions and initial configuration
% The code defines the properties of the wall and the fluid particles,
% including their size, number, and position. The particles are initially
% randomly distributed along the x-axis, and then redistributed to the
% nearest wall bead with some randomness.
% layer plot
layerplot = @(X) viscircles([X.x X.y],X.R*ones(X.n,1),'color',X.color);
% bead size
r = 0.5;
wall = struct( ...
'R',r, ...
'n',14,...
'y',-r,...
'dx',1e-2,...
'dy',0,...
'color',rgb('DarkBlue') ...
);
fluid = struct( ...
'R',r,...
'n',10,...
'y',+r,...
'dx',1e-2,...
'dy',1e-2,...
'color',rgb('Gold') ...
);
wall.x = linspace(-(wall.dx+wall.R*2)*(wall.n-1)/2,(wall.dx+wall.R*2)*(wall.n-1)/2,wall.n)';
fluid.x = linspace(-(fluid.dx+fluid.R*2)*(fluid.n-1)/2,(fluid.dx+fluid.R*2)*(fluid.n-1)/2,fluid.n)';
wall.y = ones(wall.n,1) * (wall.y + wall.dy);
fluid.y = ones(fluid.n,1) * (fluid.y + fluid.dy);
% redistribute with some randomness fluid beads along x
x0 = fluid.x(1);
dx = diff(fluid.x);
dxrandom = abs(randn(fluid.n,1))*fluid.R*0.5;
x = x0+[cumsum([0;dx]+dxrandom)];
fluid.x = x - mean(x);
% find the closest wall bead and calculate the final configuration (used to get fmax)
distance = @(i) sqrt((wall.x-fluid.x(i)).^2 + (wall.y-fluid.y(i)).^2);
iclosest = arrayfun( @(i) find( distance(i)==min(distance(i)),1),1:fluid.n );
distance2closest = sqrt((wall.x(iclosest)-fluid.x).^2 + (wall.y(iclosest)-fluid.y).^2);
dtarget = wall.R*2*(1-abs(randn(fluid.n,1)*0.02));
contraction_required = 1-dtarget./distance2closest;
final = fluid;
final.x = final.x + (wall.x(iclosest)-final.x) .* contraction_required;
final.y = final.y + (wall.y(iclosest)-final.y) .* contraction_required;
% Physical intepretation
% The force required to maintain the contact between the particles and the wall is
% calculated based on the Hertz model, and the dynamic configuration of the particles
% is then calculated using the applied load.
distance2closest = sqrt((wall.x(iclosest)-final.x).^2 + (wall.y(iclosest)-final.y).^2);
E = 1e5;
rcut = wall.R + final.R;
direction = - [wall.x(iclosest)-final.x (wall.y(iclosest)-final.y)]./distance2closest(:,[1 1]);
f = E * sqrt((rcut-distance2closest)*wall.R*final.R/rcut);
f(distance2closest>rcut) = 0;
fmax = max(f);
% dynamic configuration
nt = 200;
t = linspace(0,1,nt);
clf, hold on, axis equal
layerplot(wall)
[hp,ha] = deal([]);
axis off
RECORD = true; % set it to true to record a video
% The code then iterates over a certain number of time steps,calculating
% the dynamic configuration of the particles in each step and plotting their
% positions and contact depths using the Hertz model.
for it = 1:nt
layerplot(fluid)
config = fluid;
config.x = config.x + (wall.x(iclosest)-config.x) .* contraction_required*t(it);
config.y = config.y + (wall.y(iclosest)-config.y) .* contraction_required*t(it);
% Physical intepretation
distance2closest = sqrt((wall.x(iclosest)-config.x).^2 + (wall.y(iclosest)-config.y).^2);
min(distance2closest)
rcut = wall.R + config.R;
direction = - [wall.x(iclosest)-config.x (wall.y(iclosest)-config.y)]./distance2closest(:,[1 1]);
f = E * sqrt((rcut-distance2closest)*wall.R*config.R/rcut);
f(distance2closest>rcut) = 0;
fmax = max(f);
fscale = 2*r/fmax;
start = [config.x config.y];
stop = start + direction.*f*fscale;
start(f==0,:)=[];
stop(f==0,:)=[];
% plot
if ~isempty(hp), delete(hp); end
if ~isempty(ha), delete(ha); end
hp = layerplot(config);
if any(start)
ha = [
arrow(start,stop,'length',8,'BaseAngle',60,'Linewidth',2,'color',rgb('ForestGreen'),'Edgecolor',rgb('ForestGreen'))
arrow(start,[stop(:,1) start(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'))
arrow(start,[start(:,1) stop(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'),'EdgeColor',rgb('Tomato'))
];
else
ha = [];
end
drawnow
% record video
if RECORD
gif_add_frame(gca,'hertz.gif',15);
end
end
%% Template
% A similar code template is also provided at the end, which calculates the Hertz contact depth
% between two series of parallel spheres aligned along the x-axis, with a fixed distance between
% adjacent spheres. It calculates the normal force and contact radius based on the material
% properties of the spheres, and then loops over all possible pairs of spheres to calculate
% the Hertz contact depth between them. Finally, it plots the sphere positions and contact depths.
% % This code assumes that the two series of parallel spheres are aligned
% % along the x-axis, with a fixed distance between adjacent spheres.
% % It calculates the normal force and contact radius based on the material
% % properties of the spheres, and then loops over all possible pairs of spheres
% % to calculate the Hertz contact depth between them.
% % Finally, it plots the sphere positions and contact depths.
% % You can modify the code to suit your specific geometry and material properties.
%
% % Define material properties
% E = 2.1e11; % Young's modulus in Pa
% v = 0.3; % Poisson's ratio
% R = 0.5e-3; % Sphere radius in m
%
% % Define sphere geometry and spacing
% n1 = 10; % Number of spheres in the first row
% n2 = 10; % Number of spheres in the second row
% d = 1e-3; % Distance between spheres in m
%
% % Calculate normal force and contact radius
% P = 1; % Applied load in N
% a = (3*P*(1-v^2)/(4*E))^(1/3); % Contact radius in m
% F = 4/3*E*sqrt(R)*a^(3/2); % Normal force in N
%
% % Define sphere positions
% x1 = linspace(-d*(n1-1)/2,d*(n1-1)/2,n1);
% x2 = linspace(-d*(n2-1)/2,d*(n2-1)/2,n2)+1e-4;
% y1 = -R;
% y2 = 0.98*R;
%
% % Initialize contact matrix
% C = zeros(n1,n2);
%
% % Loop over all sphere pairs and calculate contact
% for i = 1:n1
% for j = 1:n2
% dx = x2(j) - x1(i);
% if abs(dx) > d
% % Spheres are too far apart to be in contact
% continue;
% end
% r = sqrt( (x2(j) - x1(i))^2 + (y2 - y1)^2 );
% if r >= 2*R
% % Spheres are not in contact
% continue
% end
% C(i,j) = a - sqrt((2*R-r)*r); % Hertz contact depth in m
% end
% end
%
% %% Plot sphere positions and contact depths
% figure, hold on
% for i = 1:n1
% viscircles([x1(i) y1],R,'color','b');
% for j = 1:n2
% viscircles([x2(j) y2],R-C(i,j),'color','r');
% if C(i,j) > 0
% plot([x1(i),x2(j)],[y1+R,y1+R-C(i,j)],'k');
% end
% end
% end
% axis equal
Code to illustrate the developement Landshoff forces between two horizontal layers of layer particles
translating respectively to each other with a velocity difference
%% Code to illustrate the developement Landshoff forces between two horizontal layers of layer particles
% translating respectively to each other with a velocity difference
% INRAE\Olivier Vitrac, Han Chen - 2023-02-23
% [ SYNOPSIS ] This code simulates the displacement of two layers of particles
% in a fluid using smoothed particle hydrodynamics (SPH). The two layers are
% represented by the "fixed" and "movable" structures, which contain information
% about the size, number, position, and velocity of the particles in each layer.
% [ DESCRIPTION ] The code simulates the movement of two layers of particles in a fluid
% using smoothed particle hydrodynamics (SPH). The particles in the two layers are
% represented by the "fixed" and "movable" structures, which contain information about
% the size, number, position, and velocity of the particles in each layer. The Landshoff
% forces between particles in the two layers are calculated using the kernel function and
% SPH parameters, and the positions of the particles in the "movable" layer are adjusted
% to be as close as possible to the particles in the "fixed" layer. The forces are plotted
% as arrows on the particle plot using the "arrow" function, and the plot is updated with
% each iteration. The code also includes options for recording the plot as a GIF.
%% The code begins by defining the "layerplot" function, which creates a scatter plot of the particles
% in a layer using circles with a radius equal to the particle size.
% The particle size is set to 0.5 units for both layers, and the "fixed" layer contains 14 particles,
% while the "movable" layer contains 10 particles. The particles in the "fixed" layer are arbitrarily
% fixed in place, while the particles in the "movable" layer are free to move.
% layer plot
layerplot = @(X) viscircles([X.x X.y],X.R*ones(X.n,1),'color',X.color);
% bead size
r = 0.5;
fixed = struct( ...these particles are arbitrarily fixed
'R',r, ...
'n',14,...
'y',-r,...
'dx',1e-2,...
'dy',0,...
'color',rgb('DodgerBlue'),...
'id',0 ...
);
movable = struct( ... movable particles
'R',r,...
'n',10,...
'y',+r,...
'dx',1e-2,...
'dy',1e-2,...
'color',rgb('DeepSkyBlue'), ...
'id',1 ...
);
% The positions of the particles in each layer are initiallyset up in a linear array along the x-axis,
% with some randomness added to the spacing between particles.
% The positions of the particles in the "fixed" layer are then shifted along the y-axis
% by a fixed amount, while the positions of the particles in the "movable" layer are shifted
% by a smaller amount along both the x- and y-axes.
fixed.x = linspace(-(fixed.dx+fixed.R*2)*(fixed.n-1)/2,(fixed.dx+fixed.R*2)*(fixed.n-1)/2,fixed.n)';
movable.x = linspace(-(movable.dx+movable.R*2)*(movable.n-1)/2,(movable.dx+movable.R*2)*(movable.n-1)/2,movable.n)';
fixed.y = ones(fixed.n,1) * (fixed.y + fixed.dy);
movable.y = ones(movable.n,1) * (movable.y + movable.dy);
% redistribute with some randomness fixed beads along x
x0 = fixed.x(1);
dx = diff(fixed.x);
dxrandom = abs(randn(fixed.n,1))*fixed.R*0.25;
x = x0+[cumsum([0;dx]+dxrandom)];
fixed.x = x - mean(x);
% redistribute with some randomness top beads along x
x0 = movable.x(1);
dx = diff(movable.x);
dxrandom = abs(randn(movable.n,1))*movable.R*0.4;
x = x0+[cumsum([0;dx]+dxrandom)];
movable.x = x - x(1) + mean(fixed.x(1:2));
% Next, the kernel function and SPH parameters are defined.
% The kernel function is used to calculate the smoothing function
% for the SPH method, and the parameters include the smoothing length (h),
% the density (rho), and the coefficients (c0 and q1) used in the Landshoff force calculation.
h = 2 * r;
dWdr = @(r) (r% initial position
v = 0.1; % velocity (arbitrary units)
dt = 0.1;
config = movable;
clf, hold on, axis equal
layerplot(fixed)
[hp,ha] = deal([]);
axis off
RECORD = true; % set it to true to record a video
% The code then enters a loop that simulates the movement of the particles over time.
% In each iteration, the position of the "movable" layer is shifted by a fixed amount
% along the x-axis, and the particles are then repacked along the y-axis to be as close
% as possible to the particles in the "fixed" layer. This is done using the "distance"
% and "iclosest" functions to find the closest particle in the "fixed" layer to each
% particle in the "movable" layer, and then adjusting the positions of the particles
% in the "movable" layer accordingly.
for it=1:350
shift = dt * v;
config.x = config.x + shift;
% repack the top layer respectively to the fixed
distance = @(i) sqrt((fixed.x-config.x(i)).^2 + (fixed.y-config.y(i)).^2);
iclosest = arrayfun( @(i) find( distance(i)==min(distance(i)),1),1:config.n );
distance2closest = sqrt((fixed.x(iclosest)-config.x).^2 + (fixed.y(iclosest)-config.y).^2);
dtarget = fixed.R*2*(1-abs(randn(config.n,1)*0.01));
contraction_required = 1-dtarget./distance2closest;
config.x = config.x + (fixed.x(iclosest)-config.x) .* contraction_required;
config.y = config.y + (fixed.y(iclosest)-config.y) .* contraction_required;
%The Landshoff forces are then calculated using a nested loop that iterates
% over all pairs of particles in both layers. The forces are calculated using
% the kernel function and the Landshoff force formula, which includes the velocity
% difference and distance between particles, as well as the SPH parameters
id = [ fixed.id * ones(fixed.n,1); config.id * ones(config.n,1) ];
xy = [fixed.x fixed.y; config.x config.y];
vxy = [repmat([0 0],fixed.n,1); repmat([v 0],config.n,1)];
n = fixed.n + config.n;
[mu,nu] = deal(zeros(n,n));
F = zeros(n,n,2);
for i = 1:n
for j = 1:n
rij = xy(i,:)-xy(j,:);
vij = vxy(i,:)-vxy(j,:);
if dot(rij,vij)<0
mu(i,j) = h * dot(rij,vij)/(dot(rij,rij)+0.01*h^2);
nu(i,j) = (1/rho) * (-q1*c0*mu(i,j));
rij_d = norm(rij);
rij_n = rij/rij_d;
F(i,j,:) = -nu(i,j)*dWdr(rij_d) * permute(rij_n,[1 3 2]);
end
end
end
Fbalance = squeeze(sum(F,2));
f = sum(Fbalance.^2,2);
fmedian = median(f);
fmin = fmedian/50;
fscale = 0.2*r/fmedian;
%Finally, the forces are plotted as arrows on the particle plot using the "arrow" function,
% and the plot is updated with each iteration.
start = xy;
stop = start + Fbalance*fscale;
start(fif ~isempty(hp), delete(hp); end
if ~isempty(ha), delete(ha); end
hp = layerplot(config);
ha = [
arrow(start,stop,'length',8,'BaseAngle',60,'Linewidth',2,'color',rgb('ForestGreen'),'Edgecolor',rgb('ForestGreen'))
arrow(start,[stop(:,1) start(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'))
arrow(start,[start(:,1) stop(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'),'EdgeColor',rgb('Tomato'))
];
drawnow
%If the "RECORD" flag is set to true, the plot is also recorded as a GIF using the "gif_add_frame" function.
if RECORD
gif_add_frame(gca,'landshoff.gif',15);
end
end
Landshoff forces in a wakeflow
INRAE\Olivier Vitrac - rev. 25/02/2023
%% Landshoff forces in a wakeflow
% INRAE\Olivier Vitrac - rev. 25/02/2023
% [ SYNOPSIS ] This code simulates the movement of particles in a wake flow,
% in which an obstacle is placed in front of a flow to create a wake downstream.
% The simulation includes particles in several layers, with particles in each
% layer connected by a force that is calculated based on the Landshoff force formula.
% The code uses the SPH (Smoothed Particle Hydrodynamics) method to simulate the motion
% of the particles and the arrows plotted on the particle plot indicate the forces
% between particles. The code also includes a GIF recording function to capture the
% animation of the particle movements and forces.
%% General definitions
% layout definitions and plot customized function
layercolors = struct( ...
'fixed',rgb('DodgerBlue'),...
'layer1',rgb('DeepSkyBlue'),...
'layer2',rgb('Aqua'),...
'layer3',rgb('PaleTurquoise'),...
'obstacle',rgb('Crimson') ...
);
layerplot = @(X) viscircles([X.xy],X.R*ones(X.n,1),'color',layercolors.(X.tag));
% configuration functions
layercoord = @(X) transpose([
linspace(-(X.dx+X.R*2)*(X.n-1)/2,(X.dx+X.R*2)*(X.n-1)/2,X.n)
ones(1,X.n) * (X.y + X.dy)
]);
layersliding = @(X) ones(X.n,1)*X.slidingdirection;
layerredistribute = @(X,x0) x0+[cumsum([0;diff(X.xy(:,1))]+abs(randn(X.n,1))*X.R*0.25)];
% mathematical functions for managing bead displacements
imin = @(d) find(d==min(d),1,'first');
imin2 = @(d) find(d==min(d(d>min(d))),1,'first');
distance = @(xy) sqrt(sum(xy.^2,2));
dist2ref = @(ref,testxy) distance(ref.xy-testxy);
iclosest = @(ref,test) cellfun(@(testxy) imin(dist2ref(ref,testxy)),num2cell(test.xy,2));
iclosest2 = @(ref,test) cellfun(@(testxy) imin2(dist2ref(ref,testxy)),num2cell(test.xy,2));
distance2closest = @(ref,test) distance(ref.xy(iclosest(ref,test),:)-test.xy);
distance2closest2 = @(ref,test) distance(ref.xy(iclosest2(ref,test),:)-test.xy);
contractionfactor = @(ref,test) 1 - ref.R*2*(1-abs(randn(test.n,1)*0.02)) ./ distance2closest(ref,test);
contractionfactor2 = @(ref,test) 1 - ref.R*2*(1-abs(randn(test.n,1)*0.02)) ./ distance2closest(ref,test);
updatexy = @(ref,test) test.xy + (ref.xy(iclosest(ref,test),:)-test.xy) .* contractionfactor(ref,test);
updatexy2 = @(ref,test) test.xy + (ref.xy(iclosest2(ref,test),:)-test.xy) .* contractionfactor2(ref,test);
thermostat = @(v) v .*(1+randn(size(v))*0.04);
% bead size (r)
r = 0.5;
% Kernel definitions
% The kernel function is used to calculate the smoothing function
% for the SPH method, and the parameters include the smoothing length (h),
% the density (rho), and the coefficients (c0 and q1) used in the Landshoff force calculation.
h = 2 * r;
dWdr = @(r) (r%% Fixed layer
fixed = struct( ...these particles are arbitrarily fixed
'R',r, ... radius
'n',32,... number of beads
'xy',[],... coordinates
'y',-r,... initial position
'dx',1e-2,... distance between beads
'dy',0,...
'slidingdirection',[1 0],...
'tag','fixed',...
'id',0 ...
);
fixed.xy = layercoord(fixed);
fixed.xy(:,1) = layerredistribute(fixed,0);
fixed.slidingdirection = layersliding(fixed);
%% Layer 1 (in contact with the fixed layer)
layer1 = struct( ...these particles are arbitrarily fixed
'R',r, ... radius
'n',16,... number of beads
'xy',[],... coordinates
'y',+r,... initial position
'dx',1e-2,... distance between beads
'dy',1e-2,...
'slidingdirection',[1 0],...
'tag','layer1',...
'id',1 ...
);
layer1.xy = layercoord(layer1);
layer1.xy(:,1) = layerredistribute(layer1,fixed.R);
layer1.slidingdirection = layersliding(layer1);
% %% Layer 2 (in contact with the fixed layer)
layer2 = struct( ...these particles are arbitrarily fixed
'R',r, ... radius
'n',14,... number of beads
'xy',[],... coordinates
'y',+2*r,... initial position
'dx',1e-2,... distance between beads
'dy',1e-2,...
'slidingdirection',[1 0],...
'tag','layer2',...
'id',2 ...
);
layer2.xy = layercoord(layer2);
layer2.xy(:,1) = layerredistribute(layer2,layer1.xy(3,1)-fixed.R);
layer2.slidingdirection = layersliding(layer2);
% %% Layer 3 (in contact with the fixed layer)
layer3 = struct( ...these particles are arbitrarily fixed
'R',r, ... radius
'n',12,... number of beads
'xy',[],... coordinates
'y',+3*r,... initial position
'dx',1e-2,... distance between beads
'dy',1e-2,...
'slidingdirection',[1 0],...
'tag','layer3',...
'id',3 ...
);
layer3.xy = layercoord(layer3);
layer3.xy(:,1) = layerredistribute(layer3,layer1.xy(4,1));
layer3.slidingdirection = layersliding(layer3);
%% obstacle (in contact with the fixed layer)
[i,j] = meshgrid(-1:1,-1:1); [i,j] = deal(i(:),j(:));
centers = [ 2*i + mod(j,2), sqrt(3)*j ]*r;
centers(sum(centers.^2,2)>1,:)=[];
obstacle = struct( ...these particles are arbitrarily fixed
'R',r, ... radius
'n',size(centers,1),... number of beads
'xy',[
centers(:,1)+fixed.xy(floor(fixed.n/2),1)+fixed.R*5-min(centers(:,1)), ...
centers(:,2)+fixed.y+fixed.dy+fixed.R+r-min(centers(:,2))
],... coordinates
'y',+r,... initial position
'dx',0,... distance between beads
'dy',0,...
'slidingdirection',[
0 1 % SW
0 1 % W
0.5 0.5 % NW
1 -0.2 % SE
NaN NaN % center
0.5 -0.5 % NE
0.5 -0.5 % E
],...
'tag','obstacle',...
'id',100 ...
);
%% all fixed (for collision only)
allfixed = fixed;
allfixed.xy = [fixed.xy;obstacle.xy];
allfixed.slidingdirection = [fixed.slidingdirection;obstacle.slidingdirection];
allfixed.n = fixed.n + obstacle.n;
%% control (only for validating the design)
clf, axis equal, hold on
layerplot(fixed)
layerplot(layer1)
layerplot(layer2)
layerplot(layer3)
ho=layerplot(obstacle);
%% Dynamic plot
% initial position
v = 4*0.1; % velocity (arbitrary units)
dt = 0.1;
[config1,config2,config3] = deal(layer1, layer2, layer3);
clf, hold on, axis equal
layerplot(fixed);
ho=layerplot(obstacle);
[hp,ha] = deal([],{});
ax = axis;
axis off
RECORD = false; % set it to true to record a video
% The code then enters a loop that simulates the movement of the particles over time.
for it=1:800
% layer2 neighbor
ineigh12 = iclosest(config1,config2);
ineigh13 = iclosest(config1,config3);
% move of layer 1
vshift1 = thermostat(v * allfixed.slidingdirection(iclosest(allfixed,config1),:));
vshift1 = distance(v)*vshift1./distance(vshift1);
shift1 = dt * vshift1;
lastxy1 = config1.xy;
config1.xy = config1.xy + shift1;
alternativexy = updatexy2(allfixed,config1); % only alternative point
config1.xy = updatexy(allfixed,config1);
realdisplacement1 = distance(config1.xy-lastxy1);
istuck1 = realdisplacement1<0.01;
if any(istuck1)
config1.xy(istuck1,:) = alternativexy(istuck1,:);
end
% move of layer 2
directionlayer1 = config1.xy-lastxy1./distance(config1.xy-lastxy1);
vshift2 = thermostat(v * directionlayer1(iclosest(layer1,config2),:));
shift1update = thermostat(config1.xy-lastxy1);
shift2 = shift1update(ineigh12,:);
vshift2 = shift2/dt;
config2.xy = config2.xy + shift2;
config2.xy = updatexy(config1,config2);
% move of layer 3
vshift3 = thermostat(v * directionlayer1(iclosest(layer1,config3),:));
shift3 = shift1update(ineigh13,:);
vshift3 = shift3/dt;
config3.xy = config3.xy + shift3;
config3.xy = updatexy(config2,config3);
%The Landshoff forces are then calculated using a nested loop that iterates
% over all pairs of particles in both layers. The forces are calculated using
% the kernel function and the Landshoff force formula, which includes the velocity
% difference and distance between particles, as well as the SPH parameters
id = [ allfixed.id * ones(allfixed.n,1); config1.id * ones(config1.n,1); config2.id * ones(config2.n,1); config3.id * ones(config3.n,1)];
xy = [allfixed.xy; config1.xy; config2.xy; config3.xy];
vxy = [repmat([0 0],allfixed.n,1); vshift1; vshift2; vshift3];
n = allfixed.n + config1.n + config2.n + + config3.n;
[mu,nu] = deal(zeros(n,n));
F = zeros(n,n,2);
for i = 1:n
for j = 1:n
rij = xy(i,:)-xy(j,:);
vij = vxy(i,:)-vxy(j,:);
if dot(rij,vij)<0
mu(i,j) = h * dot(rij,vij)/(dot(rij,rij)+0.01*h^2);
nu(i,j) = (1/rho) * (-q1*c0*mu(i,j));
rij_d = norm(rij);
rij_n = rij/rij_d;
F(i,j,:) = -nu(i,j)*dWdr(rij_d) * permute(rij_n,[1 3 2]);
end
end
end
Fbalance = squeeze(sum(F,2));
f = sum(Fbalance.^2,2);
flog = log(1+f);
flogmedian = median(flog(flog>0));
flogmin = flogmedian/100;
flogscale = 2*r/flogmedian;
%Finally, the forces are plotted as arrows on the particle plot using the "arrow" function,
% and the plot is updated with each iteration.
start = xy;
stop = start + Fbalance./distance(Fbalance).*flog*flogscale;
start(flogif ~isempty(hp), delete(hp{1}), delete(hp{2}), delete(hp{3}), end
if ~isempty(ha), delete(ha); end
hp = {layerplot(config1); layerplot(config2); layerplot(config3)};
ha = [
arrow(start,stop,'length',4,'BaseAngle',60,'Linewidth',1,'color',rgb('ForestGreen'),'Edgecolor',rgb('ForestGreen'))
%arrow(start,[stop(:,1) start(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'))
%arrow(start,[start(:,1) stop(:,2)],'length',4,'BaseAngle',60,'color',rgb('Tomato'),'EdgeColor',rgb('Tomato'))
];
axis(ax)
drawnow
%If the "RECORD" flag is set to true, the plot is also recorded as a GIF using the "gif_add_frame" function.
if RECORD
gif_add_frame(gca,'landshoff_obstacle.gif',15);
end
end
First template to retrieve Billy's paper 2 simulation data
rev. 2024/03/07
% First template to retrieve Billy's paper 2 simulation data
% rev. 2024/03/07
% 2024/03/07 implementation of reverse streamlines, streamline binning from their initial positions, subsampling
% 2024/03/12 implement bead along streamlines (bug:NaN and Inf not allowed.)
%% path and metadata
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
else
root = fullfile(pwd,'smalldumps');
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
%% extract information
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
% Simulation parameters
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff); %#ok
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
tframe = 0.8; % s <-------------------- select time here
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
fluidxyz = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords};
fluidid = X0.ATOMS{Xframe.ATOMS.isfluid,'id'};
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,coords};
pillarid = X0.ATOMS{Xframe.ATOMS.ispillar,'id'};
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
sphereid = X0.ATOMS{Xframe.ATOMS.issphere,'id'};
solidxyz = Xframe.ATOMS{Xframe.ATOMS.issolid,coords};
solidid = X0.ATOMS{Xframe.ATOMS.issolid,'id'};
ztop = max(pillarxyz(:,3)); % pillar top
%% Interpolate velocity field at z = ztop
fluidbox = [ min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords})
max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords}) ]';
fluidboxsize = double(diff(fluidbox,1,2)); % for control
% restrict interpolation to the viewbox
viewbox = fluidbox; viewbox(3,:) = [ztop-2*h ztop+2*h];
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& Xframe.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xframe.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
XYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZs = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.issolid,coords};
% interpolation grid
nresolution = [1024 1024 1];
xw = linspace(viewbox(1,1),viewbox(1,2),nresolution(1));
yw = linspace(viewbox(2,1),viewbox(2,2),nresolution(1));
zw = ztop;
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
% grid neighbors for interpolation and discarding grid points overlapping solid
VXYZ = buildVerletList({XYZgrid XYZ},1.2*h); % neighbors = fluid particles
VXYZs = buildVerletList({XYZgrid XYZs},0.85*s); % neighbors = solid particles
icontactsolid = find(cellfun(@length,VXYZs)>0);
VXYZ(icontactsolid) = repmat({[]},length(icontactsolid),1);
% neighbors statistics for control
% example with grid points having 20 neighbors or less: plot(XYZgrid(nn<20,1),XYZgrid(nn<20,2),'o','markerfacecolor','k')
%{
nn = cellfun(@length,VXYZ);
[nnu,~,innu] = unique(nn);
cnnu = accumarray(innu,nn,[],@length);
%}
% interpolation stuff
W = kernelSPH(h,'lucy',3); % kernel expression
v3XYZgrid = interp3SPHVerlet(XYZ,vXYZ,XYZgrid,VXYZ,W,Vbead);
vxXYZgrid = reshape(v3XYZgrid(:,1),size(Xw)); %vxXYZgrid(isnan(vxXYZgrid)) = 0;
vyXYZgrid = reshape(v3XYZgrid(:,2),size(Xw)); %vyXYZgrid(isnan(vyXYZgrid)) = 0;
vzXYZgrid = reshape(v3XYZgrid(:,3),size(Xw)); %vzXYZgrid(isnan(vzXYZgrid)) = 0;
vXYZgrid = reshape(sqrt(sum(v3XYZgrid.^2,2)),size(Xw));
%% Plot (attention 2D)
figure, hold on
% velocity magnitude
imagesc(xw,yw,vXYZgrid)
axis tight, axis equal
% quiver configuration and plot
step = 8;
boundaries = [ nearestpoint(double(xw([1,end]))+fluidboxsize(1)/30*[1 -1],double(xw))
nearestpoint(double(yw([1,end]))+fluidboxsize(2)/30*[1 -1],double(yw))
];
indxquiver = boundaries(1,1):step:boundaries(1,2);
indyquiver = boundaries(2,1):step:boundaries(2,2);
quiver(Xw(indxquiver,indyquiver),Yw(indxquiver,indyquiver), ...
vxXYZgrid(indxquiver,indyquiver),vyXYZgrid(indxquiver,indyquiver), ...
'color','k','LineWidth',1)
% streamline configuration and plot
boundaries = [ nearestpoint(double(xw([1,end]))+fluidboxsize(1)/30*[1 -1]*1.5,double(xw))
nearestpoint(double(yw([1,end]))+fluidboxsize(2)/30*[1 -1]*1,double(yw))
];
step = step/2;
indxstreamline = boundaries(1,1):step:boundaries(1,2);
indystreamline = boundaries(2,1):step:boundaries(2,2);
nstreamlines = length(indxstreamline);
% streamlines from bottom to top
[startX,startY,startZ] = meshgrid(double(xw(indxstreamline)),double(yw(indystreamline(1))),double(ztop));
vertices = stream2(double(Xw),double(Yw),vxXYZgrid,vyXYZgrid,startX,startY);
xinitialposition = cellfun(@(v) v(1,1),vertices);
yinitialposition = cellfun(@(v) v(1,2),vertices);
xfinalposition = cellfun(@(v) v(end,1),vertices);
yfinalposition = cellfun(@(v) v(end,2),vertices);
yfinaldefaultposition = yfinalposition(find(~isnan(yfinalposition),1,'first'));
indclosestToInitial = nearestpoint(xfinalposition,xinitialposition);
isinterrupted = isnan(yfinalposition);
% Build the adjacency matrix the initial and final position
A = false(nstreamlines,nstreamlines); %initial x final positions
for i=1:nstreamlines
if ~isnan(indclosestToInitial(i))
A(i,indclosestToInitial(i)) = true;
end
end
isreached = any(A,1);
% Missinf streamlines from top to bottom
[startX2,startY2,startZ2] = meshgrid(xinitialposition(~isreached),yfinaldefaultposition,double(ztop));
vertices2 = stream2(double(Xw),double(Yw),-vxXYZgrid,-vyXYZgrid,startX2,startY2);
% streamlines from top to bottom (for unreached positions)
% control plot
% figure, hold on
% colors = tooclear(parula(nstreamlines));
% for i=1:nstreamlines
% if isinterrupted(i)
% plot(vertices{i}(:,1),vertices{i}(:,2),'-','linewidth',2,'color',colors(i,:));
% else
% plot(vertices{i}(:,1),vertices{i}(:,2),'--','linewidth',2,'color',colors(i,:));
% plot( [xinitialposition(i);xinitialposition(indclosestToInitial(i))],...
% [yinitialposition(i);yfinaldefaultposition],...
% '-','linewidth',2,'color',colors(i,:))
% end
% text(xinitialposition(i),yinitialposition(i),sprintf('%d',i),'fontsize',8,'fontweight','bold','HorizontalAlignment','center','VerticalAlignment','middle')
% if isreached(i), col = 'blue'; else col = 'red'; end
% text(xinitialposition(i),yfinaldefaultposition,sprintf('%d',i),'fontsize',8,'fontweight','bold','HorizontalAlignment','center','VerticalAlignment','middle','color',col)
% end
% Merge all streamlines and create flags
allvertices = [vertices,vertices2];
idvertices = [ones(1,length(vertices)),2*ones(1,length(vertices2))];
[xallvertices,ind] = sort(cellfun(@(v) v(1,1),allvertices),'ascend'); % mean(v(:,1),'omitnan')
allvertices = allvertices(ind);
idvertices = idvertices(ind);
allbroken = cellfun(@(v) any(isnan(v(:,1))),allvertices);
allcomplete = ~allbroken;
allup = cellfun(@(v) v(2,2)>v(1,2),allvertices);
alldown = ~allup;
% Pick streamlines from binning
nbins = 80;
xbins = linspace(xinitialposition(1),xinitialposition(end),nbins); % desired bin centers
dxbins = xbins(2)-xbins(1); % bin width (for control)
% Binning for complete streamlines
ibinsall = nearestpoint(xbins,xallvertices); %interpleft(xallvertices,1:length(allvertices),xbins);
ibinsall = ibinsall(allcomplete(ibinsall)); % we keep complete
% extract the initial (0) and final (1) positions of up/down streamlines
isselected = false(1,length(allvertices)); isselected(ibinsall)=true;
x0up = cellfun(@(v) v(1,1),allvertices(isselected & allup));
x1up = cellfun(@(v) v(end,1),allvertices(isselected & allup));
x0down = cellfun(@(v) v(1,1),allvertices(isselected & alldown));
x1down = cellfun(@(v) v(end,1),allvertices(isselected & alldown));
xdeviation = cellfun(@(v) abs(v(1,1)-v(end,1)),allvertices(isselected)); % for control
% Binning from incomplete (broken) streamlines
% all bins required
xbins_broken = xbins(setdiff(1:nbins,nearestpoint(xallvertices(ibinsall),xbins)));
if isempty(x1down)
valid_up = true(size(xbins_broken));
else
valid_up = abs(x1down(nearestpoint(xbins_broken,x1down))-xbins_broken)>(0.5*dxbins);
end
if isempty(x1up)
valid_down = true(size(xbins_broken));
else
valid_down = abs(x1up(nearestpoint(xbins_broken,x1up))-xbins_broken)>(0.5*dxbins);
end
iupbroken = find(allup & allbroken);
xupbroken = xallvertices(iupbroken);
ibinsupbroken = iupbroken(nearestpoint(xbins_broken(valid_up),xupbroken));
idownbroken = find(alldown & allbroken);
xdownbroken = xallvertices(idownbroken);
ibinsdownbroken = idownbroken(nearestpoint(xbins_broken(valid_down),xdownbroken));
% Merge and sort all bins
ibins = unique([ibinsall,ibinsdownbroken,ibinsupbroken]);
% check for holes/gaps between streamlines and overlopping streamlines
% The two passes are sequenced to avoid an infinite loop between fill/remove steps
% pass 1: fill the gaps (bottom and top) iteratively
% pass 1: remove ovelaps (bottom and top) iteratively (this step is always after the first filling procedure)
% pass 2: fill the gaps (bottom and top)
[holechecked, overlapchecked] = deal(false);
gapthreshmax = 1.9; % max gap (1=bin distance)
gapthreshmin = 0.5; % minimum gap
iter = 0; maxiter = 100;
pass2 = false;
while (~holechecked || ~overlapchecked) && iterend ,1),allvertices(ispicked & allcomplete & alldown)));
dxbottom = diff(xbottom)/dxbins;
if any(dxbottom>gapthreshmax) && ~holechecked
xholebottom = xallvertices(ibins(nearestpoint(xbottom(dxbottom>gapthreshmax),xallvertices(ibins))))+dxbins;
iup = find(allup);
ibinsholebottom = iup(nearestpoint(xholebottom,xallvertices(allup)));
ibins = union(ibins,ibinsholebottom);
holecheckbottom = false;
overlapcheckbottom = false;
dispf('add %d streamlines to the bottom',length(ibinsholebottom))
elseif ~holechecked
holecheckbottom = true;
elseif any(dxbottomremove %d streamlines from the bottom',length(ioverlapbottom))
else
overlapcheckbottom = true;
end
ispicked = false(1,length(allvertices)); ispicked(ibins)=true;
xtop = union( cellfun(@(v) v(end,1),allvertices(ispicked & allcomplete & allup)),...
cellfun(@(v) v(1,1),allvertices(ispicked & alldown)));
dxtop = diff(xtop)/dxbins;
if any(dxtop>gapthreshmax) && ~holechecked
xholetop = xallvertices(ibins(nearestpoint(xtop(dxtop>gapthreshmax),xallvertices(ibins))))+dxbins;
idown = find(alldown);
ibinsholetop = idown(nearestpoint(xholetop,xallvertices(alldown)));
ibins = union(ibins,ibinsholetop);
holechecktop = false;
overlapchecktop = false;
dispf('add %d streamlines to the top',length(ibinsholetop))
elseif ~holechecked
holechecktop = true;
elseif any(dxtopremove %d streamlines from the top',length(ioverlaptop))
else
overlapchecktop = true;
end
holechecked = holecheckbottom && holechecktop;
overlapchecked = overlapcheckbottom && overlapchecktop;
if overlapchecked && ~pass2
pass2=true;
holechecked = false; % we restart the filling gap correction
end
end
if iter>=maxiter, error('Gaps cannot be filled, decrease step (current=%d) !', step), end
% plot streamlines
hsl = streamline(allvertices); set(hsl,'linewidth',0.1,'color',[0.8 0.8 0.8]);
hsl = streamline(allvertices(ibins)); set(hsl,'linewidth',2,'color',[0.4375 0.5000 0.5625])
plot(startX(:),startY(:),'ro','markerfacecolor',[0.4375 0.5000 0.5625])
%% put beads along selected streamlines
% get informations of streamlines
sl = allvertices(ibins);
nsl = length(sl); % number of selected streamlines
sall = cell(1,nsl);
for i=1:nsl
csl = sl{i}; % select current streamline
xcsl = csl(:,1); ycsl = csl(:,2);
Tcsl = [diff(xcsl) diff(ycsl); NaN NaN];
Tcsl_norm = Tcsl./sqrt(sum(Tcsl.^2,2));
lcsl = [0; cumsum(sqrt(sum(Tcsl(1:end-1,:).^2,2)))];
Vxcsl = interp2(Xw,Yw,vxXYZgrid,xcsl,ycsl);
Vycsl = interp2(Xw,Yw,vyXYZgrid,xcsl,ycsl);
Vcsl = [Vxcsl,Vycsl];
Vlcsl = dot(Vcsl,Tcsl_norm,2);
s = table(xcsl,ycsl,lcsl,Tcsl_norm,Vcsl,Vlcsl,'VariableNames',["x","y","l","T","V","Vl"]);
sall{i} = s;
end
% put beads
dt = 0.01;
figure, hold on
for i=1:nsl
[x,y,Vl,l] = add_bead(sall{i},rbead,dt);
centers = [x,y];
viscircles(centers,rbead)
end
First template to retrieve Billy's paper 2 simulation data
rev. 2024/03/17 - forked in 2024/03/16 with PBC
% First template to retrieve Billy's paper 2 simulation data
% rev. 2024/03/17 - forked in 2024/03/16 with PBC
% 2024/03/07 implementation of reverse streamlines, streamline binning from their initial positions, subsampling
% 2024/03/12 implement bead along streamlines (bug:NaN and Inf not allowed.)
% 2024/03/16 fork from Billy_results_template.m (MAJOR UPDATE)
% 2024/03/17 release candidate
% 2024/03/21 preproduction on minidump
% 2024/03/24 major update: full implementation of density correction and force contours around objects
% 2024/03/24 selective copy possible if originalroot is made available
% 2024/03/24 add prefetch management
% 2024/03/25 first batch of preproduction tframe 0.3->1.1 (step 0.01)
% 2024/03/26 second batch of preproduction tframe 0.11->0.4 (step 0.01)
% 2024/03/27 fix streamlines when less than one bead must be added (tframe = 0.11 and 0.27)
% 2024/03/27 fix contour, tangents/normals, add perssure
% 2024/03/27 Senstivity challenge test using ngenerations = 8 and xshift = double(mod(igen-1,2)*(xwPBC(indxstreamline(2))-xwPBC(indxstreamline(1))));
% 2024/03/30 full implementation of Landshoff forces
% 2024/03/31 add FLAGS, documentation and save results capability (R0 and R1 for original and informed results, respectively)
% 2024/03/31 full rewritting of PLOT capabilities to use exclusively R0 and R1 for plotting
% SUMMARY
% This MATLAB script is a comprehensive framework for analyzing fluid dynamics simulations, specifically focusing on the distribution and movement of particles or beads in a fluid environment. Key functionalities include:
%
% 1. Environment Setup:
% Initializes the simulation environment by clearing variables, setting up output folders, and defining file paths to simulation data, with adjustments for periodic boundary conditions (PBC).
% 2. Data Retrieval:
% Loads simulation data from specified file paths, handling different configurations and viscosity models.
% 3. Simulation Analysis:
% Processes the simulation data to calculate parameters such as bead sizes, timestep intervals, and spatial distributions of particles.
% 4. Frame Selection:
% Identifies simulation frames for detailed analysis based on time criteria, with capabilities to adjust for available data subsets.
% 5. Velocity Field and Density Calculation:
% Computes the velocity field at a specific plane in the simulation domain and estimates the local density of particles.
% 6. Streamline and Bead Distribution Analysis:
% Generates streamlines and distributes beads along these lines, incorporating PBC adjustments to simulate continuous fluid flow across boundaries.
% 7. Overlap Removal:
% Implements algorithms to remove overlapping beads based on their spatial proximity and streamline generation, ensuring a realistic particle distribution.
% 8. Density Filtering:
% Filters out beads in regions of excessively high simulated density to adhere to physical constraints.
% 9. Contact Detection with Objects:
% Identifies beads in contact with solid objects within the simulation, leveraging density information to infer interaction dynamics.
% 10. Visual Representation:
% Provides extensive plotting capabilities to visualize various aspects of the simulation, including velocity fields, bead distributions, and pressure around objects, with options to export figures.
%% Definitions (it is a script accepting ONE variable and FLAGS)
%
% List of of variables
% tframe <-- use this variable a in for-loop or choose a particular frame before calling this script
% tframelist (not used)
%
% List of available FLAGS
% RESETPREFETCH forces all prefetch files to be regenerated in true (default=false)
% PLOTON enables to plot results (default=true)
% PRINTON prints figures as PNG images for control (in outputfolder) (default=false)
% SAVEON saves the results R0 (original), R1 (informed) (default=true)
% OVERWRITE enables already saved results to overwritte (default=false)
% close, delete everything except variables and FLAGS
clc
close all
clearvars -except tframe tframelist RESETPREFETCH PLOTON PRINTON SAVEON OVERWRITE
t0_ = clock;
% check folders
outputfolder = fullfile(pwd,'preproduction');
savefolder = fullfile(pwd,'results');
prefetchfolder = fullfile(pwd,'prefetch');
if ~exist(outputfolder,'dir'), mkdir(outputfolder); end
if ~exist(prefetchfolder,'dir'), mkdir(prefetchfolder); end
if ~exist(savefolder,'dir'), mkdir(savefolder); end
% Assign default values if needed
if ~exist('RESETPREFETCH','var'), RESETPREFETCH = false; end
if ~exist('PLOTON','var'), PLOTON = true; end
if ~exist('PRINTON','var'), PRINTON = false; end
if ~exist('SAVEON','var'), SAVEON = true; end
if ~exist('OVERWRITE','var'), OVERWRITE = false; end
% Anonymous functions
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
fighandle = @(id) formatfig(figure,'figname',sprintf('t%0.3g_%s',tframe,id));
printhandle = @(hfig) print_png(300,fullfile(outputfolder,[get(hfig,'filename') '.png']),'','',0,0,0);
% results saved in two variables to avoid any confusion
R0 = struct([]); % reference data
R1 = struct([]); % informed ones
%% path and metadata
dispsection('INITIALIZATION')
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
dispf('config: %s | viscosity: %s | source: %s',config,viscosity,dumpfile)
%% extract information
dispsection('OVERVIEW')
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
% Billy choose a reference density of 900 kg/m3 for a physical density of 1000 kg/m3
% Viscosity: 0.13 Pa.s
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
dispf('SUMMARY: natoms: %d | dt: %0.3g s | rho: %0.4g',natoms,dt,rho)
%% selective copy of PREFETCH files (if possible)
if copymode
dispsection('COPY')
myprefetchfile = @(itime) sprintf('%s%09d.mat','TIMESTEP_',itime);
myprefetchfolder = @(d) fullfile(d,sprintf('PREFETCH_%s',lastdir(dumpfile)));
destinationfolder = fullfile(rootlocal,rootdir(simfolder.(config).(viscosity)));
sourcefolderPREFETCH = myprefetchfolder(sourcefolder);
destinationfolderPREFETCH = myprefetchfolder(destinationfolder);
% Choose the frames needed here
tcopy = 0.0:0.01:1.2;
% files to copy
tfile = arrayfun(@(t) myprefetchfile(t), timesteps(unique(nearestpoint(tcopy,times))),'UniformOutput',false);
oksource = all(cellfun(@(f) exist(fullfile(sourcefolderPREFETCH,f),'file'),tfile));
if ~oksource, error('the source folder is corrupted, please check'), end
existingfiles = cellfun(@(f) exist(fullfile(destinationfolderPREFETCH,f),'file'),tfile);
dispf('Number of files to copy: %d (%d already available)',length(find(~existingfiles)),length(find(existingfiles)))
copysuccess = cellfun(@(f) copyfile(fullfile(sourcefolderPREFETCH,f),fullfile(destinationfolderPREFETCH,f)),tfile(~existingfiles));
dispf('%d of %d files have been copied',length(find(copysuccess)),length(copysuccess));
end
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
dispsection('BEAD SIZE')
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
if isprefetch('verletList')
load(prefetchvar('verletList'))
else
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff);
save(prefetchvar('verletList'),'verletList','cutoff','dmin','config','dist')
end
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
dispf('SUMMARY: s: %0.4g m | h: %0.4g m',s,h)
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.0:0.01:1.2; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
fluidxyz = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords};
fluidid = X0.ATOMS{Xframe.ATOMS.isfluid,'id'};
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,coords};
pillarid = X0.ATOMS{Xframe.ATOMS.ispillar,'id'};
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
sphereid = X0.ATOMS{Xframe.ATOMS.issphere,'id'};
solidxyz = Xframe.ATOMS{Xframe.ATOMS.issolid,coords};
solidid = X0.ATOMS{Xframe.ATOMS.issolid,'id'};
ztop = max(pillarxyz(:,3)); % pillar top
% Definitions based on actual tframe
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
savefile = @() fullfile(savefolder,sprintf('Rt.%0.4f.mat',tframe));
%% Interpolate velocity field at z = ztop
dispsection('REFERENCE VELOCITY FIELD')
% full box (note that atoms may be outside of this box)
box = Xframe.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box,1,2);
% fluidbox (box for atoms to consider)
xmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
xmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
ymin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
ymax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
zmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
zmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
fluidbox = [xmin xmax;ymin ymax;zmin zmax];
% restrict interpolation to the viewbox
viewbox = fluidbox; viewbox(3,:) = [ztop-2*h ztop+2*h];
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& Xframe.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xframe.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
XYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZs = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.issolid,coords}; % solid kernel centers
rhobeadXYZ = Xframe.ATOMS.c_rho_smd(insideviewbox & Xframe.ATOMS.isfluid); % volume of the bead
% force incell (this step is needed to apply PBC)
XYZ = PBCincell(XYZ,box,[true,true,false]);
XYZs = PBCincell(XYZs,box,[true,true,false]);
% add PBC images
[XYZimagesONLY ,indXimagesONLY]= PBCimages(XYZ,box,[true,true,false],2*h);
XYZwithImages = [XYZ;XYZimagesONLY];
vXYZwithImages = [vXYZ;vXYZ(indXimagesONLY,:)];
rhobeadXYZwithImages = [rhobeadXYZ;rhobeadXYZ(indXimagesONLY)];
VbeadXYZwithImages = mbead./rhobeadXYZwithImages;
isImages = true(size(XYZwithImages,1),1); isImages(1:size(XYZ,1))=false;
% Plot PBC (plot saved in R0)
R0(1).XYZ = XYZ;
R0(1).indXimagesONLY = indXimagesONLY;
R0(1).XYZimagesONLY = XYZimagesONLY;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R0) **********
figure, hold on, plot3D(XYZ,'go')
plot3D(R0(1).XYZ(R0(1).indXimagesONLY,:),'go','markerfacecolor','g')
plot3D(R0(1).XYZimagesONLY,'ro','markerfacecolor','r'), view(3), axis equal, view(2)
end
% interpolation grid (central grid, no images)
nresolution = [1024 1024 1];
xw = linspace(box(1,1),box(1,2),nresolution(1));
yw = linspace(box(2,1),box(2,2),nresolution(1));
zw = ztop;
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
% grid neighbors (incl. images) for interpolation and discarding grid points overlapping solid
if isprefetch('VXYZ')
load(prefetchvar('VXYZ'))
else
VXYZ = buildVerletList({XYZgrid XYZwithImages},1.2*h); % neighbors = fluid particles
VXYZs = buildVerletList({XYZgrid XYZs},0.85*s); % neighbors = solid particles
save(prefetchvar('VXYZ'),'VXYZ','VXYZs')
end
icontactsolid = find(cellfun(@length,VXYZs)>0);
VXYZ(icontactsolid) = repmat({[]},length(icontactsolid),1);
% interpolation on the grid of the 3D velocity (central grid)
% Do not forget even if we are applying a 2D interpretation, we use a 3D simulation
% 3D velocity are projected on 2D streamlines even if the projection of the 3rd component is 0
W = kernelSPH(h,'lucy',3); % kernel expression
if isprefetch('v3XYZgrid')
load(prefetchvar('v3XYZgrid'))
else
v3XYZgrid = interp3SPHVerlet(XYZwithImages,vXYZwithImages,XYZgrid,VXYZ,W,VbeadXYZwithImages);
save(prefetchvar('v3XYZgrid'),'v3XYZgrid')
end
vxXYZgrid = reshape(v3XYZgrid(:,1),size(Xw)); %vxXYZgrid(isnan(vxXYZgrid)) = 0;
vyXYZgrid = reshape(v3XYZgrid(:,2),size(Xw)); %vyXYZgrid(isnan(vyXYZgrid)) = 0;
vzXYZgrid = reshape(v3XYZgrid(:,3),size(Xw)); %vzXYZgrid(isnan(vzXYZgrid)) = 0;
vXYZgrid = reshape(sqrt(sum(v3XYZgrid.^2,2)),size(Xw));
% density on the grid (with a possible different h)
if isprefetch('rhobeadXYZgrid')
load(prefetchvar('rhobeadXYZgrid'))
else
rhobeadXYZgrid = interp3SPHVerlet(XYZwithImages,rhobeadXYZwithImages,XYZgrid,VXYZ,W,VbeadXYZwithImages);
save(prefetchvar('rhobeadXYZgrid'),'rhobeadXYZgrid')
end
rhobeadXYZgrid = reshape(rhobeadXYZgrid,size(Xw));
% add PBC (add periodic PBC)
[vxXYZPBC,XwPBC,YwPBC] = PBCgrid(Xw,Yw,vxXYZgrid,[true,true],boxsize(1:2)./[8;2]);
rhobeadXYZPBC = PBCgrid(Xw,Yw,rhobeadXYZgrid,[true,true],boxsize(1:2)./[8;2]);
xwPBC = XwPBC(1,:); ywPBC = YwPBC(:,1)';
fluidboxPBC = double([xwPBC([1 end]);ywPBC([1 end]);fluidbox(3,:)]);
fluidboxsizePBC = diff(fluidboxPBC,1,2);
vyXYZPBC = PBCgrid(Xw,Yw,vyXYZgrid,[true,true],boxsize(1:2)./[8;2]);
vzXYZPBC = PBCgrid(Xw,Yw,vzXYZgrid,[true,true],boxsize(1:2)./[8;2]);
vXYZPBC = sqrt(vxXYZPBC.^2 + vyXYZPBC.^2 + vzXYZPBC.^2);
% Plot PBC (saved in R0)
R0(1).fluidboxPBC = fluidboxPBC;
R0(1).fluidboxsizePBC = fluidboxsizePBC;
R0(1).xwPBC = xwPBC;
R0(1).ywPBC = ywPBC;
R0(1).XwPBC = XwPBC;
R0(1).YwPBC = YwPBC;
R0(1).vxXYZPBC = vxXYZPBC;
R0(1).vyXYZPBC = vyXYZPBC;
R0(1).vXYZPBC = vXYZPBC;
R0(1).rhobeadXYZPBC = rhobeadXYZPBC;
R0(1).rhobeadXYZgrid = rhobeadXYZgrid;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R0) **********
figure, mesh(R0(1).XwPBC,R0(1).YwPBC,R0(1).vXYZPBC), axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - velocity (m/s)',tframe))
figure, mesh(R0(1).XwPBC,R0(1).YwPBC,R0(1).rhobeadXYZPBC); axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - density (kg/m3)',tframe))
end
%% Plot (attention 2D, meshgrid convention)
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R0) **********
figure, hold on
% velocity magnitude
imagesc(R0(1).xwPBC,R0(1).ywPBC,R0(1).vXYZPBC)
axis tight, axis equal
colorbar
title(sprintf('t=%0.3f s - velocity (m/s)',tframe))
% quiver configuration and plot (boundaries are with PBC)
stepPBC = 16;
boundariesPBC = ...
[ nearestpoint(double(R0(1).xwPBC([1,end]))+R0(1).fluidboxsizePBC(1)/100*[1 -1],double(R0(1).xwPBC)) % x
nearestpoint(double(R0(1).ywPBC([1,end]))+R0(1).fluidboxsizePBC(2)/100*[1 -1],double(R0(1).ywPBC)) % y
];
indxquiver = boundariesPBC(1,1):stepPBC:boundariesPBC(1,2); % x indices
indyquiver = boundariesPBC(2,1):stepPBC:boundariesPBC(2,2); % y indices
quiver(R0(1).XwPBC(indyquiver,indxquiver),R0(1).YwPBC(indyquiver,indxquiver), ...
R0(1).vxXYZPBC(indyquiver,indxquiver),R0(1).vyXYZPBC(indyquiver,indxquiver), ...
'color','k','LineWidth',1)
end
%% streamlines
dispsection('STREAMLINES')
% UP from bottom (starting position) to top
% DOWN from top to bottom (same starting position by reversing the velocity field)
% assembled as [flipud(DOWN(2:end,:));UP]
% CELL streamlines along a y period, the separation distance is added to avoid gap
% streamline configuration and plot
boundaries = [ nearestpoint(double(xw([1,end])),double(xwPBC))
nearestpoint(double(yw([1,end])),double(ywPBC))
];
step = 8;
indxstreamline = boundaries(1,1):step:boundaries(1,2);
indystreamline = boundaries(2,1):step:boundaries(2,2);
nstreamlines = length(indxstreamline);
% Generative streamlines
ngenerations = 4;
xshift = 0.0;
iygen = unique(round(linspace(indystreamline(1),indystreamline(end),ngenerations)));
ngenerations = length(iygen);
[verticesgenUP,verticesgenDOWN,verticesgen,verticesgenCELL,verticesgenCELLid] = deal({});
validstreamline = @(v) (v(:,2)-v(1,2))<=(boxsize(2)*1.5) & (v(:,2)-v(1,2))>=(boxsize(2)*0.01); % longer than 1% but not exceeding 150%
for igen = 1:ngenerations
% ith generation of stream line (from indystreamline(1))
dispf('generate the %dth of %d generation of streamlines',igen,ngenerations)
%xshift = double(mod(igen-1,2)*(xwPBC(indxstreamline(2))-xwPBC(indxstreamline(1))));
[startgenX,startgenY,startgenZ] = meshgrid(...
double(xwPBC(indxstreamline))+xshift,...
double(ywPBC(iygen(igen))),double(ztop));
verticesgenUP{igen} = stream2(double(XwPBC),double(YwPBC),vxXYZPBC,vyXYZPBC,startgenX,startgenY,[0.03 5e4]);
verticesgenDOWN{igen} = stream2(double(XwPBC),double(YwPBC),-vxXYZPBC,-vyXYZPBC,startgenX,startgenY,[0.03 5e4]);
verticesgen{igen} = cellfun(@(d,u) [flipud(d(2:end,:)); u],verticesgenDOWN{igen},verticesgenUP{igen},'UniformOutput',false);
verticesgen{igen} = cellfun(@(v) v(~isnan(v(:,2)),:),verticesgen{igen},'UniformOutput',false);
verticesgenCELL{igen} = cellfun(@(v) v(validstreamline(v),:),verticesgen{igen},'UniformOutput',false);
verticesgenCELLid{igen} = igen*ones(1,length(verticesgenCELL{igen}));
end
% merge all verticesCELL
verticesCELL = cat(2,verticesgenCELL{:});
verticesCELLid = cat(2,verticesgenCELLid{:});
okverticesCELL = cellfun(@length,verticesCELL)>100; % remove empty and too short streamlines (as a result of filtering by validstreamline)
verticesCELL = verticesCELL(okverticesCELL);
verticesCELLid = verticesCELLid(okverticesCELL);
% retrieve the separation distance
sLagrangian = startgenX(1,2,1) - startgenX(1,1,1); % separation distance between streamlines at injection/starting
rLagrangian = sLagrangian/2;
% detect interruption (for control)
% yfinalposition1UP = cellfun(@(v) v(end,2),vertices1UP);
% yfinalposition1DOWN = cellfun(@(v) v(end,2),vertices1DOWN);
% isinterrupted1 = isnan(yfinalposition1UP) | isnan(yfinalposition1DOWN);
% control plot (saved in R1, partial with decimation)
R1(1).box = box;
R1(1).sLagrangian = sLagrangian;
R1(1).rLagrangian = rLagrangian;
R1(1).ngenerations = ngenerations;
R1(1).nstreamlines = nstreamlines;
R1(1).verticesgen = cell(size(verticesgen));
R1(1).verticesgenCELL = cell(size(verticesgenCELL));
decimation = 100;
for igen = 1:ngenerations
R1(1).verticesgen{igen} = cell(size(verticesgen{igen}));
R1(1).verticesgenCELL{igen} = cell(size(verticesgenCELL{igen}));
for i=1:nstreamlines
R1(1).verticesgen{igen}{i} = verticesgen{igen}{i}(1:decimation:end,:);
R1(1).verticesgenCELL{igen}{i} = verticesgenCELL{igen}{i}(1:decimation:end,:);
end
end
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
figure, hold on
imagesc(R0(1).xwPBC,R0(1).ywPBC,R0(1).vXYZPBC)
colors = jet(R1(1).ngenerations);
for igen = 1:R1(1).ngenerations
for i=1:R1(1).nstreamlines
plot(R1(1).verticesgen{igen}{i}(:,1),R1(1).verticesgen{igen}{i}(:,2),'-','LineWidth',0.5,'color',colors(igen,:))
plot(R1(1).verticesgenCELL{igen}{i}(:,1),R1(1).verticesgenCELL{igen}{i}(:,2),'-','linewidth',1,'color',colors(igen,:));
end
end
axis equal
title(sprintf('t=%0.3f s - streamlines',tframe))
end
%% Distribute beads along streamlines tagged as CELL
% Once PBC are applied, space is filled with possibly overlaping streamlines
% the control plot is showing this effect
dispsection('INFORMED BEADS')
traj = fillstreamline2(verticesCELL,XwPBC,YwPBC,vxXYZPBC,vyXYZPBC,rLagrangian,0);
ntraj = length(traj);
%trajUP = fillstreamline2(verticesUPCELL,XwPBC,YwPBC,vxXYZPBC,vyXYZPBC,rLagrangian,0);
%traj(~isinterrupted) = trajUP(~isinterrupted);
XYZbeads = arrayfun(@(t) t.cart_distribution,traj,'UniformOutput',false);
% we apply PBC while keeping the id of the streamline
[XYZbeadsPBC,XYZbeadsPBCid] = deal(cell(ntraj,1));
for itraj=1:ntraj % trajectories and streamlines are assumed equivalent at steady state
XYZbeadsPBC{itraj} = PBCincell(XYZbeads{itraj},box(1:2,:),[true true]); % wrapping along PBC
XYZbeadsPBCid{itraj} = ones(size(XYZbeadsPBC{itraj},1),1)*verticesCELLid(itraj);
end
% control plots (saved in R1)
R1(1).ntraj = ntraj;
R1(1).XYZbeadsPBC = XYZbeadsPBC;
R1(1).XYZbeadsPBCid = XYZbeadsPBCid;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
figure, hold on
colors = tooclear(jet(R1(1).ntraj));
for itraj = 1:R1(1).ntraj
plot(R1(1).XYZbeadsPBC{itraj}(:,1),R1(1).XYZbeadsPBC{itraj}(:,2),'o',...
'markerfacecolor',colors(itraj,:),'markeredgecolor',colors(itraj,:))
end
title(sprintf('t=%0.3f s - colors match streamline index',tframe))
figure, hold on
colors = tooclear(jet(R1(1).ngenerations));
for itraj = 1:R1(1).ntraj
plot(R1(1).XYZbeadsPBC{itraj}(:,1),R1(1).XYZbeadsPBC{itraj}(:,2),'o',...
'markerfacecolor',colors(R1(1).XYZbeadsPBCid{itraj}(1),:),'markeredgecolor',colors(R1(1).XYZbeadsPBCid{itraj}(1),:))
end
title(sprintf('t=%0.3f s - colors match generation index',tframe))
end
%% remove duplicated beads between streamlines (too close)
% defined as beads from another streamline located at less than rbead
% this version incorporate the effect of the generational distance (somekind of age)
% beads from other injections points should emerge from positions farther from first generations (older ones)
% the generation index starts from the source of the flow (bottom), using streamlines from other positions is discouraged (since younger)
%
% Note: streamlines are indexed from oldest (closest to the source) to youngest (farthest from the source)
dispsection('CLEAN OVERLAPPED BEADS')
% beads from different streamlines
for i=1:ntraj % reference streamline (higher precedence)
for j=1:ntraj % the other streamline
if i~=j
indj = find(~isnan(XYZbeadsPBC{j}(:,1)));
dij=pdist2(XYZbeadsPBC{i},XYZbeadsPBC{j}(indj,:)); % Euclidian distance
% the criterion on age is too strict
%dgenij = pdist2(XYZbeadsPBCid{i},XYZbeadsPBCid{j}(indj,:)); % generational distance (0 for the same generation)
%XYZbeadsPBC{j}(indj(any(dij
XYZbeadsPBC{j}(indj(any(dijend
end
end
% beads from the same streamline
% two populations of beads are created (those inside and those outside (images) that could overlap those inside when they coordinates are wrapped)
%
% Note: we do not consider the overlapping of north and south images (the total height of a streamline is considered not to exceed 150% of the box height)
for i=1:ntraj
isoutside = (XYZbeads{i}(:,1)box(1,2)) | ...
(XYZbeads{i}(:,2)box(2,2) );
isvalid = ~isnan(XYZbeadsPBC{i}(:,1));
ioutside = find(isoutside & isvalid); % these beads are possible images of beads already inside
iinside = find(~isoutside & isvalid); % these beads have higher precedence, they are already inside
divsout=pdist2(XYZbeadsPBC{i}(iinside,:),XYZbeadsPBC{i}(ioutside,:));
XYZbeadsPBC{i}(ioutside(any(divsoutend
XYZbeadsPBCall = cat(1,XYZbeadsPBC{:});
XYZbeadsPBCidall = cat(1,XYZbeadsPBCid{:});
isPBCallok = ~isnan(XYZbeadsPBCall(:,1));
XYZbeadsPBCall = XYZbeadsPBCall(isPBCallok,:);
XYZbeadsPBCidall = XYZbeadsPBCidall(isPBCallok,:);
% control while keeping the generational index (saved in R1)
R1(1).XYZbeadsPBCall = XYZbeadsPBCall;
R1(1).XYZbeadsPBCidall = XYZbeadsPBCidall;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
hfig = fighandle('packing'); hold on
colors = tooclear(hsv(R1(1).ngenerations)); %turbo
for igen=1:R1(1).ngenerations
viscircles(R1(1).XYZbeadsPBCall(R1(1).XYZbeadsPBCidall==igen,:),R1(1).rLagrangian,'color',colors(igen,:));
end
title(sprintf('t=%0.3f s - one color per injection line',tframe)), axis equal
if PRINTON, printhandle(hfig), end
end
%% Filtering: remove overlapping from different injections using a first estimate of density
dispsection('CLEAN BEAD DENSITY')
% Summary
% This methodology effectively redistributes beads to ensure that local densities do not exceed
% the physical limits of the simulation, enhancing realism and accuracy. By adjusting bead
% distributions based on calculated densities and employing PBC to handle edge cases, the script
% ensures a uniformly plausible representation of bead distributions across the simulation domain.
hfilter = 4*rLagrangian;
XYbeadsfilterid = XYZbeadsPBCidall;
XYbeadsfilter = XYZbeadsPBCall;
nfilter = size(XYbeadsfilter,1);
XYbeadsfilter_images= PBCimages(XYbeadsfilter,box(1:2,:),[true,true],2*hfilter);
XYbeadsfilterwithImages = [XYbeadsfilter;XYbeadsfilter_images];
Vbeadsfilter = buildVerletList(XYbeadsfilter,hfilter);
VbeadsfilterBC = buildVerletList({XYbeadsfilter XYbeadsfilterwithImages},hfilter);
Volfilter = length(find(~isnan(rhobeadXYZgrid(:))))/numel(rhobeadXYZgrid) * boxsize(1) * boxsize(2) * s;
mfilter = rho*Volfilter/size(XYbeadsfilter,1); % mass of a single bead (informed)
Vfilter = mfilter/rho;
Wfilter = kernelSPH(hfilter,'lucy',2);
rhofilter = interp2SPHVerlet(...
XYbeadsfilterwithImages,...
rho*ones(size(XYbeadsfilterwithImages,1),1),...
XYbeadsfilter,VbeadsfilterBC,Wfilter,Vfilter)/s;
% target
rhomax = 1500;
ok = rhofilter<=rhomax;
% before (saved in R1)
R1(1).XYbeadsfilter = XYbeadsfilter;
R1(1).ok = ok;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
hfig = fighandle('packing_rhomax'); hold on
viscircles(R1(1).XYbeadsfilter(R1(1).ok,:),R1(1).rLagrangian,'color','b');
viscircles(R1(1).XYbeadsfilter(~R1(1).ok,:),R1(1).rLagrangian,'color','r');
title(sprintf('t=%0.3f s - in red: excess density',tframe)), axis equal
if PRINTON, printhandle(hfig), end
end
% do filter
itoohigh = find(~ok);
[rhotoohigh,ind] = sort(rhofilter(itoohigh),'descend');
itoohigh = itoohigh(ind);
kept = true(nfilter,1);
for k=1:length(itoohigh)
% valid neighbors (only)
jBC = VbeadsfilterBC{itoohigh(k)}; % it includes self with images
njBC = length(jBC);
j = Vbeadsfilter{itoohigh(k)}; % it includes self without images
j = j(kept(j));
[idj,ind] = sort(XYbeadsfilterid(j),'ascend');
j = j(ind); nj = length(j);
jdeletable = j(find(idj>min(idj),1,'first'):end); % we keep the first type
[rhojdeletable,ind] = sort(rhofilter(jdeletable),'descend');
jdeletable = jdeletable(ind); njdeletable = length(jdeletable);
njexcess = floor( (1 - rhomax/rhotoohigh(k)) * njBC );
kept(jdeletable(1:min(njexcess,njdeletable))) = false;
end
% after (saved in R1)
R1(1).kept = kept;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
hfig = fighandle('packing_rhomax_fix'); hold on
viscircles(R1(1).XYbeadsfilter(R1(1).kept,:),R1(1).rLagrangian,'color','g');
viscircles(R1(1).XYbeadsfilter(~R1(1).kept,:),R1(1).rLagrangian,'color','r');
title(sprintf('t=%0.3f s - in red: removed beads',tframe)), axis equal
if PRINTON, printhandle(hfig), end
end
%% calculate density
dispsection('INFORMED DENSITY')
XYinformed = PBCincell(XYZbeadsPBCall(kept,:),box,[true true]); % PBC just to be sure
ninformed = size(XYinformed,1);
rinformed = rLagrangian; %radius
sinformed = 2*rinformed; % separation distance
hinformed = 5*sinformed; % for integration
% add PBC images
[XYinformed_images ,indimages]= PBCimages(XYinformed,box(1:2,:),[true,true],2*hinformed);
XYinformedwithImages = [XYinformed;XYinformed_images];
% Verlet list for the original grid
XYgrid = [Xw(:) Yw(:)];
if isprefetch('VXYinformed')
load(prefetchvar('VXYinformed'))
else
VXYinformed = buildVerletList({XYgrid XYinformedwithImages},hinformed);
save(prefetchvar('VXYinformed'),'VXYinformed')
end
% Verlet list for the informed beads
if isprefetch('VXYbeadinformed')
load(prefetchvar('VXYbeadinformed'))
else
VXYbeadinformed = buildVerletList({XYinformed XYinformedwithImages},hinformed);
save(prefetchvar('VXYbeadinformed'),'VXYbeadinformed')
end
% Calculate the mass of informed beads
% total volume of fluid in the image
Volinformed = length(find(~isnan(rhobeadXYZgrid(:))))/numel(rhobeadXYZgrid) * boxsize(1) * boxsize(2) * s;
mbeadinformed = rho*Volinformed/ninformed; % mass of a single bead (informed)
% 2D Kernel
Winformed = kernelSPH(hinformed,'lucy',2); % kernel expression [/m2]
% Calculate the volume of the beads
Vbeadinformedguess = mbeadinformed/rho; % first guess
% density at the centers of the informed beads
rhobeadinformed = interp2SPHVerlet(XYinformedwithImages,rho*ones(size(XYinformedwithImages,1),1),XYinformed,VXYbeadinformed,Winformed,Vbeadinformedguess);
rhobeadinformed = rhobeadinformed/s; % the thickness is not known, s is used instead of sinformed for consistency
rhobeadinformedwithImages = [rhobeadinformed;rhobeadinformed(indimages)];
% rhobeadinformed(rhobeadinformed<200) = NaN;
% rhobeadinformed(isnan(rhobeadinformed)) = median(rhobeadinformed,'omitmissing');
% volume of the informed beads
Vbeadinformed = mbeadinformed./rhobeadinformed;
% normalized radius of informed beads
rbeadinformed = sqrt(Vbeadinformed/(s*pi));
rbeadinformed = rbeadinformed/median(rbeadinformed,'omitmissing') * rLagrangian;
% control figure (saved in R1)
R1(1).rinformed = rinformed;
R1(1).sinformed = sinformed;
R1(1).hinformed = hinformed;
R1(1).XYinformed = XYinformed;
R1(1).rhobeadinformed = rhobeadinformed;
R1(1).rbeadinformed = rbeadinformed;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
figure, viscircles(R1(1).XYinformed,R1(1).rLagrangian,'color','g'); axis equal, hold on
viscircles(R1(1).XYinformed,R1(1).rbeadinformed,'color','r');
end
% reference density at the same positions (saved in R0)
XYZeqinformed = [XYinformed ones(ninformed,1)*ztop];
VXYZeqinformed = buildVerletList({XYZeqinformed XYZwithImages},1.2*h);
rhobeadXYZ2XYinformed = interp3SPHVerlet(XYZwithImages,rhobeadXYZwithImages,XYZeqinformed,VXYZeqinformed,W,VbeadXYZwithImages);
R0(1).XYZeqinformed = XYZeqinformed;
R0(1).rhobeadXYZ2XYinformed = rhobeadXYZ2XYinformed;
% density on the grid (saved in R1)
if isprefetch('dgrid')
load(prefetchvar('dgrid'))
else
% note that rho and Vbeadinformedguess should have the same origin so that rho*Vbeadinformedguess equals mbeadinformed
dgrid = interp2SPHVerlet(XYinformedwithImages,rho*ones(size(XYinformedwithImages,1),1),XYgrid,VXYinformed,Winformed,Vbeadinformedguess);
save(prefetchvar('dgrid'),'dgrid')
end
dgrid = reshape(dgrid/s,size(Xw)); % /s to get in kg/m3 from kg/m2
dgrid(isnan(rhobeadXYZgrid)) = NaN; % we mask the objects (pillar and sphere) the same way as in the reference
R1(1).dgrid = dgrid;
%% Evaluate Landshoff forces - added 29,30/03/2024
dispsection('LANDSHOFF')
% reference velocity
XYZinformed = [XYinformed ones(size(XYinformed,1),1)*ztop];
VXYZ_forinformed = buildVerletList({XYZinformed XYZwithImages},1.2*hinformed);
if isprefetch('Velocity_beadinformed')
load(prefetchvar('Velocity_beadinformed'))
else
Velocity_beadinformed = interp3SPHVerlet(XYZwithImages,vXYZwithImages,XYZinformed,VXYZ_forinformed,W,VbeadXYZwithImages);
save(prefetchvar('Velocity_beadinformed'),'Velocity_beadinformed')
end
Velocity_beadinformed_withimages = [Velocity_beadinformed;Velocity_beadinformed(indimages,:)];
% configuration for Landshoff
% Since the scaling is not clear in 2D, q1 is left to one
% maxVelocity = max(vXYZ,[],'all');
% MachTarget = 0.1;
c0 = 1500; % maxVelocity / MachTarget;
dynamicViscosity = 0.13; % Pa.s
q1 = 1; % 8 * dynamicViscosity / (hinformed*c0*rho);
config = struct( ...real dynamic viscosity: rho * q1 * h * c0 / 8 (2D) or 10 (3D)
'gradkernel', kernelSPH(hinformed,'lucyder',2),...% kernel gradient (note that h is bound with the kernel)
'h', hinformed,...smoothing length
'c0',c0,... speed of the sound
'q1',q1,... constant
'rho', rhobeadinformedwithImages, ... fluid density
'mass', mbeadinformed,... bead weight
'vol', sinformed * mbeadinformed./rhobeadinformedwithImages, ... bead volume (uniquely for virial stress)
'repulsiononly', false ... if true, only Landshoff forces when dot(rij,vij)<0
);
% Landshoff forces
VXYZ_forinformedwithImages = buildVerletList(XYinformedwithImages,1.2*hinformed);
[FXY_tmp,WXY_tmp] = forceLandshoff(...
XYinformedwithImages,...
Velocity_beadinformed_withimages(:,1:2),...
VXYZ_forinformedwithImages,...
config);
FXY_beadsinformedwithImages = [FXY_tmp(1:size(XYinformed,1),:); FXY_tmp(indimages,:)];
WXY_beadsinformedwithImages = [WXY_tmp(1:size(XYinformed,1),:); WXY_tmp(indimages,:)];
if isprefetch('FXYgrid')
load(prefetchvar('FXYgrid'))
else
FXYgrid = interp2SPHVerlet(XYinformedwithImages,FXY_beadsinformedwithImages,XYgrid,VXYinformed,Winformed,Vbeadinformedguess);
save(prefetchvar('FXYgrid'),'FXYgrid')
end
if isprefetch('WXYgrid')
load(prefetchvar('WXYgrid'))
else
WXYgrid = interp2SPHVerlet(XYinformedwithImages,WXY_beadsinformedwithImages,XYgrid,VXYinformed,Winformed,Vbeadinformedguess);
save(prefetchvar('WXYgrid'),'WXYgrid')
end
F1XYgrid = reshape(FXYgrid(:,1),size(Xw));
F2XYgrid = reshape(FXYgrid(:,2),size(Xw));
W11XYgrid = reshape(WXYgrid(:,1),size(Xw));
W22XYgrid = reshape(WXYgrid(:,4),size(Xw));
W12XYgrid = reshape(WXYgrid(:,2),size(Xw));
W21XYgrid = reshape(WXYgrid(:,3),size(Xw));
F1XYgrid(isnan(rhobeadXYZgrid)) = NaN;
F2XYgrid(isnan(rhobeadXYZgrid)) = NaN;
W11XYgrid(isnan(rhobeadXYZgrid)) = NaN;
W22XYgrid(isnan(rhobeadXYZgrid)) = NaN;
W12XYgrid(isnan(rhobeadXYZgrid)) = NaN;
% save results in R1
R1(1).xw = xw;
R1(1).yw = yw;
R1(1).Xw = Xw;
R1(1).Yw = Yw;
R1(1).F1XYgrid = F1XYgrid;
R1(1).F2XYgrid = F2XYgrid;
R1(1).W11XYgrid = W11XYgrid;
R1(1).W22XYgrid = W22XYgrid;
R1(1).W12XYgrid = W12XYgrid;
R1(1).W21XYgrid = W21XYgrid;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
% distribution of Landshoff forces (not meaningful on a grid) - just for control
figure, hold on
hp = pcolor(R1(1).xw,R1(1).yw,abs(R1(1).W12XYgrid)); hp.EdgeColor = 'none'; colorbar
step = 25;
quiver(R1(1).Xw(1:step:end,1:step:end),R1(1).Yw(1:step:end,1:step:end),...
R1(1).F1XYgrid(1:step:end,1:step:end),R1(1).F2XYgrid(1:step:end,1:step:end),2,'color','k','LineWidth',0.5)
title(sprintf('[t=%0.3g s] informed shear stress (kg/m3)',tframe)), axis equal %, clim([500 1300])
% distribution of Landshoff stresses
hfig = fighandle('Landshoff_stress'); hold on
hp = pcolor(R1(1).xw,R1(1).yw,abs(R1(1).W12XYgrid)); hp.EdgeColor = 'none'; colorbar
step = 25;
quiver(R1(1).Xw(1:step:end,1:step:end),R1(1).Yw(1:step:end,1:step:end),...
R1(1).W11XYgrid(1:step:end,1:step:end),R1(1).W22XYgrid(1:step:end,1:step:end),2,'color','k','LineWidth',0.5)
title(sprintf('[t=%0.3g s] Landshoff - shear (abs(Lxy)) - Lxx,Lyy (Pa)',tframe)), axis equal %, clim([500 1300])
end
% gradient of velocity (saved in R1)
[gxx,gxy] = gradient(vxXYZgrid,Xw(1,2)-Xw(1,1));
[gyx,gyy] = gradient(vyXYZgrid,Yw(2,1)-Yw(1,1));
R1(1).gxx = gxx;
R1(1).gxy = gxy;
R1(1).gyx = gyx;
R1(1).gyy = gyy;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
figure, hold on, title('Gradient of velocity: xx, yy and xy')
hp = pcolor(R1(1).xw,R1(1).yw,abs(R1(1).gxy+R1(1).gyx)/2); hp.EdgeColor = 'none'; colorbar
step = 25;
quiver(R1(1).Xw(1:step:end,1:step:end),R1(1).Yw(1:step:end,1:step:end),...
R1(1).gxx(1:step:end,1:step:end),R1(1).gyy(1:step:end,1:step:end),2,'color','k','LineWidth',0.5)
end
% Viscosity control (saved in R1)
r = abs(W12XYgrid./gxy); r(isinf(r)) = NaN;
dynamicViscosity_estimated = r/(sinformed*rho);
R1(1).r = r;
R1(1).dynamicViscosity = dynamicViscosity;
R1(1).dynamicViscosity_estimated = dynamicViscosity_estimated;
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
% map
hfig(end+1) = fighandle('Viscosity_estimate'); hold on
imagesc(log10(R1(1).dynamicViscosity_estimated/R1(1).dynamicViscosity)), colorbar, clim([-2 2])
title(sprintf('[t=%0.3g s] Estimated/Real Viscosity (log)',tframe)), axis equal
% distribution
hfig(end+1) = fighandle('Viscosity_estimate_dist'); hold on
histogram(log10(R1(1).dynamicViscosity_estimated/R1(1).dynamicViscosity))
title(sprintf('[t=%0.3g s] Estimated/Real Viscosity (log)',tframe))
end
% print
if PRINTON && PLOTON
for i=1:length(hfig)
figure(hfig(i)), drawnow
printhandle(hfig(i))
end
end
%% ---------------------------- PRODUCTION FIGURES
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1 and R0) **********
hfig = []; close all
% Density mapped on beads
hfig(end+1) = fighandle('d_bead_informed'); hold on, title(sprintf('[t=%0.3g s] density mapped to informed beads',tframe))
scatter(R1(1).XYinformed(:,1),R1(1).XYinformed(:,2),100*(R1(1).rbeadinformed/min(R1(1).rbeadinformed)).^2,R1(1).rhobeadinformed,'filled'), colorbar
hfig(end+1) = fighandle('d_bead_reference'); hold on, title(sprintf('[t=%0.3g s] reference density mapped to informed beads',tframe))
scatter(R0(1).XYZeqinformed(:,1),R0(1).XYZeqinformed(:,2),100*(R1(1).rbeadinformed/min(R1(1).rbeadinformed)).^2,R1(1).rhobeadXYZ2XYinformed,'filled'), colorbar
hfig(end+1) = fighandle('d_bead_informed_vs_reference'); plot(R0(1).rhobeadXYZ2XYinformed(:),R1(1).rhobeadinformed(:),'ro'), xlabel('reference density (back mapped)'), ylabel('informed density')
title(sprintf('[t=%0.3g s] informed vs. reference density',tframe))
% density mapped on the grid
hfig(end+1) = fighandle('d_grid_informed'); hp = pcolor(R1(1).xw,R1(1).yw,R1(1).dgrid); hp.EdgeColor = 'none'; colorbar, title(sprintf('[t=%0.3g s] informed density (kg/m3)',tframe)), axis equal %, clim([500 1300])
haxtomodify = gca;
hfig(end+1) = fighandle('d_grid_reference'); hp = pcolor(R1(1).xw,R1(1).yw,R0(1).rhobeadXYZgrid); hp.EdgeColor = 'none'; colorbar, title(sprintf('[t=%0.3g s] reference density (kg/m3)',tframe)), axis equal %, clim([500 1300])
clim(haxtomodify,clim)
% distribution of densities
hfig(end+1) = fighandle('distribution_rho'); hold on
histogram(R1(1).rhobeadinformed), histogram(R0(1).rhobeadXYZ2XYinformed), legend({'informed density','reference density'})
title(sprintf('[t=%0.3g s] Density distributions',tframe))
end
%% -- identify the beads in contact with objects based on density
% SUMMARY.
% This section outlines a methodology for identifying beads in contact with solid objects
% in a fluid simulation, particularly focusing on density-based criteria to determine close
% interactions and potential contact forces.
dispsection('OBJECT DETECTION')
% Attention the sphere can exceed the area observed (overflow condition)
% Safe coordinates are only use for contour identification
isynan = isnan(mean(rhobeadXYZgrid(:,floor(size(rhobeadXYZgrid,2)*0.6):end),2));
isoverflow = (find(isynan,1,'last')-find(isynan,1,'first')+1)==size(rhobeadXYZgrid,1);
if (find(isynan,1,'last')-find(isynan,1,'first')+1)==size(rhobeadXYZgrid,1) % reach top
iyoverflow = find(~isynan,1,'first');
iymargin = round(size(rhobeadXYZgrid,1)*0.1); % margin to add
iysafe = iyoverflow+iymargin; % index translation (along y)
dysafe = -iysafe * ( Yw(2,1)-Yw(1,1) ); % distance translation (along y)
[rXYgridsafe,Xwsafe,Ywsafe] = PBCgridshift(Xw,Yw,rhobeadXYZgrid,[0 iysafe]); % translate
dgridsafe = PBCgridshift(Xw,Yw,dgrid,[0 iysafe]); % translate
[XYinformedsafe,boxsafe] = PBCimagesshift(XYinformed,[0 dysafe],box(1:2,1:2));
elseif find(isynan,1,'first')==1
iymargin = round(size(rhobeadXYZgrid,1)*0.1); % margin to add
iyoverflow = iymargin;
if isynan(end), iyoverflow = iyoverflow + length(iyoverflow)-find(~isynan,1,'last'); end
iysafe = -iyoverflow;
dysafe = -iysafe * ( Yw(2,1)-Yw(1,1) ); % distance translation (along y)
[rXYgridsafe,Xwsafe,Ywsafe] = PBCgridshift(Xw,Yw,rhobeadXYZgrid,[0 iysafe]); % translate
dgridsafe = PBCgridshift(Xw,Yw,dgrid,[0 iysafe]); % translate
[XYinformedsafe,boxsafe] = PBCimagesshift(XYinformed,[0 dysafe],box(1:2,1:2));
else
dysafe = 0;
[rXYgridsafe,Xwsafe,Ywsafe] = deal(rhobeadXYZgrid,Xw,Yw);
[dgridsafe,XYinformedsafe,boxsafe] = deal(dgrid,XYinformed,box);
end
% Volume of objects (GRID points): XYobjects where density is NaN vy definition
XYobjects = [Xwsafe(:) Ywsafe(:)];
XYobjects = XYobjects(isnan(rXYgridsafe),:);
% Looking for beads in contact with XYobjects (grid points)
V2 = buildVerletList({XYinformedsafe XYobjects},3*sinformed);
% Index of beads in contact with XYobjects (grid points)
icontact = find(cellfun(@length,V2)>0);
XYcontact = double(XYinformedsafe(icontact,:));
% Enclosed beads
k = boundary(XYcontact,1); nk = length(k);
XYcontactk = XYcontact(k,:);
% separation of the objects, contour parameterization, tangents, normals
% the pillar is assumed fixed
pillarcenter = [2.87,3.08]*1e-4; % 0.2880e-3 0.3131e-3, mean(objects(1).XY)
ispillar = vecnorm(XYcontactk - pillarcenter,2,2)<0.8e-4; % 7.7859e-05, max(vecnorm(XYcontactk(ispillar,:) - pillarcenters,2,2))
objects = struct('name',{'pillar','sphere'},'XY',{XYcontactk(ispillar,:) XYcontactk(~ispillar,:)},'marker',{'bo','ms'},'line',{'b-','m-'},'color',{'b','m'});
anglestep = pi/32;
angles = (-pi:anglestep:pi)';
for k = 1:length(objects)
% fit each object as a circle to try to close the boundaries
objects(k).fit = fitCircleFromPoints(objects(k).XY);
missingangles = angles(abs(angles-objects(k).fit.angles(nearestpoint(angles,objects(k).fit.angles)))>anglestep);
XYtoadd = objects(k).fit.XYgenerator(missingangles);
XYclosed = [objects(k).XY;XYtoadd]; nXYclosed = floor(size(XYclosed,1)/2)*2;
[anglesclosed,ind] = sort(objects(k).fit.angleGenerator(XYclosed));
XYclosed = XYclosed (ind,:); % sort points according to angles
% enforce periodic conditions along the index dimension
objects(k).XYclosed = [XYclosed((nXYclosed/2+1):end,:) ; XYclosed ; XYclosed(1:(nXYclosed/2),:)];
objects(k).n = size(objects(k).XYclosed,1);
% First pass - contour twice longer than the real one
% fit each obhect using a regularized spline approximation
sp = csaps((1:objects(k).n)',objects(k).XYclosed',0.25); % smoothed cubic spline
%sp = spaps((1:objects(k).n)',objects(k).XYclosed',0.001*sum(var(objects(k).XYclosed))*objects(k).n);
spder = fnder(sp,1);
% Second pass - approximation based on angles
xycontours = fnval(sp,sp.breaks)';
dxycontours = fnval(spder,sp.breaks)';
xycircle = fitCircleFromPoints(xycontours);
[xyangles,ind] = sort(xycircle.angles,'ascend');
minangle = min(xyangles);
maxangle = max(xyangles);
xyangles = (2*(xyangles-minangle)/(maxangle-minangle)-1) * pi; % rescaled to close the figure
xycontoursf = [ smooth(xyangles,xycontours(ind,1),0.15,'rloess'),...
smooth(xyangles,xycontours(ind,2),0.15,'rloess') ];
dxycontoursf = [ smooth(xyangles,dxycontours(ind,1),0.15,'rloess'),...
smooth(xyangles,dxycontours(ind,2),0.15,'rloess') ];
% Make all data unique
[xyangles,ind] = unique(xyangles,'stable');
xycontoursf = xycontoursf(ind,:);
dxycontoursf = dxycontoursf(ind,:);
xycontoursf([1 end],:) = [1;1] * mean(xycontoursf([1 end],:));
dxycontoursf([1 end],:) = [1;1] * mean(dxycontoursf([1 end],:));
% Third Pass using contour length instead of angle as variable
% ATTENTION: for averaging it is preferable to use curvilinear coordinates
lxycontour = [0; cumsum(sqrt(sum(diff(xycontoursf,1,1).^2,2)))];
sp2 = csaps(lxycontour',xycontoursf');
% Extract tangents at prescribed curvilinear coordinates
xp = linspace(lxycontour(1),lxycontour(end),objects(k).n)';
% center and apparent radius (for visualizing the force acting on the object)
objects(k).XYboundary = fnval(sp2,xp)';
objects(k).center = mean(objects(k).XYboundary);
objects(k).radius = min(vecnorm(objects(k).XYboundary-objects(k).center,2,2));
% tangents (from the spline approximation)
%objects(k).tangents = ndf(xp,objects(k).XYboundary,1,[],'makeuniform',true); %curve2tangent(objects(k).XYboundary); %fnval(fnder(sp2,1),xp)';
objects(k).tangents = fnval(fnder(sp2,1),xp)';
objects(k).tangents = objects(k).tangents./vecnorm(objects(k).tangents,2,2); % Normalize the tangent vectors
% normals (turn the normals to be inwards using inpolygon)
objects(k).normals = [-objects(k).tangents(:,2),objects(k).tangents(:,1)]; % % Calculate the centroid of the shape
testPoints = objects(k).XYboundary + 0.01 * objects(k).radius * objects(k).normals; % Choose a test point slightly offset from each boundary point along the normal
isInside = inpolygon(testPoints(:,1), testPoints(:,2),...
objects(k).XYboundary(:,1), objects(k).XYboundary(:,2)); % Use inpolygon to check if each test point is inside the shape
objects(k).normals(~isInside, :) = -objects(k).normals(~isInside, :); % Flip the normals where the test point is outside the shape
% add density, pressure information (look for the nearest beads closest to the boundary, average the value)
objects(k).Vcontact = buildVerletList({objects(k).XYboundary XYcontact},3*sinformed);
objects(k).Vcontact = cellfun(@(v) icontact(v)',objects(k).Vcontact,'UniformOutput',false); % Vcontact{i} index of beads in contact with XYboundary(i,:)
objects(k).rhocontact = cellfun(@(v) mean(rhobeadinformed(v)),objects(k).Vcontact);
objects(k).Pcontact = 1 + (objects(k).rhocontact/rho).^7-1; % reduced pressure P/B with P0/B=1
objects(k).densitynormals = objects(k).rhocontact.*objects(k).normals/rho;
objects(k).avdensitynormals = mean(objects(k).densitynormals,'omitmissing');
objects(k).pressurenormals = objects(k).Pcontact.*objects(k).normals;
objects(k).avpressurenormals = mean(objects(k).pressurenormals,'omitmissing');
end
% save results in R1
R1(1).XYcontact = XYcontact;
R1(1).objects = {objects.name};
for k=1:length(objects)
R1(1).(objects(k).name) = objects(k);
end
R1(1).Xwsafe = Xwsafe;
R1(1).Ywsafe = Ywsafe;
R1(1).dgridsafe = dgridsafe;
% plot
if PLOTON % <<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-<<-(saved in R1) **********
hfig(end+1) = fighandle('distribution_forces'); hold on
set(hfig(end),'PaperPosition',[ -2.0000 1.7000 30.0000 30.0000])
step = 2;
hp = pcolor(Xwsafe(1,:),Ywsafe(:,1),dgridsafe); hp.EdgeColor = 'none'; colorbar
quiv = struct('xy',[],'nxy',[],'mag',[],'nscale',2);
for k = 1:length(R1(1).objects)
currentobject = R1(1).objects{k};
% viscircles(R1(1).XYcontact,R1(1).rinformed,'color','r');
% plot(R1(1).(currentobject).XY(:,1),R1(1).(currentobject).XY(:,2),R1(1).(currentobject).marker)
plot(R1(1).(currentobject).XYboundary(:,1),R1(1).(currentobject).XYboundary(:,2),R1(1).(currentobject).line,'linewidth',2,'marker','x')
quiv.xy = [quiv.xy; R1(1).(currentobject).XYboundary(1:step:end,:); R1(1).(currentobject).center];
quiv.nxy = [quiv.nxy; R1(1).(currentobject).pressurenormals(1:step:end,:); quiv.nscale*R1(1).(currentobject).avpressurenormals];
quiv.mag = [quiv.mag; R1(1).(currentobject).Pcontact(1:step:end);mean(R1(1).(currentobject).Pcontact,'omitmissing')];
end
% all quivers at once (color
hq = quiver(quiv.xy(:,1),quiv.xy(:,2),quiv.nxy(:,1),quiv.nxy(:,2),3,'color','k','LineWidth',0.5);
SetQuiverColor(hq,tooclear(jet),'mags',quiv.mag,'range',[0 0.5])
axis equal, title(sprintf('[t=%0.3g s] Pressure around objects',tframe))
end
%% print
if PRINTON && PLOTON
for i=1:length(hfig)
figure(hfig(i)), drawnow
printhandle(hfig(i))
end
end
% save
if ~exist(savefile(),'file') || OVERWRITE
save(savefile(),'R0','R1')
dispf('\n-->Results for tframe=%0.4g s have been saved',tframe)
fileinfo(savefile())
end
dispsection('100% complete')
% relative pressure via Tait equation
% Pref = (rhobeadXYZgrid./rho).^7 - 1; % reference pressure
% Pbead = (rhobeadinformed./rho).^7 - 1; % pressure at kernel positions
% Pgrid = (dgrid./rho).^7 - 1; % pressure at grid nodes
% Pgrid = min(Pref(:)) + (max(Pref(:))-min(Pref(:)))*(Pgrid-min(Pgrid(:)))/(max(Pgrid(:))-min(Pgrid(:)));
% figure, imagesc(xw,yw,Pgrid), colorbar, title('dimensionless pressure (-)'), axis equal
% clim([-1 3]), colorbar
% figure, imagesc(xw,yw,Pref), colorbar, title('reference dimensionless pressure (-)'), axis equal
% clim([-1 3]), colorbar
% figure, scatter(XYinformed(:,1),XYinformed(:,2),rbeadinformed,Pbead,'filled')
####################################################################################
This is fork of the main script Billy_reusults_template_PBC
To be used for testing alternative parameters on a non main stream machine
####################################################################################
% ####################################################################################
% This is fork of the main script Billy_reusults_template_PBC
% To be used for testing alternative parameters on a non main stream machine
% ####################################################################################
% First template to retrieve Billy's paper 2 simulation data
% rev. 2024/03/17 - forked in 2024/03/16 with PBC
% 2024/03/07 implementation of reverse streamlines, streamline binning from their initial positions, subsampling
% 2024/03/12 implement bead along streamlines (bug:NaN and Inf not allowed.)
% 2024/03/16 fork from Billy_results_template.m (MAJOR UPDATE)
% 2024/03/17 release candidate
% 2024/03/21 preproduction
% 2024/03/24 major update: full implementation of density correction and force contours around objects
% 2024/03/24 selective copy possible if originalroot is made available
% 2024/03/24 add prefetch management
% 2024/03/25 first batch of preproduction tframe 0.3->1.1 (step 0.01)
% 2024/03/26 second batch of preproduction tframe 0.11->0.4 (step 0.01)
% 2024/03/27 fix streamlines when less than one bead must be added (tframe = 0.11 and 0.27)
% 2024/03/27 fix contour, tangents/normals, add perssure
% 2024/03/27 Senstivity challenge test using ngenerations = 8 and xshift = double(mod(igen-1,2)*(xwPBC(indxstreamline(2))-xwPBC(indxstreamline(1))));
% SUMMARY
% This MATLAB script is a comprehensive framework for analyzing fluid dynamics simulations, specifically focusing on the distribution and movement of particles or beads in a fluid environment. Key functionalities include:
%
% 1. Environment Setup:
% Initializes the simulation environment by clearing variables, setting up output folders, and defining file paths to simulation data, with adjustments for periodic boundary conditions (PBC).
% 2. Data Retrieval:
% Loads simulation data from specified file paths, handling different configurations and viscosity models.
% 3. Simulation Analysis:
% Processes the simulation data to calculate parameters such as bead sizes, timestep intervals, and spatial distributions of particles.
% 4. Frame Selection:
% Identifies simulation frames for detailed analysis based on time criteria, with capabilities to adjust for available data subsets.
% 5. Velocity Field and Density Calculation:
% Computes the velocity field at a specific plane in the simulation domain and estimates the local density of particles.
% 6. Streamline and Bead Distribution Analysis:
% Generates streamlines and distributes beads along these lines, incorporating PBC adjustments to simulate continuous fluid flow across boundaries.
% 7. Overlap Removal:
% Implements algorithms to remove overlapping beads based on their spatial proximity and streamline generation, ensuring a realistic particle distribution.
% 8. Density Filtering:
% Filters out beads in regions of excessively high simulated density to adhere to physical constraints.
% 9. Contact Detection with Objects:
% Identifies beads in contact with solid objects within the simulation, leveraging density information to infer interaction dynamics.
% 10. Visual Representation:
% Provides extensive plotting capabilities to visualize various aspects of the simulation, including velocity fields, bead distributions, and pressure around objects, with options to export figures.
%% Definitions
close all
clearvars -except tframe tframelist RESETPREFETCH
outputfolder = fullfile(pwd,'preproduction');
prefetchfolder = fullfile(pwd,'prefetch');
if ~exist(outputfolder,'dir'), mkdir(outputfolder); end
if ~exist(prefetchfolder,'dir'), mkdir(prefetchfolder); end
fighandle = @(id) formatfig(figure,'figname',sprintf('t%0.3g_%s',tframe,id));
printhandle = @(hfig) print_png(300,fullfile(outputfolder,[get(hfig,'filename') '.png']),'','',0,0,0);
if ~exist('RESETPREFETCH','var'), RESETPREFETCH = false; end
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
%% path and metadata
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
%% extract information
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
%% copy files (if possible)
if copymode
myprefetchfile = @(itime) sprintf('%s%09d.mat','TIMESTEP_',itime);
myprefetchfolder = @(d) fullfile(d,sprintf('PREFETCH_%s',lastdir(dumpfile)));
destinationfolder = fullfile(rootlocal,rootdir(simfolder.(config).(viscosity)));
sourcefolderPREFETCH = myprefetchfolder(sourcefolder);
destinationfolderPREFETCH = myprefetchfolder(destinationfolder);
tcopy = 0.1:0.01:1.1;
tfile = arrayfun(@(t) myprefetchfile(t), timesteps(unique(nearestpoint(tcopy,times))),'UniformOutput',false);
oksource = all(cellfun(@(f) exist(fullfile(sourcefolderPREFETCH,f),'file'),tfile));
if ~oksource, error('the source folder is corrupted, please check'), end
existingfiles = cellfun(@(f) exist(fullfile(destinationfolderPREFETCH,f),'file'),tfile);
dispf('Number of files to copy: %d (%d already available)',length(find(~existingfiles)),length(find(existingfiles)))
copysuccess = cellfun(@(f) copyfile(fullfile(sourcefolderPREFETCH,f),fullfile(destinationfolderPREFETCH,f)),tfile(~existingfiles));
dispf('%d of %d files have been copied',length(find(copysuccess)),length(copysuccess));
end
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
if isprefetch('verletList')
load(prefetchvar('verletList'))
else
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff);
save(prefetchvar('verletList'),'verletList','cutoff','dmin','config','dist')
end
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.1:0.01:1.1; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
fluidxyz = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords};
fluidid = X0.ATOMS{Xframe.ATOMS.isfluid,'id'};
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,coords};
pillarid = X0.ATOMS{Xframe.ATOMS.ispillar,'id'};
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
sphereid = X0.ATOMS{Xframe.ATOMS.issphere,'id'};
solidxyz = Xframe.ATOMS{Xframe.ATOMS.issolid,coords};
solidid = X0.ATOMS{Xframe.ATOMS.issolid,'id'};
ztop = max(pillarxyz(:,3)); % pillar top
%% Interpolate velocity field at z = ztop
% full box (note that atoms may be outside of this box)
box = Xframe.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box,1,2);
% fluidbox (box for atoms to consider)
xmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
xmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
ymin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
ymax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
zmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
zmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
fluidbox = [xmin xmax;ymin ymax;zmin zmax];
% restrict interpolation to the viewbox
viewbox = fluidbox; viewbox(3,:) = [ztop-2*h ztop+2*h];
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& Xframe.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xframe.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
XYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZs = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.issolid,coords}; % solid kernel centers
rhobeadXYZ = Xframe.ATOMS.c_rho_smd(insideviewbox & Xframe.ATOMS.isfluid); % volume of the bead
% force incell (this step is needed to apply PBC)
XYZ = PBCincell(XYZ,box,[true,true,false]);
XYZs = PBCincell(XYZs,box,[true,true,false]);
% add PBC images
[XYZimagesONLY ,indXimagesONLY]= PBCimages(XYZ,box,[true,true,false],2*h);
figure, hold on, plot3D(XYZ,'go')
plot3D(XYZ(indXimagesONLY,:),'go','markerfacecolor','g')
plot3D(XYZimagesONLY,'ro','markerfacecolor','r'), view(3), axis equal, view(2)
XYZwithImages = [XYZ;XYZimagesONLY];
vXYZwithImages = [vXYZ;vXYZ(indXimagesONLY,:)];
rhobeadXYZwithImages = [rhobeadXYZ;rhobeadXYZ(indXimagesONLY)];
VbeadXYZwithImages = mbead./rhobeadXYZwithImages;
isImages = true(size(XYZwithImages,1),1); isImages(1:size(XYZ,1))=false;
% interpolation grid (central grid, no images)
nresolution = [1024 1024 1];
xw = linspace(box(1,1),box(1,2),nresolution(1));
yw = linspace(box(2,1),box(2,2),nresolution(1));
zw = ztop;
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
% grid neighbors (incl. images) for interpolation and discarding grid points overlapping solid
if isprefetch('VXYZ')
load(prefetchvar('VXYZ'))
else
VXYZ = buildVerletList({XYZgrid XYZwithImages},1.2*h); % neighbors = fluid particles
VXYZs = buildVerletList({XYZgrid XYZs},0.85*s); % neighbors = solid particles
save(prefetchvar('VXYZ'),'VXYZ','VXYZs')
end
icontactsolid = find(cellfun(@length,VXYZs)>0);
VXYZ(icontactsolid) = repmat({[]},length(icontactsolid),1);
% interpolation on the grid of the 3D velocity (central grid)
% Do not forget even if we are applying a 2D interpretation, we use a 3D simulation
% 3D velocity are projected on 2D streamlines even if the projection of the 3rd component is 0
W = kernelSPH(h,'lucy',3); % kernel expression
if isprefetch('v3XYZgrid')
load(prefetchvar('v3XYZgrid'))
else
v3XYZgrid = interp3SPHVerlet(XYZwithImages,vXYZwithImages,XYZgrid,VXYZ,W,VbeadXYZwithImages);
save(prefetchvar('v3XYZgrid'),'v3XYZgrid')
end
vxXYZgrid = reshape(v3XYZgrid(:,1),size(Xw)); %vxXYZgrid(isnan(vxXYZgrid)) = 0;
vyXYZgrid = reshape(v3XYZgrid(:,2),size(Xw)); %vyXYZgrid(isnan(vyXYZgrid)) = 0;
vzXYZgrid = reshape(v3XYZgrid(:,3),size(Xw)); %vzXYZgrid(isnan(vzXYZgrid)) = 0;
vXYZgrid = reshape(sqrt(sum(v3XYZgrid.^2,2)),size(Xw));
% density on the grid (with a possible different h)
if isprefetch('rhobeadXYZgrid')
load(prefetchvar('rhobeadXYZgrid'))
else
rhobeadXYZgrid = interp3SPHVerlet(XYZwithImages,rhobeadXYZwithImages,XYZgrid,VXYZ,W,VbeadXYZwithImages);
save(prefetchvar('rhobeadXYZgrid'),'rhobeadXYZgrid')
end
rhobeadXYZgrid = reshape(rhobeadXYZgrid,size(Xw));
% add PBC (add periodic PBC)
[vxXYZPBC,XwPBC,YwPBC] = PBCgrid(Xw,Yw,vxXYZgrid,[true,true],boxsize(1:2)./[8;2]);
rhobeadXYZPBC = PBCgrid(Xw,Yw,rhobeadXYZgrid,[true,true],boxsize(1:2)./[8;2]);
xwPBC = XwPBC(1,:); ywPBC = YwPBC(:,1)';
fluidboxPBC = double([xwPBC([1 end]);ywPBC([1 end]);fluidbox(3,:)]);
fluidboxsizePBC = diff(fluidboxPBC,1,2);
vyXYZPBC = PBCgrid(Xw,Yw,vyXYZgrid,[true,true],boxsize(1:2)./[8;2]);
vzXYZPBC = PBCgrid(Xw,Yw,vzXYZgrid,[true,true],boxsize(1:2)./[8;2]);
vXYZPBC = sqrt(vxXYZPBC.^2 + vyXYZPBC.^2 + vzXYZPBC.^2);
figure, mesh(XwPBC,YwPBC,vXYZPBC), axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - velocity (m/s)',tframe))
figure, mesh(XwPBC,YwPBC,rhobeadXYZPBC); axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - density (kg/m3)',tframe))
%% Plot (attention 2D, meshgrid convention)
figure, hold on
% velocity magnitude
imagesc(xwPBC,ywPBC,vXYZPBC)
axis tight, axis equal
colorbar
title(sprintf('t=%0.3f s - velocity (m/s)',tframe))
% quiver configuration and plot (boundaries are with PBC)
stepPBC = 16;
boundariesPBC = ...
[ nearestpoint(double(xwPBC([1,end]))+fluidboxsizePBC(1)/100*[1 -1],double(xwPBC)) % x
nearestpoint(double(ywPBC([1,end]))+fluidboxsizePBC(2)/100*[1 -1],double(ywPBC)) % y
];
indxquiver = boundariesPBC(1,1):stepPBC:boundariesPBC(1,2); % x indices
indyquiver = boundariesPBC(2,1):stepPBC:boundariesPBC(2,2); % y indices
quiver(XwPBC(indyquiver,indxquiver),YwPBC(indyquiver,indxquiver), ...
vxXYZPBC(indyquiver,indxquiver),vyXYZPBC(indyquiver,indxquiver), ...
'color','k','LineWidth',1)
%% streamlines
% UP from bottom (starting position) to top
% DOWN from top to bottom (same starting position by reversing the velocity field)
% assembled as [flipud(DOWN(2:end,:));UP]
% CELL streamlines along a y period, the separation distance is added to avoid gap
% streamline configuration and plot
boundaries = [ nearestpoint(double(xw([1,end])),double(xwPBC))
nearestpoint(double(yw([1,end])),double(ywPBC))
];
step = 8;
indxstreamline = boundaries(1,1):step:boundaries(1,2);
indystreamline = boundaries(2,1):step:boundaries(2,2);
nstreamlines = length(indxstreamline);
% Generative streamlines
ngenerations = 4*2;
iygen = unique(round(linspace(indystreamline(1),indystreamline(end),ngenerations)));
ngenerations = length(iygen);
[verticesgenUP,verticesgenDOWN,verticesgen,verticesgenCELL,verticesgenCELLid] = deal({});
validstreamline = @(v) (v(:,2)-v(1,2))<=(boxsize(2)*1.5) & (v(:,2)-v(1,2))>=(boxsize(2)*0.01); % longer than 1% but not exceeding 150%
for igen = 1:ngenerations
% ith generation of stream line (from indystreamline(1))
dispf('generates the %dth of %d generation of streamlines',igen,ngenerations)
xshift = double(mod(igen-1,2)*(xwPBC(indxstreamline(2))-xwPBC(indxstreamline(1))));
[startgenX,startgenY,startgenZ] = meshgrid(...
double(xwPBC(indxstreamline))+xshift,...
double(ywPBC(iygen(igen))),double(ztop));
verticesgenUP{igen} = stream2(double(XwPBC),double(YwPBC),vxXYZPBC,vyXYZPBC,startgenX,startgenY,[0.03 5e4]);
verticesgenDOWN{igen} = stream2(double(XwPBC),double(YwPBC),-vxXYZPBC,-vyXYZPBC,startgenX,startgenY,[0.03 5e4]);
verticesgen{igen} = cellfun(@(d,u) [flipud(d(2:end,:)); u],verticesgenDOWN{igen},verticesgenUP{igen},'UniformOutput',false);
verticesgen{igen} = cellfun(@(v) v(~isnan(v(:,2)),:),verticesgen{igen},'UniformOutput',false);
verticesgenCELL{igen} = cellfun(@(v) v(validstreamline(v),:),verticesgen{igen},'UniformOutput',false);
verticesgenCELLid{igen} = igen*ones(1,length(verticesgenCELL{igen}));
end
% merge all verticesCELL
verticesCELL = cat(2,verticesgenCELL{:});
verticesCELLid = cat(2,verticesgenCELLid{:});
okverticesCELL = cellfun(@length,verticesCELL)>100; % remove empty and too short streamlines (as a result of filtering by validstreamline)
verticesCELL = verticesCELL(okverticesCELL);
verticesCELLid = verticesCELLid(okverticesCELL);
% retrieve the separation distance
sLagrangian = startgenX(1,2,1) - startgenX(1,1,1); % separation distance between streamlines at injection/starting
rLagrangian = sLagrangian/2;
% detect interruption (for control)
% yfinalposition1UP = cellfun(@(v) v(end,2),vertices1UP);
% yfinalposition1DOWN = cellfun(@(v) v(end,2),vertices1DOWN);
% isinterrupted1 = isnan(yfinalposition1UP) | isnan(yfinalposition1DOWN);
% control plot
figure, hold on
imagesc(xwPBC,ywPBC,vXYZPBC)
colors = jet(ngenerations);
for igen = 1:ngenerations
for i=1:nstreamlines
plot(verticesgen{igen}{i}(:,1),verticesgen{igen}{i}(:,2),'-','LineWidth',0.5,'color',colors(igen,:))
plot(verticesgenCELL{igen}{i}(:,1),verticesgenCELL{igen}{i}(:,2),'-','linewidth',1,'color',colors(igen,:));
end
end
axis equal
title(sprintf('t=%0.3f s - streamlines',tframe))
%% Distribute beads along streamlines tagged as CELL
% Once PBC are applied, space is filled with possibly overlaping streamlines
% the control plot is showing this effect
traj = fillstreamline2(verticesCELL,XwPBC,YwPBC,vxXYZPBC,vyXYZPBC,rLagrangian,0);
ntraj = length(traj);
%trajUP = fillstreamline2(verticesUPCELL,XwPBC,YwPBC,vxXYZPBC,vyXYZPBC,rLagrangian,0);
%traj(~isinterrupted) = trajUP(~isinterrupted);
XYZbeads = arrayfun(@(t) t.cart_distribution,traj,'UniformOutput',false);
% we apply PBC while keeping the id of the streamline
[XYZbeadsPBC,XYZbeadsPBCid] = deal(cell(ntraj,1));
for itraj=1:ntraj % trajectories and streamlines are assumed equivalent at steady state
XYZbeadsPBC{itraj} = PBCincell(XYZbeads{itraj},box(1:2,:),[true true]); % wrapping along PBC
XYZbeadsPBCid{itraj} = ones(size(XYZbeadsPBC{itraj},1),1)*verticesCELLid(itraj);
end
% control plots
figure, hold on
colors = tooclear(jet(ntraj));
for itraj = 1:ntraj
plot(XYZbeadsPBC{itraj}(:,1),XYZbeadsPBC{itraj}(:,2),'o','markerfacecolor',colors(itraj,:),'markeredgecolor',colors(itraj,:))
end
title(sprintf('t=%0.3f s - colors match streamline index',tframe))
figure, hold on
colors = tooclear(jet(ngenerations));
for itraj = 1:ntraj
plot(XYZbeadsPBC{itraj}(:,1),XYZbeadsPBC{itraj}(:,2),'o','markerfacecolor',colors(XYZbeadsPBCid{itraj}(1),:),'markeredgecolor',colors(XYZbeadsPBCid{itraj}(1),:))
end
title(sprintf('t=%0.3f s - colors match generation index',tframe))
%% remove duplicated beads between streamlines (too close)
% defined as beads from another streamline located at less than rbead
% this version incorporate the effect of the generational distance (somekind of age)
% beads from other injections points should emerge from positions farther from first generations (older ones)
% the generation index starts from the source of the flow (bottom), using streamlines from other positions is discouraged (since younger)
%
% Note: streamlines are indexed from oldest (closest to the source) to youngest (farthest from the source)
% beads from different streamlines
for i=1:ntraj % reference streamline (higher precedence)
for j=1:ntraj % the other streamline
if i~=j
indj = find(~isnan(XYZbeadsPBC{j}(:,1)));
dij=pdist2(XYZbeadsPBC{i},XYZbeadsPBC{j}(indj,:)); % Euclidian distance
% the criterion on age is too strict
%dgenij = pdist2(XYZbeadsPBCid{i},XYZbeadsPBCid{j}(indj,:)); % generational distance (0 for the same generation)
%XYZbeadsPBC{j}(indj(any(dij
XYZbeadsPBC{j}(indj(any(dijend
end
end
% beads from the same streamline
% two populations of beads are created (those inside and those outside (images) that could overlap those inside when they coordinates are wrapped)
%
% Note: we do not consider the overlapping of north and south images (the total height of a streamline is considered not to exceed 150% of the box height)
for i=1:ntraj
isoutside = (XYZbeads{i}(:,1)box(1,2)) | ...
(XYZbeads{i}(:,2)box(2,2) );
isvalid = ~isnan(XYZbeadsPBC{i}(:,1));
ioutside = find(isoutside & isvalid); % these beads are possible images of beads already inside
iinside = find(~isoutside & isvalid); % these beads have higher precedence, they are already inside
divsout=pdist2(XYZbeadsPBC{i}(iinside,:),XYZbeadsPBC{i}(ioutside,:));
XYZbeadsPBC{i}(ioutside(any(divsoutend
XYZbeadsPBCall = cat(1,XYZbeadsPBC{:});
XYZbeadsPBCidall = cat(1,XYZbeadsPBCid{:});
isPBCallok = ~isnan(XYZbeadsPBCall(:,1));
XYZbeadsPBCall = XYZbeadsPBCall(isPBCallok,:);
XYZbeadsPBCidall = XYZbeadsPBCidall(isPBCallok,:);
% control while keeping the generational index
hfig = fighandle('packing'); hold on
colors = tooclear(hsv(ngenerations)); %turbo
for igen=1:ngenerations
viscircles(XYZbeadsPBCall(XYZbeadsPBCidall==igen,:),rLagrangian,'color',colors(igen,:));
end
title(sprintf('t=%0.3f s - one color per injection line',tframe)), axis equal
PRINTON = true;
if PRINTON, printhandle(hfig), end
%% Filtering: remove overlapping from different injections using a first estimate of density
% Summary
% This methodology effectively redistributes beads to ensure that local densities do not exceed
% the physical limits of the simulation, enhancing realism and accuracy. By adjusting bead
% distributions based on calculated densities and employing PBC to handle edge cases, the script
% ensures a uniformly plausible representation of bead distributions across the simulation domain.
hfilter = 4*rLagrangian;
XYbeadsfilterid = XYZbeadsPBCidall;
XYbeadsfilter = XYZbeadsPBCall;
nfilter = size(XYbeadsfilter,1);
XYbeadsfilter_images= PBCimages(XYbeadsfilter,box(1:2,:),[true,true],2*hfilter);
XYbeadsfilterwithImages = [XYbeadsfilter;XYbeadsfilter_images];
Vbeadsfilter = buildVerletList(XYbeadsfilter,hfilter);
VbeadsfilterBC = buildVerletList({XYbeadsfilter XYbeadsfilterwithImages},hfilter);
Volfilter = length(find(~isnan(rhobeadXYZgrid(:))))/numel(rhobeadXYZgrid) * boxsize(1) * boxsize(2) * s;
mfilter = rho*Volfilter/size(XYbeadsfilter,1); % mass of a single bead (informed)
Vfilter = mfilter/rho;
Wfilter = kernelSPH(hfilter,'lucy',2);
rhofilter = interp2SPHVerlet(...
XYbeadsfilterwithImages,...
rho*ones(size(XYbeadsfilterwithImages,1),1),...
XYbeadsfilter,VbeadsfilterBC,Wfilter,Vfilter)/s;
% target
rhomax = 1500;
ok = rhofilter<=rhomax;
% before
hfig = fighandle('packing_rhomax'); hold on
viscircles(XYbeadsfilter(ok,:),rLagrangian,'color','b');
viscircles(XYbeadsfilter(~ok,:),rLagrangian,'color','r');
title(sprintf('t=%0.3f s - in red: excess density',tframe)), axis equal
PRINTON = true;
if PRINTON, printhandle(hfig), end
% do filter
itoohigh = find(~ok);
[rhotoohigh,ind] = sort(rhofilter(itoohigh),'descend');
itoohigh = itoohigh(ind);
kept = true(nfilter,1);
for k=1:length(itoohigh)
% valid neighbors (only)
jBC = VbeadsfilterBC{itoohigh(k)}; % it includes self with images
njBC = length(jBC);
j = Vbeadsfilter{itoohigh(k)}; % it includes self without images
j = j(kept(j));
[idj,ind] = sort(XYbeadsfilterid(j),'ascend');
j = j(ind); nj = length(j);
jdeletable = j(find(idj>min(idj),1,'first'):end); % we keep the first type
[rhojdeletable,ind] = sort(rhofilter(jdeletable),'descend');
jdeletable = jdeletable(ind); njdeletable = length(jdeletable);
njexcess = floor( (1 - rhomax/rhotoohigh(k)) * njBC );
kept(jdeletable(1:min(njexcess,njdeletable))) = false;
end
% after
hfig = fighandle('packing_rhomax_fix'); hold on
viscircles(XYbeadsfilter(kept,:),rLagrangian,'color','g');
viscircles(XYbeadsfilter(~kept,:),rLagrangian,'color','r');
title(sprintf('t=%0.3f s - in red: removed beads',tframe)), axis equal
if PRINTON, printhandle(hfig), end
%% calculate density
XYinformed = PBCincell(XYZbeadsPBCall(kept,:),box,[true true]); % PBC just to be sure
ninformed = size(XYinformed,1);
rinformed = rLagrangian; %radius
sinformed = 2*rinformed; % separation distance
hinformed = 5*sinformed; % for integration
% add PBC images
[XYinformed_images ,indimages]= PBCimages(XYinformed,box(1:2,:),[true,true],2*hinformed);
XYinformedwithImages = [XYinformed;XYinformed_images];
% Verlet list for the original grid
XYgrid = [Xw(:) Yw(:)];
if isprefetch('VXYinformed')
load(prefetchvar('VXYinformed'))
else
VXYinformed = buildVerletList({XYgrid XYinformedwithImages},hinformed);
save(prefetchvar('VXYinformed'),'VXYinformed')
end
% Verlet list for the informed beads
if isprefetch('VXYbeadinformed')
load(prefetchvar('VXYbeadinformed'))
else
VXYbeadinformed = buildVerletList({XYinformed XYinformedwithImages},hinformed);
save(prefetchvar('VXYbeadinformed'),'VXYbeadinformed')
end
% Calculate the mass of informed beads
% total volume of fluid in the image
Volinformed = length(find(~isnan(rhobeadXYZgrid(:))))/numel(rhobeadXYZgrid) * boxsize(1) * boxsize(2) * s;
mbeadinformed = rho*Volinformed/ninformed; % mass of a single bead (informed)
% 2D Kernel
Winformed = kernelSPH(hinformed,'lucy',2); % kernel expression [/m2]
% Calculate the volume of the beads
Vbeadinformedguess = mbeadinformed/rho; % first guess
% density at the centers of the informed beads
rhobeadinformed = interp2SPHVerlet(XYinformedwithImages,rho*ones(size(XYinformedwithImages,1),1),XYinformed,VXYbeadinformed,Winformed,Vbeadinformedguess);
rhobeadinformed = rhobeadinformed/s;
% rhobeadinformed(rhobeadinformed<200) = NaN;
% rhobeadinformed(isnan(rhobeadinformed)) = median(rhobeadinformed,'omitmissing');
% volume of the informed beads
Vbeadinformed = mbeadinformed./rhobeadinformed;
% normalized radius of informed beads
rbeadinformed = sqrt(Vbeadinformed/(s*pi));
rbeadinformed = rbeadinformed/median(rbeadinformed,'omitmissing') * rLagrangian;
% control figure
figure, viscircles(XYinformed,rLagrangian,'color','g'), axis equal, hold on
viscircles(XYinformed,rbeadinformed,'color','r')
% reference density at the same positions
XYZeqinformed = [XYinformed ones(ninformed,1)*ztop];
VXYZeqinformed = buildVerletList({XYZeqinformed XYZwithImages},1.2*h);
rhobeadXYZ2XYinformed = interp3SPHVerlet(XYZwithImages,rhobeadXYZwithImages,XYZeqinformed,VXYZeqinformed,W,VbeadXYZwithImages);
% density on the grid
if isprefetch('dgrid')
load(prefetchvar('dgrid'))
else
dgrid = interp2SPHVerlet(XYinformedwithImages,rho*ones(size(XYinformedwithImages,1),1),XYgrid,VXYinformed,Winformed,Vbeadinformedguess);
save(prefetchvar('dgrid'),'dgrid')
end
dgrid = reshape(dgrid/s,size(Xw)); % /s to get in kg/m3 from kg/m2
dgrid(isnan(rhobeadXYZgrid)) = NaN; % we mask the objects (pillar and sphere) the same way as in the reference
%% ---------------------------- PRODUCTION FIGURES
hfig = []; close all
% Density mapped on beads
hfig(end+1) = fighandle('d_bead_informed'); hold on, title(sprintf('[t=%0.3g s] density mapped to informed beads',tframe))
scatter(XYinformed(:,1),XYinformed(:,2),100*(rbeadinformed/min(rbeadinformed)).^2,rhobeadinformed,'filled'), colorbar
hfig(end+1) = fighandle('d_bead_reference'); hold on, title(sprintf('[t=%0.3g s] reference density mapped to informed beads',tframe))
scatter(XYZeqinformed(:,1),XYZeqinformed(:,2),100*(rbeadinformed/min(rbeadinformed)).^2,rhobeadXYZ2XYinformed,'filled'), colorbar
hfig(end+1) = fighandle('d_bead_informed_vs_reference'); plot(rhobeadXYZ2XYinformed(:),rhobeadinformed(:),'ro'), xlabel('reference density (back mapped)'), ylabel('informed density')
title(sprintf('[t=%0.3g s] informed vs. reference density',tframe))
% density mapped on the grid
hfig(end+1) = fighandle('d_grid_informed'); hp = pcolor(xw,yw,dgrid); hp.EdgeColor = 'none'; colorbar, title(sprintf('[t=%0.3g s] informed density (kg/m3)',tframe)), axis equal %, clim([500 1300])
haxtomodify = gca;
hfig(end+1) = fighandle('d_grid_reference'); hp = pcolor(xw,yw,rhobeadXYZgrid); hp.EdgeColor = 'none'; colorbar, title(sprintf('[t=%0.3g s] reference density (kg/m3)',tframe)), axis equal %, clim([500 1300])
clim(haxtomodify,clim)
% distribution of densities
hfig(end+1) = fighandle('distribution_rho'); hold on
histogram(rhobeadinformed), histogram(rhobeadXYZ2XYinformed), legend({'informed density','reference density'})
title(sprintf('[t=%0.3g s] Density distributions',tframe))
%% -- identify the beads in contact with objects based on density
% SUMMARY.
% This section outlines a methodology for identifying beads in contact with solid objects
% in a fluid simulation, particularly focusing on density-based criteria to determine close
% interactions and potential contact forces.
% Attention the sphere can exceed the area observed (overflow condition)
% Safe coordinates are only use for contour identification
isynan = isnan(mean(rhobeadXYZgrid(:,floor(size(rhobeadXYZgrid,2)*0.6):end),2));
isoverflow = (find(isynan,1,'last')-find(isynan,1,'first')+1)==size(rhobeadXYZgrid,1);
if (find(isynan,1,'last')-find(isynan,1,'first')+1)==size(rhobeadXYZgrid,1) % reach top
iyoverflow = find(~isynan,1,'first');
iymargin = round(size(rhobeadXYZgrid,1)*0.1); % margin to add
iysafe = iyoverflow+iymargin; % index translation (along y)
dysafe = -iysafe * ( Yw(2,1)-Yw(1,1) ); % distance translation (along y)
[rXYgridsafe,Xwsafe,Ywsafe] = PBCgridshift(Xw,Yw,rhobeadXYZgrid,[0 iysafe]); % translate
dgridsafe = PBCgridshift(Xw,Yw,dgrid,[0 iysafe]); % translate
[XYinformedsafe,boxsafe] = PBCimagesshift(XYinformed,[0 dysafe],box(1:2,1:2));
elseif find(isynan,1,'first')==1
iymargin = round(size(rhobeadXYZgrid,1)*0.1); % margin to add
iyoverflow = iymargin;
if isynan(end), iyoverflow = iyoverflow + length(iyoverflow)-find(~isynan,1,'last'); end
iysafe = -iyoverflow;
dysafe = -iysafe * ( Yw(2,1)-Yw(1,1) ); % distance translation (along y)
[rXYgridsafe,Xwsafe,Ywsafe] = PBCgridshift(Xw,Yw,rhobeadXYZgrid,[0 iysafe]); % translate
dgridsafe = PBCgridshift(Xw,Yw,dgrid,[0 iysafe]); % translate
[XYinformedsafe,boxsafe] = PBCimagesshift(XYinformed,[0 dysafe],box(1:2,1:2));
else
dysafe = 0;
[rXYgridsafe,Xwsafe,Ywsafe] = deal(rhobeadXYZgrid,Xw,Yw);
[dgridsafe,XYinformedsafe,boxsafe] = deal(dgrid,XYinformed,box);
end
% Volume of objects (GRID points): XYobjects where density is NaN vy definition
XYobjects = [Xwsafe(:) Ywsafe(:)];
XYobjects = XYobjects(isnan(rXYgridsafe),:);
% Looking for beads in contact with XYobjects (grid points)
V2 = buildVerletList({XYinformedsafe XYobjects},3*sinformed);
% Index of beads in contact with XYobjects (grid points)
icontact = find(cellfun(@length,V2)>0);
XYcontact = double(XYinformedsafe(icontact,:));
% Enclosed beads
k = boundary(XYcontact,1); nk = length(k);
XYcontactk = XYcontact(k,:);
% separation of the objects, contour parameterization, tangents, normals
% the pillar is assumed fixed
pillarcenter = [2.87,3.08]*1e-4; % 0.2880e-3 0.3131e-3, mean(objects(1).XY)
ispillar = vecnorm(XYcontactk - pillarcenter,2,2)<0.8e-4; % 7.7859e-05, max(vecnorm(XYcontactk(ispillar,:) - pillarcenters,2,2))
objects = struct('XY',{XYcontactk(ispillar,:) XYcontactk(~ispillar,:)},'marker',{'bo','ms'},'line',{'b-','m-'},'color',{'b','m'});
anglestep = pi/32;
angles = (-pi:anglestep:pi)';
for k = 1:length(objects)
% fit each object as a circle to try to close the boundaries
objects(k).fit = fitCircleFromPoints(objects(k).XY);
missingangles = angles(abs(angles-objects(k).fit.angles(nearestpoint(angles,objects(k).fit.angles)))>anglestep);
XYtoadd = objects(k).fit.XYgenerator(missingangles);
XYclosed = [objects(k).XY;XYtoadd]; nXYclosed = floor(size(XYclosed,1)/2)*2;
[anglesclosed,ind] = sort(objects(k).fit.angleGenerator(XYclosed));
XYclosed = XYclosed (ind,:); % sort points according to angles
% enforce periodic conditions along the index dimension
objects(k).XYclosed = [XYclosed((nXYclosed/2+1):end,:) ; XYclosed ; XYclosed(1:(nXYclosed/2),:)];
objects(k).n = size(objects(k).XYclosed,1);
% First pass - contour twice longer than the real one
% fit each obhect using a regularized spline approximation
sp = csaps((1:objects(k).n)',objects(k).XYclosed',0.25); % smoothed cubic spline
%sp = spaps((1:objects(k).n)',objects(k).XYclosed',0.001*sum(var(objects(k).XYclosed))*objects(k).n);
spder = fnder(sp,1);
% Second pass - approximation based on angles
xycontours = fnval(sp,sp.breaks)';
dxycontours = fnval(spder,sp.breaks)';
xycircle = fitCircleFromPoints(xycontours);
[xyangles,ind] = sort(xycircle.angles,'ascend');
minangle = min(xyangles);
maxangle = max(xyangles);
xyangles = (2*(xyangles-minangle)/(maxangle-minangle)-1) * pi; % rescaled to close the figure
xycontoursf = [ smooth(xyangles,xycontours(ind,1),0.15,'rloess'),...
smooth(xyangles,xycontours(ind,2),0.15,'rloess') ];
dxycontoursf = [ smooth(xyangles,dxycontours(ind,1),0.15,'rloess'),...
smooth(xyangles,dxycontours(ind,2),0.15,'rloess') ];
% Make all data unique
[xyangles,ind] = unique(xyangles,'stable');
xycontoursf = xycontoursf(ind,:);
dxycontoursf = dxycontoursf(ind,:);
xycontoursf([1 end],:) = [1;1] * mean(xycontoursf([1 end],:));
dxycontoursf([1 end],:) = [1;1] * mean(dxycontoursf([1 end],:));
% Third Pass using contour length instead of angle as variable
% ATTENTION: for averaging it is preferable to use curvilinear coordinates
lxycontour = [0; cumsum(sqrt(sum(diff(xycontoursf,1,1).^2,2)))];
sp2 = csaps(lxycontour',xycontoursf');
% Extract tangents at prescribed curvilinear coordinates
xp = linspace(lxycontour(1),lxycontour(end),objects(k).n)';
% center and apparent radius (for visualizing the force acting on the object)
objects(k).XYboundary = fnval(sp2,xp)';
objects(k).center = mean(objects(k).XYboundary);
objects(k).radius = min(vecnorm(objects(k).XYboundary-objects(k).center,2,2));
% tangents (from the spline approximation)
%objects(k).tangents = ndf(xp,objects(k).XYboundary,1,[],'makeuniform',true); %curve2tangent(objects(k).XYboundary); %fnval(fnder(sp2,1),xp)';
objects(k).tangents = fnval(fnder(sp2,1),xp)';
objects(k).tangents = objects(k).tangents./vecnorm(objects(k).tangents,2,2); % Normalize the tangent vectors
% normals (turn the normals to be inwards using inpolygon)
objects(k).normals = [-objects(k).tangents(:,2),objects(k).tangents(:,1)]; % % Calculate the centroid of the shape
testPoints = objects(k).XYboundary + 0.01 * objects(k).radius * objects(k).normals; % Choose a test point slightly offset from each boundary point along the normal
isInside = inpolygon(testPoints(:,1), testPoints(:,2),...
objects(k).XYboundary(:,1), objects(k).XYboundary(:,2)); % Use inpolygon to check if each test point is inside the shape
objects(k).normals(~isInside, :) = -objects(k).normals(~isInside, :); % Flip the normals where the test point is outside the shape
% add density, pressure information (look for the nearest beads closest to the boundary, average the value)
objects(k).Vcontact = buildVerletList({objects(k).XYboundary XYcontact},3*sinformed);
objects(k).Vcontact = cellfun(@(v) icontact(v)',objects(k).Vcontact,'UniformOutput',false); % Vcontact{i} index of beads in contact with XYboundary(i,:)
objects(k).rhocontact = cellfun(@(v) mean(rhobeadinformed(v)),objects(k).Vcontact);
objects(k).Pcontact = 1 + (objects(k).rhocontact/rho).^7-1; % reduced pressure P/B with P0/B=1
objects(k).densitynormals = objects(k).rhocontact.*objects(k).normals/rho;
objects(k).avdensitynormals = mean(objects(k).densitynormals,'omitmissing');
objects(k).pressurenormals = objects(k).Pcontact.*objects(k).normals;
objects(k).avpressurenormals = mean(objects(k).pressurenormals,'omitmissing');
end
% plot
hfig(end+1) = fighandle('distribution_forces'); hold on
set(hfig(end),'PaperPosition',[ -2.0000 1.7000 30.0000 30.0000])
step = 2;
hp = pcolor(Xwsafe(1,:),Ywsafe(:,1),dgridsafe); hp.EdgeColor = 'none'; colorbar
quiv = struct('xy',[],'nxy',[],'mag',[],'nscale',2);
for k = 1:length(objects)
% viscircles(XYcontact,rinformed,'color','r')
% plot(objects(k).XY(:,1),objects(k).XY(:,2),objects(k).marker)
% plot(objects(k).XYboundary(:,1),objects(k).XYboundary(:,2),objects(k).line,'linewidth',2,'marker','x')
quiv.xy = [quiv.xy; objects(k).XYboundary(1:step:end,:); objects(k).center];
quiv.nxy = [quiv.nxy; objects(k).pressurenormals(1:step:end,:); quiv.nscale*objects(k).avpressurenormals];
quiv.mag = [quiv.mag; objects(k).Pcontact(1:step:end);mean(objects(k).Pcontact,'omitmissing')];
end
% all quivers at once (color
hq = quiver(quiv.xy(:,1),quiv.xy(:,2),quiv.nxy(:,1),quiv.nxy(:,2),3,'color','k','LineWidth',0.5);
SetQuiverColor(hq,tooclear(jet),'mags',quiv.mag,'range',[0 0.5])
axis equal, title(sprintf('[t=%0.3g s] Pressure around objects',tframe))
%% print
PRINTON = true;
if PRINTON
for i=1:length(hfig)
figure(hfig(i)), drawnow
printhandle(hfig(i))
end
end
% relative pressure via Tait equation
% Pref = (rhobeadXYZgrid./rho).^7 - 1; % reference pressure
% Pbead = (rhobeadinformed./rho).^7 - 1; % pressure at kernel positions
% Pgrid = (dgrid./rho).^7 - 1; % pressure at grid nodes
% Pgrid = min(Pref(:)) + (max(Pref(:))-min(Pref(:)))*(Pgrid-min(Pgrid(:)))/(max(Pgrid(:))-min(Pgrid(:)));
% figure, imagesc(xw,yw,Pgrid), colorbar, title('dimensionless pressure (-)'), axis equal
% clim([-1 3]), colorbar
% figure, imagesc(xw,yw,Pref), colorbar, title('reference dimensionless pressure (-)'), axis equal
% clim([-1 3]), colorbar
% figure, scatter(XYinformed(:,1),XYinformed(:,2),rbeadinformed,Pbead,'filled')
Relative fluid-to-sphere velocity field
rev. 2024/04/04
% Relative fluid-to-sphere velocity field
% rev. 2024/04/04
% 2024/04/02 fork of Billy_results_template_cross_section.m (version 2024/02/31)
% SUMMARY
%% initialization
close all
clearvars -except tframe tframelist PLOTON SAVEON OVERWRITE
%% Definitions
% tframe = 0.67; % central
% tframe = 0.57; % central-0.1
% tframe = 0.77; % central+0.1
% tframe = 0.62; % central-0.05
% tframe = 0.72; % central+0.05
% tframe = 0.15;
t0_ = clock;
% check folders
prefetchfolder = fullfile(pwd,'prefetch');
savefolder = fullfile(pwd,'results');
if ~exist(savefolder,'dir'), mkdir(savefolder); end
% Assign default values if needed
if ~exist('PLOTON','var'), PLOTON = true; end
if ~exist('SAVEON','var'), SAVEON = true; end
if ~exist('OVERWRITE','var'), OVERWRITE = false; end
% Some anaonymous functions
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file');
savefile = @() fullfile(savefolder,sprintf('3DRt.%0.4f.mat',tframe));
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
%% path and metadata
dispsection('INITIALIZATION')
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
dispf('config: %s | viscosity: %s | source: %s',config,viscosity,dumpfile)
%% extract information
dispsection('OVERVIEW')
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
% Billy choose a reference density of 900 kg/m3 for a physical density of 1000 kg/m3
% Viscosity: 0.13 Pa.s
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
dispf('SUMMARY: natoms: %d | dt: %0.3g s | rho: %0.4g',natoms,dt,rho)
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
dispsection('BEAD SIZE')
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
if isprefetch('verletList')
load(prefetchvar('verletList'))
else
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff);
save(prefetchvar('verletList'),'verletList','cutoff','dmin','config','dist')
end
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
dispf('SUMMARY: s: %0.4g m | h: %0.4g m',s,h)
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.0:0.01:1.2; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
box = Xframe.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box,1,2);
%% Unwrap all coordinates
Pshift = [0 boxsize(2)/2 0];
PBC = [true true false];
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
spherebox = [min(spherexyz)' max(spherexyz)'];
sphereboxsize = diff(spherebox,1,2);
spherexyz_ = spherexyz; % initial position
if any(sphereboxsize>boxsize/2)
disp('shift UP')
Pshift = [0 boxsize(2)/2 0];
XYZunwrapped = unwrapPBC(PBCincell(Xframe.ATOMS{:,coords},box,PBC),Pshift,box,PBC);
spherexyz = XYZunwrapped(Xframe.ATOMS.issphere,:);
spherebox = [min(spherexyz)' max(spherexyz)'];
sphereboxsize = diff(spherebox,1,2);
if any(sphereboxsize>boxsize/2)
Pshift = [0 -boxsize(2)/2 0];
disp('shift DOWN')
XYZunwrapped = unwrapPBC(PBCincell(Xframe.ATOMS{:,coords},box,PBC),Pshift,box,PBC);
end
else
Pshift = [0 0 0];
XYZunwrapped = Xframe.ATOMS{:,coords};
end
fluidxyz = XYZunwrapped(Xframe.ATOMS.isfluid,:);
spherexyz = XYZunwrapped(Xframe.ATOMS.issphere,:);
Pshiftactual = spherexyz_ - spherexyz;
%% Extract sphere properties
dispsection('SPHERE INFORMATION')
R01 = struct('tframe',tframe,'box',box,'boxdim',boxsize,'Pshift',Pshift,'Pshiftactual',mean(Pshiftactual,1),...
'sphereXYZ0',[],'fluidXYZ',[],'vfluidXYZ',[],'fluidXYZ0',[],'vfluidXYZ0',[]);
R01.sphereXYZ0 = mean(spherexyz);
spherebox = [min(spherexyz)' max(spherexyz)'];
sphereboxdim = diff(spherebox,1,2);
viewbox = spherebox + [-0.25 0.25].*sphereboxdim;
viewboxsize = diff(viewbox,1,2);
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& XYZunwrapped(:,icoord)>=viewbox(icoord,1) ...
& XYZunwrapped(:,icoord)<=viewbox(icoord,2);
end
XYZ = XYZunwrapped(insideviewbox & Xframe.ATOMS.isfluid,:); % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
Vcontact = buildVerletList({XYZ spherexyz},2*s);
iscontact = cellfun(@length,Vcontact)>0;
XYZcontact = XYZ(iscontact,:);
if PLOTON
figure, hold on, plot3D(spherexyz,'ro','markerfacecolor','r'), plot3D(XYZ(iscontact,:),'bo','markerfacecolor','b'), plot3D(XYZ(~iscontact,:),'co','markerfacecolor','c'), view(3), axis equal
drawnow
end
R01.fluidXYZ = XYZ(iscontact,:);
R01.fluidXYZ0 = mean(R01.fluidXYZ,1);
R01.vfluidXYZ = vXYZ(iscontact,:);
R01.vfluidXYZ0 = mean(R01.vfluidXYZ);
%% save
if SAVEON && (~exist(savefile(),'file') || OVERWRITE)
save(savefile(),'R01')
end
First template to retrieve Billy's paper 2 simulation data
rev. 2024/03/17 - forked in 2024/03/16 with PBC
% First template to retrieve Billy's paper 2 simulation data
% rev. 2024/03/17 - forked in 2024/03/16 with PBC
% 2024/04/02 fork of Billy_results_template_PBC (version 2024/02/31)
% SUMMARY
%% Definitions
tframe = 0.67; % central
tframe = 0.57; % central-0.1
tframe = 0.77; % central+0.1
tframe = 0.62; % central-0.05
tframe = 0.72; % central+0.05
t0_ = clock;
% check folders
prefetchfolder = fullfile(pwd,'prefetch');
savefolder = fullfile(pwd,'results');
if ~exist(savefolder,'dir'), mkdir(savefolder); end
% Assign default values if needed
if ~exist('RESETPREFETCH','var'), RESETPREFETCH = false; end
if ~exist('PLOTON','var'), PLOTON = true; end
if ~exist('PRINTON','var'), PRINTON = false; end
% Some anaonymous functions
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
fighandle = @(id) formatfig(figure,'figname',sprintf('t%0.3g_%s',tframe,id));
printhandle = @(hfig) print_png(300,fullfile(outputfolder,[get(hfig,'filename') '.png']),'','',0,0,0);
% video recording parameters
fps = 10;
moviefolder = fullfile(rootdir(savefolder),'foodsim2024','movies');
moviefile = sprintf('crossection_t%0.4g.gif',tframe);
snapfile = sprintf('crossection_t%0.4g.png',tframe);
if ~exist(moviefolder,'dir'), mkdir(moviefolder), end
fullmoviefile = fullfile(moviefolder,moviefile);
fullsnapfile = fullfile(moviefolder,snapfile);
existmoviefile = @() exist(fullmoviefile,'file');
existsnapfile = @() exist(fullsnapfile,'file');
makemovie = @(hax) gif_add_frame(hax,fullmoviefile,fps);
makesnap = @() print_png(300,fullsnapfile,'','',0,0,0);
%% path and metadata
dispsection('INITIALIZATION')
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
dispf('config: %s | viscosity: %s | source: %s',config,viscosity,dumpfile)
%% extract information
dispsection('OVERVIEW')
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
% Billy choose a reference density of 900 kg/m3 for a physical density of 1000 kg/m3
% Viscosity: 0.13 Pa.s
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
dispf('SUMMARY: natoms: %d | dt: %0.3g s | rho: %0.4g',natoms,dt,rho)
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
dispsection('BEAD SIZE')
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
if isprefetch('verletList')
load(prefetchvar('verletList'))
else
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff);
save(prefetchvar('verletList'),'verletList','cutoff','dmin','config','dist')
end
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
dispf('SUMMARY: s: %0.4g m | h: %0.4g m',s,h)
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.0:0.01:1.2; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
fluidxyz = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords};
fluidid = X0.ATOMS{Xframe.ATOMS.isfluid,'id'};
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,coords};
pillarid = X0.ATOMS{Xframe.ATOMS.ispillar,'id'};
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
sphereid = X0.ATOMS{Xframe.ATOMS.issphere,'id'};
solidxyz = Xframe.ATOMS{Xframe.ATOMS.issolid,coords};
solidid = X0.ATOMS{Xframe.ATOMS.issolid,'id'};
ztop = max(pillarxyz(:,3)); % pillar top
spherebox = [min(spherexyz)' max(spherexyz)'];
halftoppillar = pillarxyz(:,3)>ztop/2;
pillarbox = [min(pillarxyz(halftoppillar,:))' max(pillarxyz(halftoppillar,:))'];
spherepillarbox = [ min(spherebox(:,1),pillarbox(:,1)) max(spherebox(:,2),pillarbox(:,2)) ]; spherepillarboxdim=diff(spherepillarbox,1,2);
viewbox3d = [(spherepillarbox(:,1)-[0.15;0.5;0].*spherepillarboxdim),...
(spherepillarbox(:,2)+[0.15;0.5;0.02].*spherepillarboxdim)];
%% Interpolate velocity field at z = ztop
dispsection('REFERENCE VELOCITY FIELD')
% full box (note that atoms may be outside of this box)
box = Xframe.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box,1,2);
% fluidbox (box for atoms to consider)
xmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
xmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
ymin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
ymax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
zmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
zmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
fluidbox = [xmin xmax;ymin ymax;zmin zmax];
% restrict interpolation to the viewbox
viewbox = viewbox3d; %fluidbox; viewbox(3,:) = [ztop-2*h ztop+2*h];
viewboxsize = diff(viewbox,1,2);
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& Xframe.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xframe.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
XYZall = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZall = Xframe.ATOMS{Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZp = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.ispillar,coords}; % solid kernel centers
XYZs = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.issphere,coords};
rhobeadXYZall = Xframe.ATOMS.c_rho_smd(Xframe.ATOMS.isfluid); % volume of the bead
rhobeadXYZ = Xframe.ATOMS.c_rho_smd(insideviewbox & Xframe.ATOMS.isfluid); % volume of the bead
VbeadXYZall = mbead./rhobeadXYZall;
VbeadXYZ = mbead./rhobeadXYZ;
% Plot
if PLOTON
figure, hold on
plot3D(XYZ,'bo')
plot3D(XYZs,'ro')
plot3D(XYZp,'go')
view(3)
end
% interpolation grid (central grid, no images)
%nresolution = [128 128 128];
nresolution = round(128*diff(viewbox,1,2)'/max(diff(viewbox,1,2)));
xw = linspace(viewbox(1,1),viewbox(1,2),nresolution(1));
yw = linspace(viewbox(2,1),viewbox(2,2),nresolution(2));
zw = linspace(viewbox(3,1),viewbox(3,2),nresolution(3));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
% grid neighbors (incl. images) for interpolation and discarding grid points overlapping solid
VXYZ = buildVerletList({XYZgrid XYZall},1.2*h); % neighbors = fluid particles
VXYZs = buildVerletList({XYZgrid XYZs},0.85*s); % neighbors = solid particles
icontactsolid = find(cellfun(@length,VXYZs)>0);
%VXYZ(icontactsolid) = repmat({[]},length(icontactsolid),1);
% interpolation on the grid of the 3D velocity (central grid)
% Do not forget even if we are applying a 2D interpretation, we use a 3D simulation
% 3D velocity are projected on 2D streamlines even if the projection of the 3rd component is 0
W = kernelSPH(h,'lucy',3); % kernel expression
v3XYZgrid = interp3SPHVerlet(XYZall,vXYZall,XYZgrid,VXYZ,W,VbeadXYZall);
vxXYZgrid = reshape(v3XYZgrid(:,1),size(Xw)); vxXYZgrid(icontactsolid) = NaN;
vyXYZgrid = reshape(v3XYZgrid(:,2),size(Xw)); vyXYZgrid(icontactsolid) = NaN;
vzXYZgrid = reshape(v3XYZgrid(:,3),size(Xw)); vzXYZgrid(icontactsolid) = NaN;
vXYZgrid = reshape(sqrt(sum(v3XYZgrid.^2,2)),size(Xw));
% density on the grid (with a possible different h)
Wd = kernelSPH(1.08*s,'lucy',3); % kernel expression
rhobeadXYZgrid = interp3SPHVerlet(XYZall,rhobeadXYZall,XYZgrid,VXYZ,Wd,VbeadXYZall);
rhobeadXYZgrid = reshape(rhobeadXYZgrid,size(Xw)); rhobeadXYZgrid(icontactsolid) = NaN;
% Plot
if PLOTON
figure, slice(Xw,Yw,Zw,vXYZgrid,mean(viewbox(1,:)),mean(viewbox(2,:)),mean(viewbox(3,:))), axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - velocity (m/s)',tframe)), view(3)
figure, slice(Xw,Yw,Zw,rhobeadXYZgrid,mean(viewbox(1,:)),mean(viewbox(2,:)),mean(viewbox(3,:))); axis equal, view(2), colorbar, title(sprintf('t=%0.3f s - density (kg/m3)',tframe)), view(3)
end
%% Plot pillar and sphere atoms, quiver, streamlines
figure, hold on
[xs,ys,zs] = sphere(20); [xs,ys,zs] = deal(xs*rbead,ys*rbead,zs*rbead);
colors = struct('pillar',rgb('DarkGreen'),'sphere',rgb('FireBrick'));
for i=1:size(XYZs,1)
surf(xs+ XYZs(i,1), ys + XYZs(i,2), zs+ XYZs(i,3),'FaceColor',colors.sphere,'EdgeColor','none');
end
for i=1:size(XYZp,1)
surf(xs+ XYZp(i,1), ys + XYZp(i,2), zs+ XYZp(i,3),'FaceColor',colors.pillar,'EdgeColor','none');
end
lighting phong, camlight left, camlight right, axis equal, view(3)
% quiver
step = 12;
boundaries = ...
[ nearestpoint(double(xw([1,end])+viewboxsize(1)/100*[1 -1]),double(xw)) % x
nearestpoint(double(yw([1,end])+viewboxsize(2)/100*[1 -1]),double(yw)) % y
nearestpoint(double(zw([1,end])+viewboxsize(3)/100*[1 -1]),double(zw)) % z
];
indxquiver = boundaries(1,1):step:boundaries(1,2); % x indices
indyquiver = boundaries(2,1):step:boundaries(2,2); % y indices
indzquiver = boundaries(3,1):step:boundaries(3,2); % y indices
quiver3( Xw(indyquiver,indxquiver,indzquiver),...
Yw(indyquiver,indxquiver,indzquiver), ...
Zw(indyquiver,indxquiver,indzquiver), ...
vxXYZgrid(indyquiver,indxquiver,indzquiver),...
vyXYZgrid(indyquiver,indxquiver,indzquiver),...
vzXYZgrid(indyquiver,indxquiver,indzquiver),...
'color',rgb('Navy'),'LineWidth',2)
% streamlines
step = 6;
boundaries = ...
[ nearestpoint(double(xw([1,end])+viewboxsize(1)/100*[1 -1]),double(xw)) % x
nearestpoint(double(yw([1,end])+viewboxsize(2)/100*[1 -1]),double(yw)) % y
nearestpoint(double(zw([1,end])+viewboxsize(3)/100*[1 -1]),double(zw)) % z
];
indxstreamline = boundaries(1,1):step:boundaries(1,2); % x indices
indystreamline = boundaries(2,1):step:boundaries(2,2); % y indices
indzstreamline = boundaries(3,1):step:boundaries(3,2); % y indices
[startgenX,startgenY,startgenZ] = meshgrid(double(xw(indxstreamline)),double(yw(indystreamline)),double(ztop));
vxXYZgrid_ = vxXYZgrid; vxXYZgrid_(isnan(vxXYZgrid))=0;
vyXYZgrid_ = vyXYZgrid; vyXYZgrid_(isnan(vyXYZgrid))=0;
vzXYZgrid_ = vzXYZgrid; vzXYZgrid_(isnan(vzXYZgrid))=0;
hstr = streamribbon(double(Xw),double(Yw),double(Zw),vxXYZgrid_,vyXYZgrid_,vzXYZgrid_,startgenX,startgenY,startgenZ);
set(hstr,'edgecolor','none','facecolor',rgb('DeepSkyBlue'),'facealpha',0.4)
% Plot density
% figure, hold on
[Xwcut,Ywcut,Zwcut,Rwcut] = deal(Xw,Yw,Zw,rhobeadXYZgrid);
[Xwcut2,Ywcut2,Zwcut2,Rwcut2] = deal(Xw,Yw,Zw,rhobeadXYZgrid);
zcut = 3e-4;
xcut = 3.1e-4;
Xwcut(:,:,zw>zcut) = [];
Ywcut(:,:,zw>zcut) = [];
Zwcut(:,:,zw>zcut) = [];
Rwcut(:,:,zw>zcut) = [];
Xwcut2(:,xw>xcut,:) = [];
Ywcut2(:,xw>xcut,:) = [];
Zwcut2(:,xw>xcut,:) = [];
Rwcut2(:,xw>xcut,:) = [];
isorho = 1200;
% Rwcut2(Rwcut2<=isorho)=isorho;
p1a = patch(isosurface(Xwcut2,Ywcut2,Zwcut2,Rwcut2, isorho),'FaceColor',rgb('Navy'),'EdgeColor','none');
p1b = patch(isosurface(Xwcut2,Ywcut2,Zwcut2,Rwcut2, isorho-200),'FaceColor',rgb('MediumBlue'),'EdgeColor','none');
p1c = patch(isosurface(Xwcut2,Ywcut2,Zwcut2,Rwcut2, isorho-400),'FaceColor',rgb('RoyalBlue'),'EdgeColor','none');
p1d = patch(isosurface(Xwcut2,Ywcut2,Zwcut2,Rwcut2, isorho-600),'FaceColor',rgb('DodgerBlue'),'EdgeColor','none');
p2 = patch(isocaps(Xwcut,Ywcut,Zwcut,Rwcut, 200),'FaceColor','interp','EdgeColor','none');
colormap(bone(100)), axis equal
%camlight left, camlight right, lighting gouraud, view(-138,28), axis equal
camlight left; % Adds a light to the left of the camera
camlight right; % Adds a light to the right of the camera
light('Position',[-1 -1 0.5],'Style','infinite'); % Additional light source from a specific direction
ambientLight = light('Position',[1 1 1],'Style','infinite');
set(ambientLight,'Color',[0.3 0.3 0.3]); % A soft, white ambient light
lighting phong;
view([-199 24])
camproj perspective
hfig = gcf; hax = gca;
formatfig(hfig,'color','k','InvertHardcopy',false,'position',[-2143 -123 1600 1200],'PaperPosition',[0.6350 7.0918 19.7300 15.5164])
axis off
set(hax,'color','k')
axis([0.2142 0.5488 0.1409 0.4544 0.2046 0.5135]*1e-3)
%% Assuming you have your scene set up before this script
MOVIEON = true;
if ~existsnapfile()
set(hfig,'color','w')
set(hax,'color','w')
makesnap()
set(hfig,'color','k')
set(hax,'color','k')
end
if existmoviefile()
MOVIEON = false;
dispf('delete(''%s'')',fullmoviefile)
warning('delete the movie file')
else
set(hfig,'color','k')
set(hax,'color','k')
end
% Number of frames for the video
nFrames = 360; % This can be adjusted for smoother animation
% Initial view settings
initialAzimuth = -199; % Adjusting for MATLAB's view system
initialElevation = 24;
% Normalize initialAzimuth to ensure smooth transition
% MATLAB's view system handles azimuths mod 360
if initialAzimuth < 0
initialAzimuth = 360 + initialAzimuth; % Converts -199 to 161
end
% Animation parameters
azimuthEnd = initialAzimuth + 360; % Completes a full circle
elevationMin = 4; % Minimum elevation
elevationMax = 54; % Maximum elevation
midElevation = (elevationMin + elevationMax) / 2; % Midpoint for elevation
elevationAmplitude = (elevationMax - elevationMin) / 2; % Elevation change amplitude
% Time vector for sinusoidal elevation change, adjusted to start/end at initial elevation
t = linspace(0, 2*pi, nFrames);
% Pre-calculation for azimuth and elevation
azimuths = linspace(initialAzimuth, azimuthEnd, nFrames);
elevations = midElevation + elevationAmplitude * sin(t);
% Make the start and end elevations match the initial setting by adjusting the first and last values
elevations(1) = initialElevation;
elevations(end) = initialElevation;
% Animation loop
for k = 1:nFrames
% Calculate current azimuth and elevation
currentAzimuth = azimuths(k);
currentElevation = elevations(k);
% Ensure the azimuth is within the 0° to 360° range for viewing
currentAzimuth = mod(currentAzimuth, 360);
% Update view
view(currentAzimuth, currentElevation);
% Update the scene
drawnow;
% Optionally, capture the frame for video creation
% frame = getframe(gcf);
% writeVideo(videoObject, frame);
if MOVIEON
makemovie(hfig)
end
end
% slice
% figure, hs= slice(Xw,Yw,Zw,sumW,single(1:3),single(1:3),single([])); set(hs,'edgecolor','none','facealpha',0.5), axis equal
Interpretation O. Vitrac - rev. 2024-07-12-31
% Interpretation O. Vitrac - rev. 2024-07-12-31
clearvars -except d it forcedsave PREVIOUSdumpFile dataFile
% Definitions
switch localname
case 'WS-OLIVIER2023'
root = 'D:\Sensory_viscosimeter_V0';
otherwise
error('Set your machine ''%s'' first',localname)
end
subdatafolder = '/Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary';
datafolder = fullfile(root,subdatafolder);
% POST template
allDumpFiles = {
'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_10Yparticle',
'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_1000Yparticle',
'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_100000Yparticle'
};
jumps = 30000*30;
allTimesteps = {
[0:jumps:10650000],
[0:jumps:10500000],
[0:jumps:10530000]
};
D = 0.001;
BOX_DIMENSIONS = [0 2*D -0.1*D 1.1*D 0 2*D];
BOUNDARIES = [1 0 1]; % 1 if periodic
S = D / 48;
R = 0.5 * S; % radius of the particle (please, be very accurate)
E = 2000; % Hertz contact stiffness
A = 4 * D * D; % Area of the wall
H = 2 * R;
MU = 0.01;
NU = MU/1000;
U = 0.01;
%% choose file, backup file
if ~exist('PREVIOUSdumpFile','var'), PREVIOUSdumpFile = ''; end
if ~exist('dataFile','var'), dataFile = []; end
if ~exist('d','var'), d = 1; end
if ~exist('it','var'), it = 3; end
if ~exist('forcedsave','var'), forcedsave = false; end
% root folder
rootsaveresultfolder = fullfile(datafolder,'RESULTS_OV');
if ~exist(rootsaveresultfolder,'dir'), mkdir(rootsaveresultfolder); end
%% retrieve all frame results (if they exist)
if ischar(d) && strcmp(d,'all')
fres = explore('*.mat',rootsaveresultfolder,3,'abbreviate');
fresu = unique({fres.subpath},'stable');
Elist = str2double(uncell(regexp(fresu,'\_(\d+)Yparticle','tokens')));
nE = length(Elist);
leg = arrayfun(@(e) sprintf('E=%s',formatsci(e,'eco')),Elist,'UniformOutput',false);
formatfig(figure,'figname','resultsOV_meta','PaperPosition',[4.6933 9.2937 11.6133 11.1125]); %'Paperposition')
hold on
col = tooclear(parula(nE+1));
hp = zeros(nE,1);
nmax = 0;
for i=1:nE
k = find(ismember({fres.subpath},fresu(i)));
nk = length(k);
T = [];
for j=1:nk
tmp = load(fullfile(fres(k(j)).path,fres(k(j)).file));
tmp.Rcurrent.E = Elist(i) * ones(height(tmp.Rcurrent),1);
T = [T;tmp.Rcurrent]; %#ok
end
T = T(T.timestep>0,:);
hp(i) = plot(T.nNodes,T.fractalDim,'o','markersize',10,'markeredgecolor',col(i,:),'markerfacecolor',col(i,:));
nmax= max(nmax,max(T.nNodes));
end
n = 1:(nmax+1);
hp(end+1) = plot(n,(n-1)./n,'k-','linewidth',1);
uistack(hp(end),"bottom")
formatax(gca,'fontsize',12)
xlabel('Number of particles in the cluster')
ylabel('Fractal dimension (-)')
hl = legend(hp,[leg;{'linear cluster (theory)'}],'box','off','location','SouthEast');
print_png(400,fullfile(rootsaveresultfolder,get(gcf,'filename')),'','',0,0,0)
return
end
%% selected frame
dumpFile = allDumpFiles{d};
timesteps = allTimesteps{d};
DEBUGmode = false; % true to force intermediate plots
% results files for the selected frame
saveresultfolder = fullfile(rootsaveresultfolder,regexprep(dumpFile,'^dump\.',''));
if ~exist(saveresultfolder,'dir'), mkdir(saveresultfolder); end
saveresultfile = fullfile(saveresultfolder,sprintf('graph_it%d_T%d.mat',it,timesteps(it)));
saveresultfig2D = fullfile(saveresultfolder,sprintf('graph2D_it%d_T%d.png',it,timesteps(it)));
saveresultfig3D = fullfile(saveresultfolder,sprintf('graph3D_it%d_T%d.png',it,timesteps(it)));
if exist(saveresultfile,'file') && exist(saveresultfig2D,'file') && exist(saveresultfig3D,'file') && ~forcedsave
warning('this iteration is discarded, set forcedsave=true or delete the file in\n %s\n\t or use\n delete(''%s'')\n delete(''%s'')\n delete(''%s'')', ...
saveresultfolder,saveresultfile,saveresultfig2D,saveresultfig3D)
return
end
close all
resultfigs = [figure; figure];
%% Load file
if ~strcmp(dumpFile,PREVIOUSdumpFile) || isempty(dataFile)
dataFile = lamdumpread2(fullfile(datafolder, dumpFile), 'usesplit', [], timesteps);
PREVIOUSdumpFile = dumpFile;
end
% Box size
coords = {'x', 'z', 'y'};
icoords = cellfun(@(c) find(ismember({'x', 'y', 'z'}, c)), coords);
box = dataFile.BOX(icoords, :); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box, 1, 2);
PBC = [true, false, true]; % true if periodic
PBC = PBC(icoords);
PBCthickness = max(0.4 * boxsize(PBC));
%% First analysis (raw)
selection = dataFile.ATOMS(dataFile.ATOMS.TIMESTEP == timesteps(it) & dataFile.ATOMS.type >= 4, :);
XYZselection = PBCincell(selection{:, coords}, box, PBC);
[XYZimagesONLY, indXimagesONLY, copyimagesIdx] = PBCimages(XYZselection, box, PBC, PBCthickness);
if DEBUGmode
figure, hold on, plot3D(XYZselection, 'kx'), plot3D(XYZimagesONLY, 'ro'), view(3), axis equal
end
selectionimage = selection(indXimagesONLY, :);
selectionimage{:, coords} = XYZimagesONLY;
selectionimage.type = selectionimage.type + 1000 * copyimagesIdx;
selectionwithimages = [selection; selectionimage];
selectionwithimages.isimages = selectionwithimages.type > 1000;
stypes = unique(selectionwithimages.type);
nstypes = length(stypes);
[XYZ, stypesfull] = deal(cell(nstypes, 1));
isimages = stypes > 1000;
for i = 1:nstypes
XYZ{i} = selectionwithimages{selectionwithimages.type == stypes(i), coords};
stypesfull{i} = selectionwithimages.type(selectionwithimages.type == stypes(i));
end
% Control figure
if DEBUGmode
figure, hold on, col = parula(nstypes);
for i = 1:nstypes
plot3D(XYZ{i}, 'o', 'color', col(i, :));
end
view(3), axis equal
end
%% Contact pairwise distance (based on contacts)
others = 1:nstypes;
C = repmat(struct('XYZ', [], 'i', [], 'type', NaN, 'n', NaN, 'center', [], 'jneigh', [], 'tneigh', [], 'dneigh', [], ...
'isReunited', false, 'deleted', false, 'tobeupdated', true, 'ifull', []), nstypes, 1);
imagestoremove = [];
for i = 1:nstypes
dispf('\n-- object %d of %d --', i, nstypes)
j = setdiff(others, i);
sti = stypes(i);
stj = cat(1, stypesfull{j});
XYZothers = cat(1, XYZ{j});
[VXYZ, ~, ~, ~, dij] = buildVerletList({XYZ{i}, XYZothers}, 2 * H, false, 1);
found = find(~cellfun(@isempty, VXYZ));
currentNeigh = [VXYZ{found}];
currenttyp = stj(currentNeigh);
currenttypu = unique(currenttyp, 'stable')';
currentDist = [dij{found}];
currentDistu = arrayfun(@(t) min(currentDist(currenttyp == t)), currenttypu) / H;
% Record
C(i).i = i;
C(i).n = size(XYZ{i}, 1);
C(i).jneigh = j;
C(i).type = sti;
C(i).tneigh = currenttypu;
C(i).jneigh = arrayfun(@(t) find(stypes == t), currenttypu);
C(i).dneigh = currentDistu';
C(i).XYZ = XYZ{i};
C(i).tobeupdated = false;
end
Cbackup = C;
%% Identify split globules and reunite them
C = Cbackup;
nodes_tobeupdated = [];
for i = 1:nstypes
dispf('object %d of %d', i, nstypes)
if (C(i).type < 1000) ... % not an image (images of split globules are managed in the same time)
|| (~C(i).tobeupdated && ~C(i).deleted && ~C(i).isReunited) % not examined image
[reunitedXYZ, isReunited] = PBCoutcell(XYZ{i}, box, PBC);
else
isReunited = false;
end
if isReunited
C(i).XYZ = reunitedXYZ;
tk = intersect(C(i).tneigh, (1:26) * 1000 + C(i).type); % for each image, we try to reunite them on the size they are
k = arrayfun(@(t) find(stypes == t), tk);
% Merge entries
if ~isempty(k)
XYZtmp = PBCoutcell(cat(1, C(k).XYZ), box, PBC);
C(k(1)).XYZ = XYZtmp;
C(k(1)).tobeupdated = true;
[C(k(2:end)).deleted] = deal(true);
end
% Set updates
C(i).tobeupdated = true;
[C(C(i).jneigh).tobeupdated] = deal(true);
elseif ~C(i).tobeupdated
if ~C(i).deleted
C(i).XYZ = XYZ{i};
else
C(i).XYZ = [];
end
end
% Default update behavior
if ~C(i).deleted
C(i).n = size(C(i).XYZ, 1);
C(i).ifull = ones(C(i).n, 1) * i;
C(i).center = mean(C(i).XYZ, 1);
C(i).isReunited = isReunited;
else
C(i).n = 0;
C(i).center = [];
C(i).ifull = [];
C(i).tobeupdated = false;
end
if C(i).tobeupdated
nodes_tobeupdated(end + 1) = i; %#ok
end
end
%% Update the nodes
for i = 1:nstypes
if ismember(i,nodes_tobeupdated) % to be updated
if ~C(i).deleted
j = setdiff(others, i);
j = j(~[C(j).deleted]);
XYZothers = cat(1, C(j).XYZ);
jothers = cat(1, C(j).ifull);
[VXYZ, ~, ~, ~, dij] = buildVerletList({C(i).XYZ, XYZothers}, 2 * H, false, 1);
found = find(~cellfun(@isempty, VXYZ));
currentNeigh = [VXYZ{found}];
currentjneigh = jothers(currentNeigh);
C(i).oldjneigh = C(i).jneigh;
C(i).jneigh = unique(currentjneigh, 'stable')';
end
else
if ~isfield(C(i),'oldjneigh') || isempty(C(i).oldjneigh)
C(i).oldjneigh = C(i).jneigh;
end
end
C(i).jneigh = C(i).jneigh(~[C(C(i).jneigh).deleted]);
end
%% Result container ----------------------------
Rtemplate = table('Size',[1 10],...
'VariableTypes',{'string' ,'string' ,'double' ,'cell','double','double' ,'cell' ,'double','cell' ,'cell'}, ...
'VariableNames',{'datafolder','dumpfile','timestep','graph' ,'nNodes','fractalDim','XYZ' ,'counts','nodeDegrees','color'});
Rtemplate.datafolder = datafolder;
Rtemplate.dumpfile = dumpFile;
Rtemplate.timestep = timesteps(it);
%% Build the contact matrix: A
nC = length(C);
A = sparse(nC, nC);
D = sparse(nC, nC);
dmax = max(boxsize);
nmax = max([C.n]);
for i = find([C.type]<1000)
if ~C(i).deleted
j = C(i).jneigh;
j = j(~[C(j).deleted]);
if any(C(i).jneigh)
dtmp = vecnorm(cat(1,C(j).center)-C(i).center,2,2)';
D(i,j) = dtmp;
else
dtmp=0;
end
if ~isempty(j), j = j(dtmp<=dmax/2); end
A(i, j) = 1;
A(j, i) = 1; % Ensure symmetry
end
end
% Create a table for node information
nodeInfo = table((1:nC)', 'VariableNames', {'OriginalIndex'}); % Create table with original indices
% Add additional properties to the table if needed
nodeInfo.XYZ = cell(nC, 1);
nodeInfo.Degree = zeros(nC, 1);
for i = 1:nC
nodeInfo.XYZ{i} = C(i).XYZ;
nodeInfo.Degree(i) = numel(C(i).jneigh); % Example: store the degree (number of neighbors)
end
% Create the graph
G = graph(A);
G.Nodes = nodeInfo;
% Display the graph's nodes table
disp(G.Nodes);
% Create a table for node information
nodeInfo = table((1:nC)', 'VariableNames', {'OriginalIndex'}); % Create table with original indices
% Add additional properties to the table if needed
nodeInfo.XYZ = cell(nC, 1);
nodeInfo.Degree = zeros(nC, 1);
for i = 1:nC
nodeInfo.XYZ{i} = C(i).XYZ;
nodeInfo.Degree(i) = numel(C(i).jneigh); % Example: store the degree (number of neighbors)
end
% Create the graph
G = graph(A);
G.Nodes = nodeInfo;
% Display the graph's nodes table
disp(G.Nodes);
% Plot the topology
% Identify unique subgraphs
bins = conncomp(G); % Find connected components
uniqueSubgraphs = containers.Map;
uniqueSubgraphsCounts = containers.Map;
for i = 1:max(bins)
subG = subgraph(G, bins == i);
sortedEdges = sortrows(table2array(subG.Edges), 1:2);
subgraphStr = mat2str(sortedEdges);
if ~isKey(uniqueSubgraphs, subgraphStr)
uniqueSubgraphs(subgraphStr) = subG;
uniqueSubgraphsCounts(subgraphStr) = 0;
end
uniqueSubgraphsCounts(subgraphStr) = uniqueSubgraphsCounts(subgraphStr) + 1;
end
% Calculate properties
subgraphProperties = [];
subgraphList = keys(uniqueSubgraphs);
for k = 1:length(subgraphList)
subG = uniqueSubgraphs(subgraphList{k});
nNodes = numnodes(subG);
fractalDim = numedges(subG) / numnodes(subG);
count = uniqueSubgraphsCounts(subgraphList{k});
% Store node mapping
nodeIndices = subG.Nodes.OriginalIndex; % Use the OriginalIndex from G.Nodes
subgraphProperties = [subgraphProperties; struct('graph', subG, 'nNodes', nNodes, 'fractalDim', fractalDim, 'count', count, 'NodeMapping', nodeIndices)];
end
% Sort subgraphs by number of nodes and remove duplicates
[~, sortIdx] = sort([subgraphProperties.nNodes], 'descend');
sortedSubgraphProperties = subgraphProperties(sortIdx);
% Remove duplicates based on fractalDim and structure
uniqueSubgraphProperties = [];
uniqueKeys = containers.Map;
for i = 1:length(sortedSubgraphProperties)
subG = sortedSubgraphProperties(i).graph;
fractalDim = sortedSubgraphProperties(i).fractalDim;
nNodes = sortedSubgraphProperties(i).nNodes;
uniqueKey = sprintf('%.2f-%d', fractalDim, nNodes); % Unique key based on fractal dimension and number of nodes
if ~isKey(uniqueKeys, uniqueKey)
uniqueKeys(uniqueKey) = true;
uniqueSubgraphProperties = [uniqueSubgraphProperties; sortedSubgraphProperties(i)];
end
end
% Plot unique subgraphs
figure(resultfigs(1))
formatfig(resultfigs(1),'figname',lastdir(saveresultfig2D),'PaperPosition',[-0.3939 3.6647 21.7719 22.393]);
clf(resultfigs(1))
nUnique = length(uniqueSubgraphProperties);
nCols = floor(sqrt(nUnique));
nRows = ceil(nUnique / nCols);
Rcurrent = repmat(Rtemplate,nUnique,1);
% Choose color
cmap = colormap(jet);
normDegree = @(nodeDegrees) (nodeDegrees - min(nodeDegrees)) / (max(nodeDegrees) - min(nodeDegrees)); % Normalize degree
colorDegree = @(nodeDegrees) interp1(linspace(0, 1, size(cmap, 1)), cmap, normDegree(nodeDegrees));
% for each unique case
for idx = 1:nUnique
subGData = uniqueSubgraphProperties(idx);
subGData.NodeDetails = G.Nodes(subGData.NodeMapping,:);
subplot(nRows, nCols, idx);
% Get node degrees
nodeDegrees = degree(subGData.graph);
% Plot the graph with nodes colored according to their degrees
Nodelabel = cell(subGData.nNodes,1);
if DEBUGmode
for inl = 1:subGData.nNodes
Nodelabel{inl} = sprintf('\\bf%d\\rm: %d-%d-%d-%d',subGData.graph.Nodes.OriginalIndex(inl), ...
C(subGData.graph.Nodes.OriginalIndex(inl)).n,...
C(subGData.graph.Nodes.OriginalIndex(inl)).deleted,...
C(subGData.graph.Nodes.OriginalIndex(inl)).isReunited,...
C(subGData.graph.Nodes.OriginalIndex(inl)).tobeupdated);
end
else
Nodelabel = {};
end
p = plot(subGData.graph, 'Layout', 'force', 'NodeLabel', Nodelabel);
p.NodeCData = nodeDegrees;
% Adjust colorbar
colormap(jet)
cb = colorbar('eastoutside');
cb.Label.String = 'Node Degree';
pos = cb.Position;
cb.Position = [pos(1) + 4*pos(3), pos(2) + 0.25 * pos(4), 0.25 * pos(3), 0.5 * pos(4)];
% Set color limits ensuring valid range
if max(nodeDegrees) > 1
clim([1, max(nodeDegrees)]);
else
clim([1, 2]); % Set a default valid range if max degree is 1
end
title(sprintf('Nodes: %d, Fractal Dim: %.2f, Count: %d', subGData.nNodes, subGData.fractalDim, subGData.count));
% store results
Rcurrent.graph{idx} = subGData.graph;
Rcurrent.nNodes(idx) = subGData.nNodes;
Rcurrent.fractalDim(idx) = subGData.fractalDim;
Rcurrent.count(idx) = subGData.count;
Rcurrent.nodeDegrees{idx} = nodeDegrees;
Rcurrent.XYZ{idx} = subGData.NodeDetails.XYZ;
Rcurrent.color{idx} = colorDegree(nodeDegrees);
end
% global title
sgtitle(sprintf('Time Step: %d',timesteps(it)))
% Save results and print figure
save(saveresultfile,'Rcurrent')
print_png(400,fullfile(saveresultfolder,get(resultfigs(1),'filename')),'','',0,0,0)
%% Identify unique subgraphs
% bins = conncomp(G); % Find connected components
% uniqueSubgraphs = containers.Map;
% uniqueSubgraphsCounts = containers.Map;
%
% for i = 1:max(bins)
% subG = subgraph(G, bins == i);
% sortedEdges = sortrows(table2array(subG.Edges), 1:2);
% subgraphStr = mat2str(sortedEdges);
%
% if ~isKey(uniqueSubgraphs, subgraphStr)
% uniqueSubgraphs(subgraphStr) = subG;
% uniqueSubgraphsCounts(subgraphStr) = 0;
% end
% uniqueSubgraphsCounts(subgraphStr) = uniqueSubgraphsCounts(subgraphStr) + 1;
% end
%
% %%Calculate properties
% subgraphProperties = [];
% subgraphList = keys(uniqueSubgraphs);
%
% for k = 1:length(subgraphList)
% subG = uniqueSubgraphs(subgraphList{k});
% nNodes = numnodes(subG);
% fractalDim = numedges(subG) / numnodes(subG);
% count = uniqueSubgraphsCounts(subgraphList{k});
%
% % Store node mapping
% nodeIndices = subG.Nodes.OriginalIndex; % Use the OriginalIndex from G.Nodes
% subgraphProperties = [subgraphProperties; struct('graph', subG, 'nNodes', nNodes, 'fractalDim', fractalDim, 'count', count, 'NodeMapping', nodeIndices)];
% end
% Filter subgraphs with fractal dimension >= 1
filteredSubgraphProperties = subgraphProperties([subgraphProperties.fractalDim] > 0.6);
% Plot filtered subgraphs in 3D
figure(resultfigs(2))
formatfig(resultfigs(2),'figname',lastdir(saveresultfig3D),'PaperPosition',[-0.0000 0.3500 21.0000 29.0000]);
clf(resultfigs(2))
nFiltered = length(filteredSubgraphProperties);
nCols = floor(sqrt(nFiltered));
nRows = ceil(nFiltered / nCols);
for idx = 1:nFiltered
subGData = filteredSubgraphProperties(idx);
subplot(nRows, nCols, idx);
% Get node degrees
nodeDegrees = subGData.graph.Nodes.Degree;
% Plot each globule in the subgraph
hold on;
for j = 1:numel(subGData.NodeMapping)
nodeIdx = subGData.NodeMapping(j);
if nodeIdx > 0 % Ensure valid index
globuleXYZ = G.Nodes.XYZ{nodeIdx};
globuleDegree = nodeDegrees(j);
% Map degree to color
cmap = colormap(jet);
clim([1 max(nodeDegrees)]);
normDegree = (globuleDegree - min(nodeDegrees)) / (max(nodeDegrees) - min(nodeDegrees)); % Normalize degree
color = interp1(linspace(0, 1, size(cmap, 1)), cmap, normDegree);
% Plot the 3D scatter plot for the globule
if ~isempty(globuleXYZ)
scatter3(globuleXYZ(:, 1), globuleXYZ(:, 2), globuleXYZ(:, 3), 36, repmat(color, size(globuleXYZ, 1), 1), 'filled');
end
end
end
hold off;
% Add colorbar and set limits
cb = colorbar('eastoutside');
cb.Label.String = 'Node Degree';
pos = cb.Position;
cb.Position = [pos(1) + 5*pos(3), pos(2) + 0.25 * pos(4), pos(3) * 0.25, pos(4) * 0.5];
% Set color limits ensuring valid range
if max(nodeDegrees) > 1
clim([1, max(nodeDegrees)]);
else
clim([1, 2]); % Set a default valid range if max degree is 1
end
axis equal, axis tight, view(3)
% Title for the plot
title(sprintf('Nodes: %d, Fractal Dim: %.2f, Count: %d', subGData.nNodes, subGData.fractalDim, subGData.count));
end
% global title
sgtitle(sprintf('Time Step: %d',timesteps(it)))
% Save results and print figure
print_png(400,fullfile(saveresultfolder,get(resultfigs(2),'filename')),'','',0,0,0)
% My old code
% % Interpretation O. Vitrac - rev. 2024-07-12
%
% % Definitions
% switch localname
% case 'WS-OLIVIER2023'
% root = 'D:\Sensory_viscosimeter_V0';
% otherwise
% error('Set your machine ''%s'' first',localname)
% end
% subdatafolder = '/Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary';
% datafolder = fullfile(root,subdatafolder);
%
% % POST template
% allDumpFiles = {
% %'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with40SuspendedParticle_10Yparticle',
% %'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with40SuspendedParticle_1000Yparticle',
% %'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with40SuspendedParticle_100000Yparticle',
% 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_10Yparticle',
% 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_1000Yparticle',
% 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with80SuspendedParticle_100000Yparticle'
% };
%
% jumps = 30000*30;
% allTimesteps = {
% %[0:jumps:10080000],
% %[0:jumps:9990000],
% %[0:jumps:10170000],
% [0:jumps:10650000],
% [0:jumps:10500000],
% [0:jumps:10530000]
% };
%
% D = 0.001;
% BOX_DIMENSIONS = [0 2*D -0.1*D 1.1*D 0 2*D];
% BOUNDARIES = [1 0 1]; % 1 if periodic
% S = D / 48;
% R = 0.5 * S; % radius of the particle (please, be very accurate)
% E = 2000; % Hertz contact stiffness
% A = 4 * D * D; % Area of the wall
% H = 2 * R;
% MU = 0.01;
% NU = MU/1000;
% U= 0.01;
%
% % load file
% d = 1;
% dumpFile = allDumpFiles{d};
% timesteps = allTimesteps{d};
% dataFile=lamdumpread2(fullfile(datafolder,dumpFile),'usesplit',[], timesteps);
%
% % box size
% coords = {'x','y','z'};
% icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords);
% box = dataFile.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
% boxsize = diff(box,1,2);
% PBC = [true,false,true]; % true if periodic
% PBCthickness = max(0.4*boxsize(PBC));
%
% %% First analysis (raw)
% it = 3;
% selection = dataFile.ATOMS(dataFile.ATOMS.TIMESTEP==timesteps(it) & dataFile.ATOMS.type>=4,:);
% XYZselection = PBCincell(selection{:,coords},box,PBC);
% [XYZimagesONLY ,indXimagesONLY,copyimagesIdx]= PBCimages(XYZselection,box,PBC,PBCthickness);
% figure, hold on, plot3D(XYZselection,'kx'), plot3D(XYZimagesONLY,'ro'), view(3), axis equal
% selectionimage = selection(indXimagesONLY,:);
% selectionimage{:,coords} = XYZimagesONLY;
% selectionimage.type = selectionimage.type+1000*copyimagesIdx;
% selectionwithimages = [selection;selectionimage];
% selectionwithimages.isimages = selectionwithimages.type>1000;
%
% stypes = unique(selectionwithimages.type); nstypes = length(stypes);
% [XYZ,stypesfull] = deal(cell(nstypes,1));
% isimages = stypes>1000;
% for i = 1:nstypes
% XYZ{i} = selectionwithimages{selectionwithimages.type == stypes(i),coords};
% stypesfull{i} = selectionwithimages.type(selectionwithimages.type == stypes(i));
% end
% % control figure
% figure, hold on, col = parula(nstypes); for i=1:nstypes, plot3D(XYZ{i},'o','color',col(i,:)), end, view(3), axis equal
%
%
% %% Contact pair wise distance (based on contacts)
% % i = 45, j = 125
% others = 1:nstypes;
% C = repmat(struct('XYZ',[],'i',[],'type',NaN,'n',NaN,'center',[],'jneigh',[],'tneigh',[],'dneigh',[],...
% 'isReunited',false,'deleted',false,'tobeupdated',true,'ifull',[]),nstypes,1);
% imagestoremove = [];
% for i=1:nstypes
% dispf('\n-- object %d of %d --',i,nstypes)
% j = setdiff(others,i);
% sti = stypes(i);
% stj = cat(1,stypesfull{j});
% XYZothers = cat(1,XYZ{j});
% [VXYZ,~,~,~,dij] = buildVerletList({XYZ{i} XYZothers},2*H,false,1);
% found = find(~cellfun(@isempty,VXYZ));
% currentNeigh = [VXYZ{found}];
% currenttyp = stj(currentNeigh);
% currenttypu = unique(currenttyp,'stable')';
% currentDist = [dij{found}];
% currentDistu = arrayfun(@(t) min(currentDist(currenttyp==t)),currenttypu)/H;
% % record
% C(i).i = i;
% C(i).n = size(XYZ{i},1);
% C(i).jneigh = j;
% C(i).type = sti;
% C(i).tneigh = currenttypu;
% C(i).jneigh = arrayfun(@(t) find(stypes==t), currenttypu);
% C(i).dneigh = currentDistu';
% C(i).XYZ = XYZ{i};
% C(i).tobeupdated = false;
% end
% Cbackup = C;
%
% %% Identify split globules and reunite them
% C = Cbackup;
% nodes_tobeupdated = [];
% for i=1:nstypes
% dispf('object %d of %d',i,nstypes)
% if C(i).type<1000
% [reunitedXYZ, isReunited] = PBCoutcell(XYZ{i}, box, PBC);
% else
% isReunited = false;
% end
% if isReunited
% C(i).XYZ = reunitedXYZ;
% tk = intersect(C(i).tneigh,(1:26)*1000+C(i).type); % for each image, we try to reunite them on the size they are
% k = arrayfun(@(t)find(stypes==t),tk);
% % merge entries
% XYZtmp = PBCoutcell(cat(1,C(k).XYZ),box,PBC);
% C(k(1)).XYZ = XYZtmp;
% C(k(1)).tobeupdated = true;
% [C(k(2:end)).deleted] = deal(true);
% % set updates
% C(i).tobeupdated = true;
% [C(C(i).jneigh).tobeupdated] = deal(true);
% else
% if ~C(i).deleted
% C(i).XYZ = XYZ{i};
% else
% C(i).XYZ = [];
% end
% end
% % default update behavior
% if ~C(i).deleted
% C(i).n = size(C(i).XYZ,1);
% C(i).ifull = ones(C(i).n,1)*i;
% C(i).center = mean(C(i).XYZ,1);
% C(i).isReunited = isReunited;
% else
% C(i).n = [];
% C(i).center = [];
% C(i).ifull = [];
% C(i).tobeupdated = false;
% end
% if C(i).tobeupdated
% nodes_tobeupdated(end+1) = i; %#ok
% end
% end
%
% %% Update the nodes
% for i = nodes_tobeupdated
% j = setdiff(others,i);
% XYZothers = cat(1,C(j).XYZ);
% jothers = cat(1,C(j).ifull);
% [VXYZ,~,~,~,dij] = buildVerletList({C(i).XYZ XYZothers},2*H,false,1);
% found = find(~cellfun(@isempty,VXYZ));
% currentNeigh = [VXYZ{found}];
% currentjneigh = jothers(currentNeigh);
% C(i).oldjneigh = C(i).jneigh;
% C(i).jneigh = unique(currentjneigh,'stable')';
% end
%
% %% Build the contact matrix: A
% nC = length(C);
% A = zeros(nC,nC);
% for i=1:length(C)
% A(i,C(i).jneigh) = 1;
% A(C(i).jneigh,i) = 1;
% end
% G = graph(A);
%
KE_t for post-treatment of Billy' dump files (3D viscosimeter)
INRAE\Olivier Vitrac - rev. 2023-03-26
INRAE\William Jenkinson
% KE_t for post-treatment of Billy' dump files (3D viscosimeter)
% INRAE\Olivier Vitrac - rev. 2023-03-26
% INRAE\William Jenkinson
% Dependencies (not included in MS, at least not yet)
% lamdumpread2() version 2023-03-23 or later
% buildVerletList() version 2023-03-25 or later
%
% note: be sure Olivier/INRA/Codes/MS is in your Path (MS=Molecular Studio)
% Revision history
% 2023-03-23 RC, early design based on dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv100_lite
% 2023-03-24 first interaction with Billy
% the file for design was shifted to dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv10_lite
% 2023-03-25 implementation of a full Verletlist, automatic identification of fluid-solid contacts without using any
% particular topology, the template is fully operational and used to plot the number of contacts with time
% 2023-03-06 first implementation of Hertz contacts (to be validated and extended)
% better figure management (previous results can be reloaded)
% 2023-03-29 [BRANCH - WJ] script taken to template the kinetic energy vs time
% 2023-03-31 WJ - meta data and script are up-to-date for the calculation of Kinetic Energy
%% read datafile
% path definitions (please add your machine name by typing localname in your command window)
switch localname
case 'LP-OLIVIER2022'
local = 'C:\Users\olivi\OneDrive - agroparistech.fr\Billy\ProductionSandbox_toOV_23-03-2023';
case 'LX-Willy2021'
local = '/Data/billy/Results/Viscosimeter_SMJ_V6/ProductionSandbox_toOV_23-03-2023';
case 'YOUR MACHINE'
local = 'it is the path where the dump file is located, results are stored at the sample place';
otherwise
error('add a case with your machine name, which is ''%s''',localname)
end
datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv100_lite';
%datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1';
fulldatafile = fullfile(local,datafile); % concatenate path (local) and data filename
X = lamdumpread2(fulldatafile); % be sure the last version of lamdumpread2() is in the same folder as this script
% result file (to store results)
resultfile = fullfile(local,['RESULT_' datafile '.mat']);
%% Region-based definitions: Wall and fluid regions
% note that the interface between regions is defined from pair-distances
types = struct('wall',1,'fluid',2);
X.ATOMS.iswall = X.ATOMS.type==types.wall; % add the column iswall to the table X.ATOMS
X.ATOMS.isfluid = X.ATOMS.type==types.fluid;% add the column isfluid to the table X.ATOMS
X.ATOMS.isundef = ~X.ATOMS.iswall & ~X.ATOMS.isfluid; % add the column isundef to the table X.ATOMS
%% Control frame
% note that the data are stored in data.ATOMS, which is a table with named colums allowing hybrid indexing
nsteps = length(X.TIMESTEP);
icurrenttime = ceil(0.5*nsteps); % index of the control frame (used to set basic definitions before more advanced interpretation)
currenttime = X.TIMESTEP(icurrenttime);
rawframe = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','type','x','y','z','iswall','isfluid','isundef'});
frame = table2array(rawframe(:,{'x','y','z'})); % generate an array of coordinates
% Build the main Verlet list (for all particles)
% V use the natural indexing instead of rawframe.id
cutoff = 60e-6; % empty or NaN value will force an automatic estimation of cutoff
[V,cutoff,dmin] = buildVerletList(frame,cutoff); % cutoff can be omitted
% Build the secondary Verlet lists (highly vectorized code)
% V1 the neighbors of type 2 (fluid) for type 1 atoms (1..n1)
% V2 the neighbors of type 1 (wall) for type 2 atoms (1..n2)
idx1 = find(rawframe.type==1); n1 = length(idx1); % indices of the wall particles in the current frame
idx2 = find(rawframe.type==2); n2 = length(idx2); % indices of the fluid particles in the current frame
V1 = cellfun(@(v) v(rawframe.type(v)==2),V(idx1),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
V2 = cellfun(@(v) v(rawframe.type(v)==1),V(idx2),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
% corresponding distances d1,d2
% some distances can be empty for d2
d1 = arrayfun(@(i) pdist2(frame(idx1(i),:),frame(V1{i},:)),(1:n1)','UniformOutput',false); % evaluate the distance for each idx1(i)
d2 = arrayfun(@(i) pdist2(frame(idx2(i),:),frame(V2{i},:)),(1:n2)','UniformOutput',false); % evaluate the distance for each idx2(i)
d1min = cellfun(@min,d1); % minimum distance for each idx1()
d2min = cellfun(@min,d2,'UniformOutput',false); % minimum distance for each idx2()
[d2min{cellfun(@isempty,d2)}] = deal(NaN); % populate empty distances with NaN
d2min = cat(1,d2min{:}); % we collect all now all distances (since the fluid is moving with respect with the wall)
% Identify beads of type 1 (wall) directly in contact with fluids
% this method is general and relies only on pair distances
% the term "contact" is general (vincinity) not a "real" contact as set later
iswallcontact = d1min < 1.5 * dmin; % condition for a wall particle to be considered possibly in contact with the fluid
isfluidcontact = ~isnan(d2min); % the condition is less restrictive for the fluid, based on the cutoff distance
V1contact = V1(iswallcontact); % "contact" Verlet list corresponding to V1
V2contact = V2(isfluidcontact); % "contact" Verlet list corresponding to V1
iwallcontact = idx1(iswallcontact);
ifluidcontact = unique(cat(2,V1contact{:})); % within cutoff
%
% control plot
% one color is assigned to each phase
% symbols are filled if they are included in the contact Verlet list
% type figure, rgb() to list all available colors
colors = struct('wall',rgb('Crimson'),'fluid',rgb('DeepSkyBlue'),'none','None');
figure, hold on
plot3D(frame(rawframe.isundef,:),'ko');
plot3D(frame(rawframe.iswall,:),'o','MarkerEdgeColor',colors.wall,'MarkerFaceColor',colors.none);
plot3D(frame(iwallcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.wall);
plot3D(frame(rawframe.isfluid,:),'o','MarkerEdgeColor',colors.fluid,'MarkerFaceColor',colors.none);
plot3D(frame(ifluidcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.fluid);
axis equal, axis tight, view(3)
%% [1:X] INTERPRETATION: counting the number of contact with time
% count the number of fluid beads in contact with wall (within 2*R)
R = 0.5*0.001/48; % radius of the particle (please, be very accurate)
dbond = 2*R; % bond = link between 2 atoms
wall_id = rawframe.id(iwallcontact); % extract the ids matching the contact condition in the reference frame
fluid_id = rawframe.id(ifluidcontact); % idem for the fluid particles within the contact Verlet list
% config setup
E = 2e4 ; % ref:2e6, Hertzdiv10:2e5, Hertzdiv100:2e4
Hertzconfig = struct('name',{'wall','fluid'},'R',R,'E',E); % entries are duplicated if not mentioned
% prepare to calculate pair distances between different beads
pairdist = @(X,Y) triu(pdist2(X,Y),0); % note that the diagonal is included here (since X and Y are different)
ncontacts = zeros(nsteps,1);
KEnergy = zeros(nsteps,1);
[t_,t__] = deal(clock); %#ok<*CLOCK>
screen = '';
for icurrenttime = 1:nsteps
currenttime = X.TIMESTEP(icurrenttime);
% --- some display, to encourage the user to be patient
if mod(icurrenttime,5)
if etime(clock,t__)>2 %#ok<*DETIM>
t__ = clock; dt = etime(t__,t_); done = 100*(icurrenttime-1)/nsteps;
screen = dispb(screen,'[%d/%d] interpretation [ done %0.3g %% | elapsed %0.3g s | remaining %0.3g s ] ...', ...
icurrenttime,nsteps,done,dt,dt*(100/done-1));
end
end % --- end of display
Vel = table2array(X.ATOMS((X.ATOMS.TIMESTEP==currenttime) & (X.ATOMS.type==1),{'vx','vy','vz','mass'})); % raw data for the current frame
V_mag_sq = Vel(:,1).^2 + Vel(:,2).^2 + Vel(:,3).^2;
KEnergy(icurrenttime) = sum(0.5*Vel(:,4).*V_mag_sq); % sum forces along x
end
% save the data to enable a refresh of the figure without restarting this block
timesteps = X.TIMESTEP;
save(resultfile,'datafile','KEnergy','ncontacts','timesteps','Hertzconfig')
dispf('Results saved (%s):',datafile), fileinfo(resultfile)
%% PLots and figure management
% reload the data
if exist(resultfile,'file'), load(resultfile), end
% plot number of contacts vs. time
contactfigure = figure;
formatfig(contactfigure,'figname',['NumberContact' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,ncontacts,'linewidth',0.5,'Color',rgb('Crimson'))
formatax(gca,'fontsize',14)
xlabel('time (units)','fontsize',16)
ylabel('Kinetic energy of particles ()','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% plot number of Hertz projection along x vs. time
hertzfigure = figure;
formatfig(hertzfigure,'figname',['KineticEnergy' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,KEnergy,'linewidth',0.5,'Color',rgb('Teal'))
formatax(gca,'fontsize',14)
xlabel('Frame (-)','fontsize',16)
ylabel('Kinetic energy (J)','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% save images in all valid formats (including Matlab one, the data can be extracted with this format)
% filenames are identical to the dump file with the proper extension: fig, pdf, png
for myfig = [contactfigure,hertzfigure] % loop over all figures to print
figure(myfig)
saveas(gcf,fullfile(local,[get(gcf,'filename') '.fig']),'fig') % fig can be open without restarting the code
print_pdf(600,[get(gcf,'filename') '.pdf'],local,'nocheck') % PDF 600 dpi
print_png(600,[get(gcf,'filename') '.png'],local,'',0,0,0) % PNG 600 dpi
end
MDUNIDRND generates m uniform random numbers (one appearance) ranged between 1 and n (n>m)
Syntax: x = MDunidrnd(n,m)
function x = MDunidrnd(n,m)
% MDUNIDRND generates m uniform random numbers (one appearance) ranged between 1 and n (n>m)
%
% Syntax: x = MDunidrnd(n,m)
% MDsimple 2.0 - 21/07/03 - Olivier Vitrac - rev.
% argument check
if n'n), end
% random generator
x = []; lx = 0;
while lxend+1:m) = unidrnd(n,m-lx,1);
[xu,j] = unique(x);
x = x(sort(j));
lx = length(x);
end
PBCgrid add periodic boundary conditions to meshgrid/ndgrid meshed values
USAGE in 3D
[Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,PBC [,cutoff])
USAGE in 2D
[Vp,Xp,Yp] = PBCgrid(X,Y,V,PBC [,cutoff])
USAGE in 1D
[Vp,Xp] = PBCgrid(X,V,PBC [,cutoff])
INPUTS (3D):
X: a x b x c array created by meshgrid, ndgrid coding for X coordinates
Y: a x b x c array created by meshgrid, ndgrid coding for Y coordinates
Z: a x b x c array created by meshgrid, ndgrid coding for Z coordinates
V: a x b x c array where V(i,j,k) is the value at X(i,j,k), Y(i,j,k) and Z(i,j,k)
PBC: 3 x 1 boolean array (true if the dimension is periodic)
cutoff: cutoff value either scalar or vector
[cutoff;cutoff;cutoff] or [cutoffx;cutoffy;cutoffz]
INPUTS (2D):
X: a x b array created by meshgrid, ndgrid coding for X coordinates
Y: a x b array created by meshgrid, ndgrid coding for Y coordinates
V: a x b array where V(i,j) is the value at X(i,j) and Y(i,j)
PBC: 2 x 1 boolean array (true if the dimension is periodic)
cutoff: cutoff value either scalar or vector
[cutoff;cutoff] or [cutoffx;cutoffy]
INPUTS (1D):
X: a x 1 array created by linspace or equivalent
V: a x 1 array where V(i) is the value at X(i)
PBC: boolean (true if the dimension is periodic)
cutoff: cutoff value
OUTPUTS (1-3D)
Vp: array with ndims(Vp) = ndims(V) augmented with perodic values
Xp,Yp,Zp corresponding coordinates
See also
PBCgridshift, PBCimages, PBCimageschift, PBCincell
See also
See also
See also
Example
[X,Y,V] = peaks(100);
[Vp,Xp,Yp] = PBCgrid(X,Y,V,[true,true],[1.5 3]);
figure, mesh(Xp,Yp,Vp)
function [Vp,Xout,Yout,Zout] = PBCgrid(varargin)
%PBCgrid add periodic boundary conditions to meshgrid/ndgrid meshed values
%
% USAGE in 3D
% [Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,PBC [,cutoff])
% USAGE in 2D
% [Vp,Xp,Yp] = PBCgrid(X,Y,V,PBC [,cutoff])
% USAGE in 1D
% [Vp,Xp] = PBCgrid(X,V,PBC [,cutoff])
%
% INPUTS (3D):
% X: a x b x c array created by meshgrid, ndgrid coding for X coordinates
% Y: a x b x c array created by meshgrid, ndgrid coding for Y coordinates
% Z: a x b x c array created by meshgrid, ndgrid coding for Z coordinates
% V: a x b x c array where V(i,j,k) is the value at X(i,j,k), Y(i,j,k) and Z(i,j,k)
% PBC: 3 x 1 boolean array (true if the dimension is periodic)
% cutoff: cutoff value either scalar or vector
% [cutoff;cutoff;cutoff] or [cutoffx;cutoffy;cutoffz]
%
% INPUTS (2D):
% X: a x b array created by meshgrid, ndgrid coding for X coordinates
% Y: a x b array created by meshgrid, ndgrid coding for Y coordinates
% V: a x b array where V(i,j) is the value at X(i,j) and Y(i,j)
% PBC: 2 x 1 boolean array (true if the dimension is periodic)
% cutoff: cutoff value either scalar or vector
% [cutoff;cutoff] or [cutoffx;cutoffy]
%
% INPUTS (1D):
% X: a x 1 array created by linspace or equivalent
% V: a x 1 array where V(i) is the value at X(i)
% PBC: boolean (true if the dimension is periodic)
% cutoff: cutoff value
%
% OUTPUTS (1-3D)
% Vp: array with ndims(Vp) = ndims(V) augmented with perodic values
% Xp,Yp,Zp corresponding coordinates
%
%
%
% See also: PBCgridshift, PBCimages, PBCimageschift, PBCincell
%
%
%
% Example:
% [X,Y,V] = peaks(100);
% [Vp,Xp,Yp] = PBCgrid(X,Y,V,[true,true],[1.5 3]);
% figure, mesh(Xp,Yp,Vp)
% MS 3.0 | 2024-03-15 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-16
% Revision history
% 2024-03-15 release candidate with example
% 2024-03-16 fix nmirror when more than available points are required
%% check arguments
if nargin<3, error('Syntax: [Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,PBC [,cutoff]) in 3D (other syntaxes available)'), end
X = varargin{1};
d = ndims(X); %<<- the number of dimensions in X sets 1D, 2D or 3D syntax
if d==3 && nargin<5, error('five arguments are at least required in 3D: [Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,PBC [,cutoff])'), end
if d==2 && nargin<4, error('four arguments are at least required in 2D: [Vp,Xp,Yp] = PBCgrid(X,Y,V,PBC [,cutoff])'), end
if d>1
Y = varargin{2};
if ~isequal(size(X),size(Y)), error('X and Y are not compatible'), end
if d>2 % 3D
Z = varargin{3};
if ~isequal(size(X),size(Z)), error('X, Y and Z are not compatible'), end
V = varargin{4};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X, Y and Z'), end
PBC = varargin{5};
if nargin>5, cutoff = varargin{6}; else cutoff = []; end %#ok<*SEPEX>
else % 2D
V = varargin{3};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X and Y'), end
PBC = varargin{4};
if nargin>4, cutoff = varargin{5}; else cutoff = []; end
end
else % 1D
V = varargin{2};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X'), end
PBC = varargin{3};
if nargin>3, cutoff = varargin{4}; else cutoff = []; end
end
% fix PBC
if length(PBC)~=d, error('PBC should be a %dx1 boolean array',d), end
PBC = PBC>0; % convert to boolean
% fix cutoff (heuristic for the default value)
if isempty(cutoff), cutoff = max(abs(X(1:2,1:2,1)-X(1,1,1)),[],'all')*(numel(X).^(1/d))/4; end
if length(cutoff)==1, cutoff = cutoff(ones(d,1)); end
cutoff(~PBC) = 0;
% discriminat between meshgrid or ndgrid generation
if d>1
ismeshgrid = all(diff(X(1:2,:,:),1,1)==0); % true if X,Y,Z generated with meshgrid
else
ismeshgrid = false; % by convention
X = X(:); % force column-wise
V = V(:); % force column-wise
end
% Found bounds along X and Y (dependent on meshgrid or ndgrid generation)
nmirror = zeros(d,1); % number of values to mirror along each dimension
if ismeshgrid % 2D or 3D
% bounds
xmin = min(X(1,:,1));
xmax = max(X(1,:,1));
ymin = min(Y(:,1,1));
ymax = max(Y(:,1,1));
if cutoff(1)>0
ntmp = find(abs(X(1,:,1)-X(1,1,1))>cutoff(1),1,'first')-1;
if isempty(ntmp), nmirror(1) = size(X,2); else, nmirror(1) = ntmp; end
end
if cutoff(2)>0
ntmp = find(abs(Y(:,1,1)-Y(1,1,1))>cutoff(2),1,'first')-1;
if isempty(ntmp), nmirror(2) = size(X,1); else, nmirror(2) = ntmp; end
end
dx = diff(X(1,:,1),1,2);
dy = diff(Y(:,1,1),1,1);
else % ndgrid: 1D, 2D or 3D
xmin = min(X(:,1,:));
xmax = max(X(:,1,:));
if cutoff(1)>0
ntmp = find(abs(X(:,1,1)-X(1,1,1))>cutoff(1),1,'first')-1;
if isempty(ntmp), nmirror(1) = size(X,1); else, nmirror(1) = ntmp; end
end
dx = diff(X(:,1,1),1,1);
if d>1
ymin = min(Y(1,:,1));
ymax = max(Y(1,:,1));
if cutoff(2)>0
ntmp = find(abs(Y(1,:,1)-Y(1,1,1))>cutoff(2),1,'first')-1;
if isempty(ntmp), nmirror(2) = size(X,2); else, nmirror(2) = ntmp; end
end
dy = diff(X(1,:,1),1,2);
else
[ymin,ymax] = deal(NaN);
end
end
% Found bounds along Z. Note that it is managed independently of ndgrid, meshgrid
if d>2 %3D
zmin = min(Z(:,:,1));
zmax = max(Z(:,:,1));
if cutoff(3)>0
ntmp = find(abs(Z(:,:,1)-Z(1,1,1))>cutoff(3),1,'first')-1;
if isempty(ntmp), nmirror(3) = size(X,3); else, nmirror(3) = ntmp; end
end
dz = diff(X(1,1,:),1,3);
else
[zmin,zmax] = deal(NaN);
end
% full bounds
bounds = [xmin xmax; ymin ymax; zmin zmax]; % NaN values for non-defined dimensions
dimensions = diff(bounds,1,2);
%% apply PBC to V
% nmirror(1) -> number of points to translate along X (regardless the value of ismeshgrid)
% nmirror(2) -> number of points to translate along Y (idem)
% nmirror(3) -> number of points to translate along Z (idem)
% dimensions
% a = length of grid along Y (if ismeshgrid), along X instead (ndgrid)
% b = length of grid along X (if ismeshgrid), along Y instead (ndgrid)
% c = length of grid along Z
[a,b,c] = size(X);
if ismeshgrid % 2D, 3D
left = (b-nmirror(1)+1):b;
right = 1:nmirror(1);
indx = [left, 1:b, right];
top = (a-nmirror(2)+1):a;
bottom = 1:nmirror(2);
indy = [top, 1:a, bottom];
else % 1D, 2D, 3D
left = (a-nmirror(1)+1):a;
right = 1:nmirror(1);
indx = [left, 1:a, right];
if d>1
top = (b-nmirror(2)+1):b;
bottom = 1:nmirror(2);
indy = [top, 1:b, bottom];
end
end
if d>2
front = (c-nmirror(3)+1):c;
back = 1:nmirror(3);
indz = [front, 1:c, back];
end
%% duplication and translation
% (note that dimensions and nmirror obey to the same convention independently of ismeshgrid)
% nmirror(1) and dimensions(1) are always along X, 2 for Y and 3 for Z
if ismeshgrid % 2D and 3D
if d==2
Vp = V(indy,indx);
Xp = X(indy,indx);
Yp = Y(indy,indx);
else
Vp = V(indy,indx,indz);
Xp = X(indy,indx,indz);
Yp = Y(indy,indx,indz);
Zp = Y(indy,indx,indz);
end
% translation X and Y (Z common with ndgrid)
Xp(:,1:nmirror(1),:) = Xp(:,1:nmirror(1),:)-dimensions(1)-dx(end); % left translation
Xp(:,(end-nmirror(1)+1):end,:) = Xp(:,(end-nmirror(1)+1):end,:)+dimensions(1)+dx(1); % right translation
Yp(1:nmirror(2),:,:) = Yp(1:nmirror(2),:)-dimensions(2)-dy(end); % top translation
Yp((end-nmirror(2)+1):end,:,:) = Yp((end-nmirror(2)+1):end,:,:)+dimensions(2)+dy(1); % bottom translation
else % ndgrid in 1D, 2D and 3D
if d==1
Vp = V(indx);
Xp = X(indx);
elseif d==2
Vp = V(indx,indy);
Xp = X(indx,indy);
Yp = Y(indx,indy);
else
Vp = V(indx,indy,indz);
Xp = X(indx,indy,indz);
Yp = Y(indx,indy,indz);
end
Xp(1:nmirror(1),:,:) = Xp(1:nmirror(1),:,:)-dimensions(1)-dx(end); % left translation
Xp((end-nmirror(1)+1):end,:,:) = Xp((end-nmirror(1)+1):end,:,:)+dimensions(1)+dx(1); % top translation
if d>1
Yp(:,1:nmirror(2),:) = Yp(:,1:nmirror(2),:)-dimensions(2)-dy(end); % top translation
Yp(:,(end-nmirror(2)+1):end,:) = Yp(:,(end-nmirror(2)+1):end,:)+dimensions(2)+dy(1); % bottom translation
end
end
if d>2
Zp(:,:,1:nmirror(3)) = Zp(:,:,1:nmirror(3))-dimensions(3)-dz(end); % front translation
Zp(:,:,(end-nmirror(3)+1):end) = Zp(:,:,(end-nmirror(3)+1):end)+dimensions(3)+dz(1); % back translation
end
%% outputs
if nargout>1, Xout = Xp; end
if nargout>2 && d>1, Yout = Yp; end
if nargout>3 && d>2, Zout = Zp; end
PBCGRIDSHIFT shift meshgrid/ndgrid meshed values to a vector P assuming periodic boundary conditions
and considering the grid created by either meshgrid or ndgrid.
USAGE in 3D
[Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,P)
USAGE in 2D
[Vp,Xp,Yp] = PBCgrid(X,Y,V,P)
USAGE in 1D
[Vp,Xp] = PBCgrid(X,V,P)
INPUTS (3D):
X: a x b x c array created by meshgrid, ndgrid coding for X coordinates
Y: a x b x c array created by meshgrid, ndgrid coding for Y coordinates
Z: a x b x c array created by meshgrid, ndgrid coding for Z coordinates
V: a x b x c array where V(i,j,k) is the value at X(i,j,k), Y(i,j,k) and Z(i,j,k)
P: 3 x 1 shift vector (units in grid step)
P(1) is the shift along x, P(2) along y, P(3) along z
INPUTS (2D):
X: a x b array created by meshgrid, ndgrid coding for X coordinates
Y: a x b array created by meshgrid, ndgrid coding for Y coordinates
V: a x b array where V(i,j) is the value at X(i,j) and Y(i,j)
P: 2 x 1 shift vector (units in grid step)
P(1) is the shift along x, P(2) along y
INPUTS (1D):
X: a x 1 array created by linspace or equivalent
V: a x 1 array where V(i) is the value at X(i)
P: scalar shift (units in grid step)
OUTPUTS (1-3D)
Vp: array of the same size as V
Xp,Yp,Zp corresponding coordinates (with proper translation)
preserving the nature of the grid (ndgrid or meshgrid)
Note1: an error is returned if X,Y,Z are not generated by meshgrid or ndgrid
Note2: This function assumes a uniform grid
See also
PBCgrid, PBCimages, PBCimageschift, PBCincell
See also
See also
Example
[X,Y,V] = peaks(100);
[Vp,Xp,Yp] = PBCgridshift(X,Y,V,[10 20]);
figure, mesh(Xp,Yp,Vp)
function [Vp,varargout] = PBCgridshift(varargin)
%PBCGRIDSHIFT shift meshgrid/ndgrid meshed values to a vector P assuming periodic boundary conditions
% and considering the grid created by either meshgrid or ndgrid.
%
% USAGE in 3D
% [Vp,Xp,Yp,Zp] = PBCgrid(X,Y,Z,V,P)
% USAGE in 2D
% [Vp,Xp,Yp] = PBCgrid(X,Y,V,P)
% USAGE in 1D
% [Vp,Xp] = PBCgrid(X,V,P)
%
% INPUTS (3D):
% X: a x b x c array created by meshgrid, ndgrid coding for X coordinates
% Y: a x b x c array created by meshgrid, ndgrid coding for Y coordinates
% Z: a x b x c array created by meshgrid, ndgrid coding for Z coordinates
% V: a x b x c array where V(i,j,k) is the value at X(i,j,k), Y(i,j,k) and Z(i,j,k)
% P: 3 x 1 shift vector (units in grid step)
% P(1) is the shift along x, P(2) along y, P(3) along z
%
% INPUTS (2D):
% X: a x b array created by meshgrid, ndgrid coding for X coordinates
% Y: a x b array created by meshgrid, ndgrid coding for Y coordinates
% V: a x b array where V(i,j) is the value at X(i,j) and Y(i,j)
% P: 2 x 1 shift vector (units in grid step)
% P(1) is the shift along x, P(2) along y
%
% INPUTS (1D):
% X: a x 1 array created by linspace or equivalent
% V: a x 1 array where V(i) is the value at X(i)
% P: scalar shift (units in grid step)
%
% OUTPUTS (1-3D)
% Vp: array of the same size as V
% Xp,Yp,Zp corresponding coordinates (with proper translation)
% preserving the nature of the grid (ndgrid or meshgrid)
%
%
% Note1: an error is returned if X,Y,Z are not generated by meshgrid or ndgrid
% Note2: This function assumes a uniform grid
%
%
%
% See also: PBCgrid, PBCimages, PBCimageschift, PBCincell
%
%
% Example:
% [X,Y,V] = peaks(100);
% [Vp,Xp,Yp] = PBCgridshift(X,Y,V,[10 20]);
% figure, mesh(Xp,Yp,Vp)
% MS 3.0 | 2024-03-24 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev.
% Revision history
% Determine the number of dimensions and validate input
X = varargin{1};
d = ndims(X); %<<- the number of dimensions in X sets 1D, 2D or 3D syntax
if d>3, error('the number of dimensions should be 1,2,3'), end
if d==3 && nargin<5, error('five arguments are at least required in 3D: [Vp,Xp,Yp,Zp] = PBCgridshift(X,Y,Z,V,P)'), end
if d==2 && nargin<4, error('four arguments are at least required in 2D: [Vp,Xp,Yp] = PBCgridshift(X,Y,V,P)'), end
if d>1
Y = varargin{2};
if ~isequal(size(X),size(Y)), error('X and Y are not compatible'), end
if d>2 % 3D
Z = varargin{3};
if ~isequal(size(X),size(Z)), error('X, Y and Z are not compatible'), end
V = varargin{4};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X, Y and Z'), end
P = varargin{5};
if nargin>5, error('4 arguments in 3D'), end
deltaZ = Z(1,1,2) - Z(1,1,1);
else % 2D
V = varargin{3};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X and Y'), end
P = varargin{4};
if nargin>4, error('4 arguments in 2D'), end
end
isMesh = X(1,1,:) == X(2,1,:);
if isMesh
deltaX = X(1,2,1) - X(1,1,1);
deltaY = Y(2,1,1) - Y(1,1,1);
else % ndgrid
deltaX = X(2,1,1) - X(1,1,1);
deltaY = Y(1,2,1) - Y(1,1,1);
end
else % 1D
V = varargin{2};
if ~isequal(size(X),size(V)), error('V is not compatible with supplied X'), end
P = varargin{3};
if nargin>3, error('3 arguments in 2D'), end
isMesh = false; % 1D grid doesn't require meshgrid/ndgrid distinction
deltaX = X(2)-X(1);
end
% Translate Xp,Yp,Zp
varargout{1} = X + P(1) * deltaX;
if d>1, varargout{2} = Y + P(2) * deltaY; end
if d==3, varargout{3} = Z + P(3) * deltaZ; end
% Apply periodic boundary condition shift based on the dimensionality
switch d
case 1
shift = mod((0:numel(X)-1) + P(1), numel(X)) + 1;
Vp = V(shift);
case 2
[rows, cols] = size(X);
if isMesh
rShift = mod((0:rows-1)' + P(2), rows) + 1;
cShift = mod((0:cols-1) + P(1), cols) + 1;
Vp = V(rShift, cShift);
else
rShift = mod((0:rows-1)' + P(1), rows) + 1;
cShift = mod((0:cols-1) + P(2), cols) + 1;
Vp = V(cShift, rShift);
end
case 3
[rows, cols, pages] = size(X);
zShift = mod((0:pages-1) + P(3), pages) + 1;
if isMesh
rShift = mod((0:rows-1)' + P(2), rows) + 1;
cShift = mod((0:cols-1) + P(1), cols) + 1;
Vp = V(rShift, cShift, zShift);
else
rShift = mod((0:rows-1)' + P(1), rows) + 1;
cShift = mod((0:cols-1) + P(2), cols) + 1;
Vp = V(cShift, rShift, zShift);
end
end
PBCIMAGES return the coordinates of fictive particle images outside box limits along periodic boundary dimensions
USAGE: Ximages = PBCimages(X,box,PBC [,cutoff])
[Ximages,indX,copyIdx] = PBCimages(...)
INPUTS:
X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
box: 2x2 or 3x2 array coding for box dimensions
the box spans along dimension i between box(i,1) and box(i,2)
all X values should lie within box limits, if not an error is generated
PBC: 1x2 or 1x3 boolean array
PBC(i) is true if the dimension i is periodic
cutoff: scalar or 1xd array with d the number of dimensions (2 or 3)
setting the cutoff distance beyond bounds to include fictive images
if cutoff is a scalar, it is redefined as cutoff(ones(1,d))
cutoff(i) is applied along dimension i
OUTPUTS:
Ximages: mx2 or mx3 array coding for the coordinates of the m particle images in 2D and 3D, respectively
indX: corresponding indices of images in X
copyIdx: indices of the copies of each atom created (starting from 1)
See also
PBCgrid, PBCgridshift, PBCimageschift, PBCincell
See also
See also
NOTE, about, copyIdx, and, its, values, (2D:, maximum, value, =, 5, 3D:, maximum, value, =, 26)
See also
In, 2D., Each, dimension, can, generate, two, types, of, images:, lower, and, upper, images., If, both, dimensions, are, periodic:, there, are, additional, corner, images., Lower, images, for, dimension, 1:, copyIdx, =, 1, Upper, images, for, dimension, 1:, copyIdx, =, 2, Lower, images, for, dimension, 2:, copyIdx, =, 3, Upper, images, for, dimension, 2:, copyIdx, =, 4, Corner, images, (4, possible, combinations):, copyIdx, =, 5, In, 3D., If, all, three, dimensions, are, periodic:, there, are, additional, corner, images., If, at, least, two, dimensions, are, periodic:, there, are, additional, edge, images., Lower, images, for, dimension, 1:, copyIdx, =, 1, Upper, images, for, dimension, 1:, copyIdx, =, 2, Lower, images, for, dimension, 2:, copyIdx, =, 3, Upper, images, for, dimension, 2:, copyIdx, =, 4, Lower, images, for, dimension, 3:, copyIdx, =, 5, Upper, images, for, dimension, 3:, copyIdx, =, 6, Edge, images, (12, possible, combinations):, copyIdx, =, 7, 8, ..., 18, Corner, images, (8, possible, combinations):, copyIdx, =, 19, 20, ..., 26
function [Ximages,indXout,copyIdx] = PBCimages(X,box,PBC,cutoff)
%PBCIMAGES return the coordinates of fictive particle images outside box limits along periodic boundary dimensions
%
% USAGE: Ximages = PBCimages(X,box,PBC [,cutoff])
% [Ximages,indX,copyIdx] = PBCimages(...)
%
% INPUTS:
% X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
% box: 2x2 or 3x2 array coding for box dimensions
% the box spans along dimension i between box(i,1) and box(i,2)
% all X values should lie within box limits, if not an error is generated
% PBC: 1x2 or 1x3 boolean array
% PBC(i) is true if the dimension i is periodic
% cutoff: scalar or 1xd array with d the number of dimensions (2 or 3)
% setting the cutoff distance beyond bounds to include fictive images
% if cutoff is a scalar, it is redefined as cutoff(ones(1,d))
% cutoff(i) is applied along dimension i
%
% OUTPUTS:
% Ximages: mx2 or mx3 array coding for the coordinates of the m particle images in 2D and 3D, respectively
% indX: corresponding indices of images in X
% copyIdx: indices of the copies of each atom created (starting from 1)
%
% See also: PBCgrid, PBCgridshift, PBCimageschift, PBCincell
%
%
% NOTE about copyIdx and its values (2D: maximum value = 5, 3D: maximum value = 26)
%
% In 2D. Each dimension can generate two types of images: lower and upper images.
% If both dimensions are periodic: there are additional corner images.
% Lower images for dimension 1: copyIdx = 1
% Upper images for dimension 1: copyIdx = 2
% Lower images for dimension 2: copyIdx = 3
% Upper images for dimension 2: copyIdx = 4
% Corner images (4 possible combinations): copyIdx = 5
% In 3D. If all three dimensions are periodic: there are additional corner images.
% If at least two dimensions are periodic: there are additional edge images.
% Lower images for dimension 1: copyIdx = 1
% Upper images for dimension 1: copyIdx = 2
% Lower images for dimension 2: copyIdx = 3
% Upper images for dimension 2: copyIdx = 4
% Lower images for dimension 3: copyIdx = 5
% Upper images for dimension 3: copyIdx = 6
% Edge images (12 possible combinations): copyIdx = 7, 8, ..., 18
% Corner images (8 possible combinations): copyIdx = 19, 20, ..., 26
% Full example in 2D
%{
% Parameters
n_particles = 10000;
box = [0 10; 0 10]; % Box limits for 2D
PBC = [true, true]; % Periodic boundary conditions in both dimensions
cutoff = 3.0; % Cutoff distance
% Generate random particles within the box
X = rand(n_particles, 2) .* (box(:,2) - box(:,1))' + box(:,1)';
% Calculate fictive images
[Ximages, indX, copyIdx] = PBCimages(X, box, PBC, cutoff);
% Plotting
figure, hold on
plot(X(:,1), X(:,2), 'k.', 'MarkerSize', 15); % Original particles in black
% Define a colormap for different copyIdx values
colormap = lines(max(copyIdx));
for i = 1:max(copyIdx)
idx = (copyIdx == i);
plot(Ximages(idx,1), Ximages(idx,2), '.', 'MarkerSize', 15, 'Color', colormap(i, :)); % Images with different colors
end
xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
xlabel('X');
ylabel('Y');
title('Original Particles and Their Fictive Images');
% Create legend
legendEntries = arrayfun(@(i) sprintf('Copy %d', i), 1:max(copyIdx), 'UniformOutput', false);
legend(['Original Particles', legendEntries]);
grid on, axis equal
%}
% Full example in 3D
%{
% Parameters
n_particles = 10000;
box = [0 10; 0 10; 0 10]; % Box limits for 3D
PBC = [true, true, true]; % Periodic boundary conditions in all dimensions
cutoff = 3.0; % Cutoff distance
% Generate random particles within the box
X = rand(n_particles, 3) .* (box(:,2) - box(:,1))' + box(:,1)';
% Calculate fictive images
[Ximages, indX, copyIdx] = PBCimages(X, box, PBC, cutoff);
% Plotting
figure, hold on
plot3(X(:,1), X(:,2), X(:,3), 'k.', 'MarkerSize', 15); % Original particles in black
% Define a colormap for different copyIdx values
%colormap = lines(max(copyIdx));
% Define a more diverse set of colors (compact definition)
colormap = [0, 0, 1; 0, 1, 0; 1, 0, 0; 0, 1, 1; 1, 0, 1; 1, 1, 0;
0.5, 0, 0; 0, 0.5, 0; 0, 0, 0.5; 0.5, 0.5, 0; 0.5, 0, 0.5;
0, 0.5, 0.5; 0.75, 0.25, 0.25; 0.25, 0.75, 0.25; 0.25, 0.25, 0.75;
0.75, 0.75, 0.25; 0.75, 0.25, 0.75; 0.25, 0.75, 0.75; 0.5, 0.5, 0.5;
0.75, 0.75, 0.75; 0.25, 0.25, 0.25; 1, 0.5, 0; 0, 1, 0.5;
0.5, 0, 1; 0, 0.5, 1; 1, 0, 0.5];
for i = 1:max(copyIdx)
idx = (copyIdx == i);
plot3(Ximages(idx,1), Ximages(idx,2), Ximages(idx,3), '.', 'MarkerSize', 15, 'Color', colormap(i, :)); % Images with different colors
end
xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
zlim([box(3,1) - 2*cutoff, box(3,2) + 2*cutoff]);
xlabel('X'); ylabel('Y'); zlabel('Z');
title('Original Particles and Their Fictive Images');
% Create legend
legendEntries = arrayfun(@(i) sprintf('Copy %d', i), 1:max(copyIdx), 'UniformOutput', false);
legend(['Original Particles', legendEntries]);
grid on;
hold off;
axis equal;
view(3);
%}
% MS 3.0 | 2024-03-15 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-07-16
% Revision history
% 2024-03-15 release candidate with examples in 2D and 3D
% 2024-03-16 return indX, advise using PBCincell if particles outside are detected
% 2024-07-16 add copyIdx and extend 2D and 3D examples to exemplify all copy possibilities
%% Check arguments
if nargin<3, error('Not enough input arguments. Syntax: Ximages = PBCimages(X,box,PBC [,cutoff])'), end
if nargin<4, cutoff = []; end
[n,d] = size(X); % Number of particles and dimensions
if d>3, error('the number of dimensions should be 1,2,3'), end
Xmin = min(X);
Xmax = max(X);
if isempty(cutoff), cutoff = 0.1*(Xmax-Xmin); end % heuristic default value
if length(cutoff)==1, cutoff = cutoff(ones(1,d)); end
if length(cutoff)~=d, error('cutoff must be a scalar or 1x%d vector',d); end
if size(box,2)~=2 || size(box,1)~=d, error('Box dimensions must be a %dx2 vector',d); end
boxlength = diff(box, 1, 2);
if length(PBC)~=d; error('PBC must be a 1x%d logical vector',d), end
PBC = PBC>0;
%% Check that all points lie within the box
incellok = true;
for i=1:d
if box(i,1)>Xmin(i)
dispf('some particles are outside the lower bound %0.3g along dimension %d',box(i,1),i)
incellok = false;
end
if box(i,2)'some particles are outside the upper bound %0.3g along dimension %d',box(i,2),i),
incellok = false;
end
end
if ~incellok
disp('use: X = PBCincell(X,box,PBC) to wrap particles around coordinates')
error('PBCimages: some particles outside the box...')
end
%% Initialize empty array for images
[Ximages,indX,copyIdx] = deal([]);
copyCounter = 0; % Counter to track the number of copies
%% Generate images for each dimension independently
for i = 1:d
if PBC(i)
lowerBound = box(i,1);
upperBound = box(i,2);
% Lower and upper images
islowerImages = X(:, i) < (lowerBound + cutoff(i));
lowerImages = X(islowerImages, :);
lowerImages(:, i) = lowerImages(:, i) + (upperBound - lowerBound);
isupperimages = X(:, i) > (upperBound - cutoff(i));
upperImages = X(isupperimages, :);
upperImages(:, i) = upperImages(:, i) - (upperBound - lowerBound);
Ximages = [Ximages; lowerImages; upperImages]; %#ok
indX = [indX; find(islowerImages); find(isupperimages)]; %#ok
copyIdx = [copyIdx; repmat(copyCounter+1, sum(islowerImages), 1); repmat(copyCounter+2, sum(isupperimages), 1)]; %#ok
copyCounter = copyCounter + 2; % Update the copy counter
end
end
%% Generate corner images if all dimensions are periodic
if all(PBC)
% Find all combinations for corners
if d==2
[x,y]=meshgrid([0 1],[0 1]);
corners = [x(:) y(:)];
elseif d==3
[x,y,z]=meshgrid([0 1],[0 1],[0 1]);
corners = [x(:) y(:) z(:)];
end
for i = 1:size(corners, 1) % 4 (in 2D), 8 (in 3D)
cornerCond = true(n, 1);
cornerImages = X;
for j = 1:d
if corners(i,j) == 0
% Lower corner images
thisCorner = X(:,j) < (box(j,1) + cutoff(j));
cornerImages(thisCorner,j) = cornerImages(thisCorner,j) + boxlength(j);
else
% Upper corner images
thisCorner = X(:,j) > (box(j,2) - cutoff(j));
cornerImages(thisCorner,j) = cornerImages(thisCorner,j) - boxlength(j);
end
cornerCond = cornerCond & thisCorner;
end
% Add corner images if any particles satisfy the corner condition
if any(cornerCond)
Ximages = [Ximages; cornerImages(cornerCond, :)]; %#ok
indX = [indX; find(cornerCond)]; %#ok
copyIdx = [copyIdx; repmat(copyCounter+1, sum(cornerCond), 1)]; %#ok
copyCounter = copyCounter + 1; % Update the copy counter
end
end
end
%% Generate edge images if two dimensions are at least periodic in 3D
if (d==3) && (sum(PBC)>1)
dvalid = find(PBC);
[x,y] = meshgrid(dvalid,dvalid); ok = (x% dimension pairs (with x
[x,y]=meshgrid([0 1],[0 1]);
edges = [x(:) y(:)]; % edge pairs
for k = 1:size(dimforedges,1) % number of dimension pairs (max 3)
for i = 1:size(edges, 1) % number of lower/upper edges (4) ==> 3x4 = 12 edges at maximum
edgeCond = true(n, 1);
edgeImages = X;
for j = 1:2 % for edges in 3D (2 dimensions only)
jdim = dimforedges(k,j);
if edges(i,j) == 0
% Lower edge images
thisEdge = X(:,jdim) < (box(jdim,1) + cutoff(jdim));
edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) + boxlength(jdim);
else
% Upper edge images
thisEdge = X(:,jdim) > (box(jdim,2) - cutoff(jdim));
edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) - boxlength(jdim);
end
end
edgeCond = edgeCond & thisEdge;
% Add edge images if any particles satisfy the edge condition
if any(edgeCond)
Ximages = [Ximages; edgeImages(edgeCond, :)]; %#ok
indX = [indX; find(edgeCond)]; %#ok
copyIdx = [copyIdx; repmat(copyCounter+1, sum(edgeCond), 1)]; %#ok
copyCounter = copyCounter + 1; % Update the copy counter
end
end
end
end
%% Removing duplicates if any
[Ximages,iXuniq] = unique(Ximages, 'rows');
indX = indX(iXuniq);
copyIdx = copyIdx(iXuniq);
% output
if nargout>1, indXout = indX; end
if nargout>2, copyIdx = copyIdx; end
end
%% --- version before 2024-08-16
% function [Ximages,indXout] = PBCimages(X,box,PBC,cutoff)
% %PBCIMAGES return the coordinates of fictive particle images outside box limits along periodic boundary dimensions
% %
% % USAGE: Ximages = PBCimages(X,box,PBC [,cutoff])
% % [Ximages,indX] = PBCimages(...)
% %
% % INPUTS:
% % X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
% % box: 2x2 or 3x2 array coding for box dimensions
% % the box spans along dimension i between box(i,1) and box(i,2)
% % all X values should lie within box limits, if not an error ois generated
% % PBC: 1x2 or 1x3 boolean array
% % PBC(i) is true if the dimension i is periodic
% % cutoff: scalar or 1xd array with d the number of dimensions (2 or 3)
% % setting the cutoff distance beyond bounds to include fictive images
% % if cutoff is a scalar, it is refedined as cutoff(ones(1,d))
% % cutoff(i) is applied along dimension i
% %
% % OUTPUTS:
% % Ximages: mx2 or mx3 array coding for the coordinates of the m particle images in 2D and 3D, respectively
% % indX: corresponding indices of images in X
% %
% %
% % See also: PBCgrid, PBCgridshift, PBCimageschift, PBCincell
% %
%
% % Full example in 2D
% %{
% % Parameters
% n_particles = 10000;
% box = [0 10; 0 10]; % Box limits for 2D
% PBC = [true, true]; % Periodic boundary conditions in both dimensions
% cutoff = 3.0; % Cutoff distance
%
% % Generate random particles within the box
% X = rand(n_particles, 2) .* (box(:,2) - box(:,1))' + box(:,1)';
%
% % Calculate fictive images
% Ximages = PBCimages(X, box, PBC, cutoff);
%
% % Plotting
% figure, hold on
% plot(X(:,1), X(:,2), 'g.', 'MarkerSize', 15); % Original particles in green
% if ~isempty(Ximages)
% plot(Ximages(:,1), Ximages(:,2), 'b.', 'MarkerSize', 15); % Images in blue
% end
% xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
% ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
% xlabel('X');
% ylabel('Y');
% title('Original Particles and Their Fictive Images');
% legend('Original Particles', 'Fictive Images');
% grid on;
% hold off;
% axis equal
% %}
%
% % Full example in 3D
% %{
% % Parameters
% n_particles = 10000;
% box = [0 10; 0 10; 0 10]; % Box limits for 3D
% PBC = [true, true, true]; % Periodic boundary conditions in both dimensions
% cutoff = 3.0; % Cutoff distance
%
% % Generate random particles within the box
% X = rand(n_particles, 3) .* (box(:,2) - box(:,1))' + box(:,1)';
%
% % Calculate fictive images
% Ximages = PBCimages(X, box, PBC, cutoff);
%
% % Plotting
% figure, hold on
% plot3(X(:,1), X(:,2), X(:,3), 'g.', 'MarkerSize', 15); % Original particles in green
% plot3(Ximages(:,1), Ximages(:,2),Ximages(:,3), 'b.', 'MarkerSize', 15); % Images in blue
% xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
% ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
% zlim([box(3,1) - 2*cutoff, box(3,2) + 2*cutoff]);
% xlabel('X'); ylabel('Y'); ylabel('Z'); title('Original Particles and Their Fictive Images');
% legend('Original Particles', 'Fictive Images');
% grid on, hold off, axis equal, view(3)
% %}
%
%
% % MS 3.0 | 2024-03-15 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-16
%
%
% % Revision history
% % 2024-03-15 release canidate with examples in 2D and 3D
% % 2024-03-16 return indX, advise using PBCincell if particles outside are detected
%
% %% Check arguments
% if nargin<3, error('Not enough input arguments. Syntax: Ximages = PBCimages(X,box,PBC [,cutoff])'), end
% if nargin<4, cutoff = []; end
% [n,d] = size(X); % Number of particles and dimensions
% if d>3, error('the number of dimensions should be 1,2,3'), end
% Xmin = min(X);
% Xmax = max(X);
% if isempty(cutoff), cutoff = 0.1*(Xmax-Xmin); end % heuristic default value
% if length(cutoff)==1, cutoff = cutoff(ones(1,d)); end
% if length(cutoff)~=d, error('cutoff must be a scalar or 1x%d vector',d); end
% if size(box,2)~=2 || size(box,1)~=d, error('Box dimensions must be a %dx2 vector',d); end
% boxlength = diff(box, 1, 2);
% if length(PBC)~=d; error('PBC must be a 1x%d logical vector',d), end
% PBC = PBC>0;
%
% %% Check that all points lie within the box
% incellok = true;
% for i=1:d
% if box(i,1)>Xmin(i)
% dispf('some particles are outside the lower bound %0.3g along dimension %d',box(i,1),i)
% incellok = false;
% end
% if box(i,2)
% dispf('some particles are outside the upper bound %0.3g along dimension %d',box(i,2),i),
% incellok = false;
% end
% end
% if ~incellok
% disp('use: X = PBCincell(X,box,PBC) to wrap particles around coordinates')
% error('PBCimages: some particles outside the box...')
% end
%
% %% Initialize empty array for images
% [Ximages,indX] = deal([]);
%
% %% Generate images for each dimension independently
% for i = 1:d
% if PBC(i)
% lowerBound = box(i,1);
% upperBound = box(i,2);
%
% % Lower and upper images
% islowerImages = X(:, i) < (lowerBound + cutoff(i));
% lowerImages = X(islowerImages, :);
% lowerImages(:, i) = lowerImages(:, i) + (upperBound - lowerBound);
%
% isupperimages = X(:, i) > (upperBound - cutoff(i));
% upperImages = X(isupperimages, :);
% upperImages(:, i) = upperImages(:, i) - (upperBound - lowerBound);
%
% Ximages = [Ximages; lowerImages; upperImages]; %#ok
% indX = [indX;find(islowerImages);find(isupperimages)]; %#ok
% end
% end
%
% %% Generate corner images if all dimensions are periodic
% if all(PBC)
% % Find all combinations for corners
% % corners = dec2bin(1:(2^d)-1) - '0';
% if d==2
% [x,y]=meshgrid([0 1],[0 1]);
% corners = [x(:) y(:)];
% elseif d==3
% [x,y,z]=meshgrid([0 1],[0 1],[0 1]);
% corners = [x(:) y(:) z(:)];
% end
%
% for i = 1:size(corners, 1) % 4 (in 2D), 8 (in 3D)
% cornerCond = true(n, 1);
% cornerImages = X;
% for j = 1:d
% if corners(i,j) == 0
% % Lower corner images
% thisCorner = X(:,j) < (box(j,1) + cutoff(j));
% cornerImages(thisCorner,j) = cornerImages(thisCorner,j) + boxlength(j);
% else
% % Upper corner images
% thisCorner = X(:,j) > (box(j,2) - cutoff(j));
% cornerImages(thisCorner,j) = cornerImages(thisCorner,j) - boxlength(j);
% end
% cornerCond = cornerCond & thisCorner;
% end
% % Add corner images if any particles satisfy the corner condition
% if any(cornerCond)
% Ximages = [Ximages; cornerImages(cornerCond, :)]; %#ok
% indX = [indX;find(cornerCond)]; %#ok
% end
% end
% end
%
% %% Generate edge images if two dimensions are at least periodic in 3D
% if (d==3) && (sum(PBC)>1)
% dvalid = find(PBC);
% [x,y] = meshgrid(dvalid,dvalid); ok = (x
% dimforedges = [x(ok) y(ok)]; % dimension pairs (with x
% [x,y]=meshgrid([0 1],[0 1]);
% edges = [x(:) y(:)]; % edge pairs
%
% for k = 1:size(dimforedges,1) % number of dimension pairs (max 3)
% for i = 1:size(edges, 1) % number of lower/upper edges (4) ==> 3x4 = 12 edges at maximum
% edgeCond = true(n, 1);
% edgeImages = X;
% for j = 1:2 % for edges in 3D (2 dimensions only)
% jdim = dimforedges(k,j);
% if edges(i,j) == 0
% % Lower edge images
% thisEdge = X(:,jdim) < (box(jdim,1) + cutoff(jdim));
% edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) + boxlength(jdim);
% else
% % Upper edge images
% thisEdge = X(:,jdim) > (box(jdim,2) - cutoff(jdim));
% edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) - boxlength(jdim);
% end
% end
% edgeCond = edgeCond & thisEdge;
% % Add corner images if any particles satisfy the edge condition
% if any(edgeCond)
% Ximages = [Ximages; edgeImages(edgeCond, :)]; %#ok
% indX = [indX;find(edgeCond)]; %#ok
% end
% end
% end
% end
%
%
% %% Removing duplicates if any
% [Ximages,iXuniq] = unique(Ximages, 'rows');
% indX = indX(iXuniq);
%
% % output
% if nargout>1, indXout = indX; end
PBCIMAGES return the coordinates of fictive particle images outside box limits along periodic boundary dimensions
USAGE: Ximages = PBCimages(X,box,PBC [,cutoff])
[Ximages,indX] = PBCimages(...)
INPUTS:
X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
box: 2x2 or 3x2 array coding for box dimensions
the box spans along dimension i between box(i,1) and box(i,2)
all X values should lie within box limits, if not an error ois generated
PBC: 1x2 or 1x3 boolean array
PBC(i) is true if the dimension i is periodic
cutoff: scalar or 1xd array with d the number of dimensions (2 or 3)
setting the cutoff distance beyond bounds to include fictive images
if cutoff is a scalar, it is refedined as cutoff(ones(1,d))
cutoff(i) is applied along dimension i
OUTPUTS:
Ximages: mx2 or mx3 array coding for the coordinates of the m particle images in 2D and 3D, respectively
indX: corresponding indices of images in X
See also
PBCgrid, PBCgridshift, PBCimageschift, PBCincell
See also
function [Ximages,indXout] = PBCimages0(X,box,PBC,cutoff)
%PBCIMAGES return the coordinates of fictive particle images outside box limits along periodic boundary dimensions
%
% USAGE: Ximages = PBCimages(X,box,PBC [,cutoff])
% [Ximages,indX] = PBCimages(...)
%
% INPUTS:
% X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
% box: 2x2 or 3x2 array coding for box dimensions
% the box spans along dimension i between box(i,1) and box(i,2)
% all X values should lie within box limits, if not an error ois generated
% PBC: 1x2 or 1x3 boolean array
% PBC(i) is true if the dimension i is periodic
% cutoff: scalar or 1xd array with d the number of dimensions (2 or 3)
% setting the cutoff distance beyond bounds to include fictive images
% if cutoff is a scalar, it is refedined as cutoff(ones(1,d))
% cutoff(i) is applied along dimension i
%
% OUTPUTS:
% Ximages: mx2 or mx3 array coding for the coordinates of the m particle images in 2D and 3D, respectively
% indX: corresponding indices of images in X
%
%
% See also: PBCgrid, PBCgridshift, PBCimageschift, PBCincell
%
% Full example in 2D
%{
% Parameters
n_particles = 10000;
box = [0 10; 0 10]; % Box limits for 2D
PBC = [true, true]; % Periodic boundary conditions in both dimensions
cutoff = 3.0; % Cutoff distance
% Generate random particles within the box
X = rand(n_particles, 2) .* (box(:,2) - box(:,1))' + box(:,1)';
% Calculate fictive images
Ximages = PBCimages(X, box, PBC, cutoff);
% Plotting
figure, hold on
plot(X(:,1), X(:,2), 'g.', 'MarkerSize', 15); % Original particles in green
if ~isempty(Ximages)
plot(Ximages(:,1), Ximages(:,2), 'b.', 'MarkerSize', 15); % Images in blue
end
xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
xlabel('X');
ylabel('Y');
title('Original Particles and Their Fictive Images');
legend('Original Particles', 'Fictive Images');
grid on;
hold off;
axis equal
%}
% Full example in 3D
%{
% Parameters
n_particles = 10000;
box = [0 10; 0 10; 0 10]; % Box limits for 3D
PBC = [true, true, true]; % Periodic boundary conditions in both dimensions
cutoff = 3.0; % Cutoff distance
% Generate random particles within the box
X = rand(n_particles, 3) .* (box(:,2) - box(:,1))' + box(:,1)';
% Calculate fictive images
Ximages = PBCimages(X, box, PBC, cutoff);
% Plotting
figure, hold on
plot3(X(:,1), X(:,2), X(:,3), 'g.', 'MarkerSize', 15); % Original particles in green
plot3(Ximages(:,1), Ximages(:,2),Ximages(:,3), 'b.', 'MarkerSize', 15); % Images in blue
xlim([box(1,1) - 2*cutoff, box(1,2) + 2*cutoff]);
ylim([box(2,1) - 2*cutoff, box(2,2) + 2*cutoff]);
zlim([box(3,1) - 2*cutoff, box(3,2) + 2*cutoff]);
xlabel('X'); ylabel('Y'); ylabel('Z'); title('Original Particles and Their Fictive Images');
legend('Original Particles', 'Fictive Images');
grid on, hold off, axis equal, view(3)
%}
% MS 3.0 | 2024-03-15 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-16
% Revision history
% 2024-03-15 release canidate with examples in 2D and 3D
% 2024-03-16 return indX, advise using PBCincell if particles outside are detected
%% Check arguments
if nargin<3, error('Not enough input arguments. Syntax: Ximages = PBCimages(X,box,PBC [,cutoff])'), end
if nargin<4, cutoff = []; end
[n,d] = size(X); % Number of particles and dimensions
if d>3, error('the number of dimensions should be 1,2,3'), end
Xmin = min(X);
Xmax = max(X);
if isempty(cutoff), cutoff = 0.1*(Xmax-Xmin); end % heuristic default value
if length(cutoff)==1, cutoff = cutoff(ones(1,d)); end
if length(cutoff)~=d, error('cutoff must be a scalar or 1x%d vector',d); end
if size(box,2)~=2 || size(box,1)~=d, error('Box dimensions must be a %dx2 vector',d); end
boxlength = diff(box, 1, 2);
if length(PBC)~=d; error('PBC must be a 1x%d logical vector',d), end
PBC = PBC>0;
%% Check that all points lie within the box
incellok = true;
for i=1:d
if box(i,1)>Xmin(i)
dispf('some particles are outside the lower bound %0.3g along dimension %d',box(i,1),i)
incellok = false;
end
if box(i,2)'some particles are outside the upper bound %0.3g along dimension %d',box(i,2),i),
incellok = false;
end
end
if ~incellok
disp('use: X = PBCincell(X,box,PBC) to wrap particles around coordinates')
error('PBCimages: some particles outside the box...')
end
%% Initialize empty array for images
[Ximages,indX] = deal([]);
%% Generate images for each dimension independently
for i = 1:d
if PBC(i)
lowerBound = box(i,1);
upperBound = box(i,2);
% Lower and upper images
islowerImages = X(:, i) < (lowerBound + cutoff(i));
lowerImages = X(islowerImages, :);
lowerImages(:, i) = lowerImages(:, i) + (upperBound - lowerBound);
isupperimages = X(:, i) > (upperBound - cutoff(i));
upperImages = X(isupperimages, :);
upperImages(:, i) = upperImages(:, i) - (upperBound - lowerBound);
Ximages = [Ximages; lowerImages; upperImages]; %#ok
indX = [indX;find(islowerImages);find(isupperimages)]; %#ok
end
end
%% Generate corner images if all dimensions are periodic
if all(PBC)
% Find all combinations for corners
% corners = dec2bin(1:(2^d)-1) - '0';
if d==2
[x,y]=meshgrid([0 1],[0 1]);
corners = [x(:) y(:)];
elseif d==3
[x,y,z]=meshgrid([0 1],[0 1],[0 1]);
corners = [x(:) y(:) z(:)];
end
for i = 1:size(corners, 1) % 4 (in 2D), 8 (in 3D)
cornerCond = true(n, 1);
cornerImages = X;
for j = 1:d
if corners(i,j) == 0
% Lower corner images
thisCorner = X(:,j) < (box(j,1) + cutoff(j));
cornerImages(thisCorner,j) = cornerImages(thisCorner,j) + boxlength(j);
else
% Upper corner images
thisCorner = X(:,j) > (box(j,2) - cutoff(j));
cornerImages(thisCorner,j) = cornerImages(thisCorner,j) - boxlength(j);
end
cornerCond = cornerCond & thisCorner;
end
% Add corner images if any particles satisfy the corner condition
if any(cornerCond)
Ximages = [Ximages; cornerImages(cornerCond, :)]; %#ok
indX = [indX;find(cornerCond)]; %#ok
end
end
end
%% Generate edge images if two dimensions are at least periodic in 3D
if (d==3) && (sum(PBC)>1)
dvalid = find(PBC);
[x,y] = meshgrid(dvalid,dvalid); ok = (x% dimension pairs (with x
[x,y]=meshgrid([0 1],[0 1]);
edges = [x(:) y(:)]; % edge pairs
for k = 1:size(dimforedges,1) % number of dimension pairs (max 3)
for i = 1:size(edges, 1) % number of lower/upper edges (4) ==> 3x4 = 12 edges at maximum
edgeCond = true(n, 1);
edgeImages = X;
for j = 1:2 % for edges in 3D (2 dimensions only)
jdim = dimforedges(k,j);
if edges(i,j) == 0
% Lower edge images
thisEdge = X(:,jdim) < (box(jdim,1) + cutoff(jdim));
edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) + boxlength(jdim);
else
% Upper edge images
thisEdge = X(:,jdim) > (box(jdim,2) - cutoff(jdim));
edgeImages(thisEdge,jdim) = edgeImages(thisEdge,jdim) - boxlength(jdim);
end
end
edgeCond = edgeCond & thisEdge;
% Add corner images if any particles satisfy the edge condition
if any(edgeCond)
Ximages = [Ximages; edgeImages(edgeCond, :)]; %#ok
indX = [indX;find(edgeCond)]; %#ok
end
end
end
end
%% Removing duplicates if any
[Ximages,iXuniq] = unique(Ximages, 'rows');
indX = indX(iXuniq);
% output
if nargout>1, indXout = indX; end
PBCIMAGESSHIFT shift X coordinates assuming periodic conditions
USAGE: Ximages = PBCimages(X,box,Pshift)
[Ximages,updtbox] = PBCimages(...)
INPUTS:
X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
Pshift: 1x2 or 1x3 array coding for the translation to apply
box: 2x2 or 3x2 array coding for current box dimensions (before translation)
the box spans along dimension i between box(i,1) and box(i,2)
all X values should lie within box limits, if not an error is generated
OUTPUTS:
Ximages: nx2 or nx3 array coding for the translated coordinates wrapped around periodic boundaries
updtbox: 2x2 or 3x3 updated box coordinates
See also
PBCgrid, PBCgridshift, PBCimages, PBCincell
function [Ximages,updtbox] = PBCimagesshift(X,Pshift,box)
%PBCIMAGESSHIFT shift X coordinates assuming periodic conditions
%
% USAGE: Ximages = PBCimages(X,box,Pshift)
% [Ximages,updtbox] = PBCimages(...)
%
% INPUTS:
% X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
% Pshift: 1x2 or 1x3 array coding for the translation to apply
% box: 2x2 or 3x2 array coding for current box dimensions (before translation)
% the box spans along dimension i between box(i,1) and box(i,2)
% all X values should lie within box limits, if not an error is generated
%
% OUTPUTS:
% Ximages: nx2 or nx3 array coding for the translated coordinates wrapped around periodic boundaries
% updtbox: 2x2 or 3x3 updated box coordinates
%
%
% See also: PBCgrid, PBCgridshift, PBCimages, PBCincell
% MS 3.0 | 2024-03-24 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-16
% Revision history
%% Check arguments
if nargin<3, error('Not enough input arguments. Syntax: Ximages = PBCimagesshift(X,Pshift,box)'), end
[n,d] = size(X); % Number of particles and dimensions
if d>3, error('the number of dimensions should be 1,2,3'), end
Xmin = min(X);
Xmax = max(X);
if size(box,2)~=2 || size(box,1)~=d, error('Box dimensions must be a %dx2 vector',d); end
boxlength = diff(box, 1, 2);
%% Check that all points lie within the box
incellok = true;
for i=1:d
if box(i,1)>Xmin(i)
dispf('some particles are outside the lower bound %0.3g along dimension %d',box(i,1),i)
incellok = false;
end
if box(i,2)'some particles are outside the upper bound %0.3g along dimension %d',box(i,2),i),
incellok = false;
end
end
if ~incellok
disp('use: X = PBCincell(X,box,PBC) to wrap particles around coordinates')
error('PBCimages: some particles outside the box...')
end
%% Apply shift and wrap around using periodic boundary conditions
boxtranslated = zeros(size(box));
Ximages = zeros(size(X),class(X));
for i = 1:d
boxtranslated(i,:) = box(i,:) - Pshift(i);
Ximages(:, i) = mod(X(:, i) - box(i, 1) + Pshift(i), boxlength(i)) + boxtranslated(i, 1);
end
%% Output
if nargout>1
updtbox = boxtranslated;
end
PBCINCELL force incell coordinates (without X coordinates outside the box along perodic coordinates)
USAGE: Xincell = PBCincell(X,box,PBC)
INPUTS:
X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
box: 2x2 or 3x2 array coding for box dimensions
the box spans along dimension i between box(i,1) and box(i,2)
PBC: 1x2 or 1x3 boolean array
PBC(i) is true if the dimension i is periodic
OUTPUTS:
Xincell: nx2 or nx3 array coding for the coordinates incell
See also
PBCgrid, PBCgridshift, PBCimages, PBCimageschift, PBCincell
function Xincell = PBCincell(X,box,PBC)
%PBCINCELL force incell coordinates (without X coordinates outside the box along perodic coordinates)
%
% USAGE: Xincell = PBCincell(X,box,PBC)
%
% INPUTS:
% X: nx2 or nx3 array coding for the coordinates of the n particles in 2D and 3D, respectively
% box: 2x2 or 3x2 array coding for box dimensions
% the box spans along dimension i between box(i,1) and box(i,2)
% PBC: 1x2 or 1x3 boolean array
% PBC(i) is true if the dimension i is periodic
%
% OUTPUTS:
% Xincell: nx2 or nx3 array coding for the coordinates incell
%
%
%
% See also: PBCgrid, PBCgridshift, PBCimages, PBCimageschift, PBCincell
% MS 3.0 | 2024-03-16 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr |
% Revision history
% Initialize the output array with the same size as input coordinates
Xincell = X;
% Number of dimensions (2D or 3D)
dims = size(X, 2);
% Loop over each dimension to apply periodic boundary conditions
for i = 1:dims
if PBC(i)
% Get the box size for the current dimension
boxSize = box(i, 2) - box(i, 1);
% Adjust positions for periodic boundary conditions
% Use mod to wrap coordinates within the box dimensions
Xincell(:, i) = mod(X(:, i) - box(i, 1), boxSize) + box(i, 1);
end
end
PBCOUTCELL reunite atoms crossing PBC boundaries on one side based on the centroid's location
USAGE: XYZout = PBCoutcell(XYZ, box, PBC)
[XYZout, isReunited] = PBCoutcell(...)
INPUTS:
XYZ: nx2 or nx3 array coding for the coordinates of the n atoms in 2D or 3D
box: 2x2 or 3x2 array coding for box dimensions
the box spans along dimension i between box(i,1) and box(i,2)
PBC: 1x2 or 1x3 boolean array
PBC(i) is true if the dimension i is periodic
OUTPUT:
XYZout: nx2 or nx3 array with coordinates adjusted to reunite atoms on one side of the boundaries
isReunited: true if reunion has been applied
function [XYZout, isReunitedout] = PBCoutcell(XYZ, box, PBC)
%PBCOUTCELL reunite atoms crossing PBC boundaries on one side based on the centroid's location
%
% USAGE: XYZout = PBCoutcell(XYZ, box, PBC)
% [XYZout, isReunited] = PBCoutcell(...)
%
% INPUTS:
% XYZ: nx2 or nx3 array coding for the coordinates of the n atoms in 2D or 3D
% box: 2x2 or 3x2 array coding for box dimensions
% the box spans along dimension i between box(i,1) and box(i,2)
% PBC: 1x2 or 1x3 boolean array
% PBC(i) is true if the dimension i is periodic
%
% OUTPUT:
% XYZout: nx2 or nx3 array with coordinates adjusted to reunite atoms on one side of the boundaries
% isReunited: true if reunion has been applied
% Example in 2D:
%{
% Box limits
box = [0 10; 0 10];
% PBC in both dimensions
PBC = [true, true];
% Generate random points in a disk
n_particles = 10000;
radius = 0.55 * min(box(:,2) - box(:,1)) / 2;
theta = 2 * pi * rand(n_particles, 1);
r = radius * sqrt(rand(n_particles, 1));
center = (box(:,2) - box(:,1))' .* rand(1, 2) + box(:,1)';
XYZ0 = [center(1) + r .* cos(theta), center(2) + r .* sin(theta)];
% Apply PBC to bring coordinates within the box
XYZ = PBCincell(XYZ0,box,PBC);
XYZout = PBCoutcell(XYZ, box, PBC);
% Plotting
figure, hold on
plot(XYZ0(:,1), XYZ0(:,2), 'g.', 'MarkerSize', 10); % OUTCELL Original points in green
plot(XYZ(:,1), XYZ(:,2), 'r.', 'MarkerSize', 10); % Original INCELL points in red
plot(XYZout(:,1), XYZout(:,2), 'b.', 'MarkerSize', 10); % Adjusted points in blue
xlabel('X'); ylabel('Y'); title('Reunited Atoms in 2D');
legend('True', 'Original', 'Adjusted');
grid on; axis equal; hold off;
%}
%
% Example in 3D:
%{
% Box limits
box = [0 10; 0 10; 0 10];
% PBC in all dimensions
PBC = [true, true, true];
% Generate random points in a sphere
n_particles = 10000;
radius = 4.4*0.25 * min(box(:,2) - box(:,1)) / 2;
phi = 2 * pi * rand(n_particles, 1);
costheta = 2 * rand(n_particles, 1) - 1;
u = rand(n_particles, 1);
theta = acos(costheta);
r = radius * u.^(1/3);
center = (box(:,2) - box(:,1))' .* rand(1, 3) + box(:,1)';
XYZ0 = [center(1) + r .* sin(theta) .* cos(phi), ...
center(2) + r .* sin(theta) .* sin(phi), ...
center(3) + r .* cos(theta)];
% Apply PBC to bring coordinates within the box
XYZ = PBCincell(XYZ0,box,PBC)
XYZout = PBCoutcell(XYZ, box, PBC);
figure, hold on
plot3(XYZ0(:,1), XYZ0(:,2), XYZ0(:,3), 'g.', 'MarkerSize', 10); % OUTCELL Original points in green
plot3(XYZ(:,1), XYZ(:,2), XYZ(:,3), 'r.', 'MarkerSize', 10); % Original INCELL points in red
plot3(XYZout(:,1), XYZout(:,2), XYZout(:,3), 'b.', 'MarkerSize', 10); % Adjusted points in blue
xlabel('X'); ylabel('Y'); zlabel('Z'); title('Reunited Atoms in 3D');
legend('True','Original', 'Adjusted');
grid on; axis equal; view(3); hold off;
%}
[n, d] = size(XYZ);
% Validate inputs
if size(box, 2) ~= 2 || size(box, 1) ~= d
error('Box dimensions must be a %dx2 vector', d);
end
if length(PBC) ~= d
error('PBC must be a 1x%d logical vector', d);
end
% Calculate the center of the box
box_center = mean(box, 2)';
% Initialize output
XYZout = XYZ;
isReunited = false;
% Maximum iterations to avoid infinite loops
max_iters = 10;
iter = 0;
while iter < max_iters
iter = iter + 1;
XYZout_prev = XYZout;
% Calculate the centroid of the points
centroid = mean(XYZout, 1);
% Adjust coordinates based on the centroid's location
for i = 1:d
if PBC(i)
box_length = box(i, 2) - box(i, 1);
for j = 1:n
if abs(XYZout(j, i) - centroid(i)) > box_length / 2
if XYZout(j, i) > centroid(i)
XYZout(j, i) = XYZout(j, i) - box_length;
else
XYZout(j, i) = XYZout(j, i) + box_length;
end
end
end
end
end
% Recalculate the centroid
new_centroid = mean(XYZout, 1);
% If the output is stable, break the loop
if isequal(XYZout, XYZout_prev)
break;
end
% Update the centroid
centroid = new_centroid;
end
% Calculate the inertia before and after adjustment
initial_inertia = sum(sum((XYZ - mean(XYZ, 1)).^2));
adjusted_inertia = sum(sum((XYZout - mean(XYZout, 1)).^2));
% If the adjusted inertia is larger, revert to the original coordinates
if adjusted_inertia >= initial_inertia
XYZout = XYZ;
isReunited = false;
else
isReunited = true;
end
if nargout > 1
isReunitedout = isReunited;
end
end
% function [XYZout,isReunitedout] = PBCoutcell(XYZ, box, PBC)
% %PBCOUTCELL reunite atoms crossing PBC boundaries on one side based on the centroid's location
% %
% % USAGE: XYZout = PBCoutcell(XYZ, box, PBC)
% % [XYZout,isreunited] = PBCoutcell(...)
% %
% % INPUTS:
% % XYZ: nx2 or nx3 array coding for the coordinates of the n atoms in 2D or 3D
% % box: 2x2 or 3x2 array coding for box dimensions
% % the box spans along dimension i between box(i,1) and box(i,2)
% % PBC: 1x2 or 1x3 boolean array
% % PBC(i) is true if the dimension i is periodic
% %
% % OUTPUT:
% % XYZout: nx2 or nx3 array with coordinates adjusted to reunite atoms on one side of the boundaries
% % isreunited: true if reunion has been applied
%
% % Example in 2D:
% %{
% % Box limits
% box = [0 10; 0 10];
% % PBC in both dimensions
% PBC = [true, true];
% % Generate random points in a disk
% n_particles = 10000;
% radius = 0.35 * min(box(:,2) - box(:,1)) / 2;
% theta = 2 * pi * rand(n_particles, 1);
% r = radius * sqrt(rand(n_particles, 1));
% center = (box(:,2) - box(:,1))' .* rand(1, 2) + box(:,1)';
% XYZ0 = [center(1) + r .* cos(theta), center(2) + r .* sin(theta)];
% % Adjust coordinates
% XYZ = PBCincell(XYZ0,box,PBC);
% XYZout = PBCoutcell(XYZ, box, PBC);
% % Plotting
% figure, hold on
% plot(XYZ(:,1), XYZ(:,2), 'r.', 'MarkerSize', 10); % Original points in red
% plot(XYZout(:,1), XYZout(:,2), 'b.', 'MarkerSize', 10); % Adjusted points in blue
% xlabel('X'); ylabel('Y'); title('Reunited Atoms in 2D');
% legend('Original', 'Adjusted');
% grid on; axis equal; hold off;
% %}
% %
% % Example in 3D:
% %{
% % Box limits
% box = [0 10; 0 10; 0 10];
% % PBC in all dimensions
% PBC = [true, true, true];
% % Generate random points in a sphere
% n_particles = 10000;
% radius = 0.25 * min(box(:,2) - box(:,1)) / 2;
% phi = 2 * pi * rand(n_particles, 1);
% costheta = 2 * rand(n_particles, 1) - 1;
% u = rand(n_particles, 1);
% theta = acos(costheta);
% r = radius * u.^(1/3);
% center = (box(:,2) - box(:,1))' .* rand(1, 3) + box(:,1)';
% XYZ0 = [center(1) + r .* sin(theta) .* cos(phi), ...
% center(2) + r .* sin(theta) .* sin(phi), ...
% center(3) + r .* cos(theta)];
% % Adjust coordinates
% XYZ = PBCincell(XYZ0,box,PBC)
% XYZout = PBCoutcell(XYZ, box, PBC);
% figure, hold on
% plot3(XYZ0(:,1), XYZ0(:,2), XYZ0(:,3), 'g.', 'MarkerSize', 10); % OUTCELL Original points in green
% plot3(XYZ(:,1), XYZ(:,2), XYZ(:,3), 'r.', 'MarkerSize', 10); % Original INCELL points in red
% plot3(XYZout(:,1), XYZout(:,2), XYZout(:,3), 'b.', 'MarkerSize', 10); % Adjusted points in blue
% xlabel('X'); ylabel('Y'); zlabel('Z'); title('Reunited Atoms in 3D');
% legend('True','Original', 'Adjusted');
% grid on; axis equal; view(3); hold off;
% %}
%
%
% % MS 3.0 | 2024-07-16 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev.
%
%
% % Revision history
%
%
% % Number of atoms and dimensions
% [n, d] = size(XYZ);
%
% % Validate inputs
% if size(box,2) ~= 2 || size(box,1) ~= d
% error('Box dimensions must be a %dx2 vector', d);
% end
%
% if length(PBC) ~= d
% error('PBC must be a 1x%d logical vector', d);
% end
%
% % Calculate the centroid of the object
% centroid = mean(XYZ, 1);
%
% % Calculate the initial inertia
% initial_inertia = sum(sum((XYZ - centroid).^2));
%
% % Initialize output
% XYZout = XYZ;
%
% % Adjust coordinates based on the centroid's half-space
% for i = 1:d
% if PBC(i)
% box_length = box(i,2) - box(i,1);
% half_box_length = box_length / 2;
% centroid_half_space = (centroid(i) > (box(i,1) + half_box_length));
%
% % Check and adjust atoms' coordinates
% for j = 1:n
% if centroid_half_space
% % If centroid is in the upper half-space, move atoms in the lower half-space up
% if XYZ(j,i) < (box(i,1) + half_box_length)
% XYZout(j,i) = XYZout(j,i) + box_length;
% end
% else
% % If centroid is in the lower half-space, move atoms in the upper half-space down
% if XYZ(j,i) > (box(i,1) + half_box_length)
% XYZout(j,i) = XYZout(j,i) - box_length;
% end
% end
% end
% end
% end
%
% % Calculate the adjusted inertia
% adjusted_inertia = sum(sum((XYZout - mean(XYZout, 1)).^2));
%
% % If the adjusted inertia is larger, revert to the original coordinates
% if adjusted_inertia >= initial_inertia
% XYZout = XYZ;
% isReunited = false;
% else
% isReunited = true;
% end
%
% if nargout>1, isReunitedout = isReunited; end
%
% end
--------------------------------------------------
function SetQuiverColor(q,currentColormap)
INPUT:
q = handle to quiver plot
currentColormap = e.g. jet;
OPTIONAL INPUT ('Field',value):
'range' = [min,max]; % Range of the magnitude in the colorbar
(used to possibly saturate or expand the color used compared to the vectors)
'mags' = magnitude; % Actual magnitude of the vectors
Example
[x,y] = meshgrid(-2:.2:2,-1:.15:1);
z = x .* exp(-x.^2 - y.^2);
[u,v,w] = surfnorm(x,y,z);
q = quiver3(x,y,z,u,v,w);
mag = 1+3.*rand(size(u)); % Creates number between 1 and 4
colormap(jet);
colorbar;
SetQuiverColor(q,jet,'mags',mag,'range',[-2 8]); % Color range between -2 8 => all colors are not used
caxis([-2 8]);
set(gca,'Color','k');
Example
--------------------------------------------------
Authorship:
This code is heavily based from the answer by the user Suever on Stackoverflow forum
at: https://stackoverflow.com/questions/29632430/quiver3-arrow-color-corresponding-to-magnitude
Example
I, Alexandre De Spiegeleer, only added minor changes to the original answer to have a bit more flexibility.
--------------------------------------------------
// Set default values
function SetQuiverColor(q,currentColormap,varargin)
%--------------------------------------------------
% function SetQuiverColor(q,currentColormap)
%
% INPUT:
% q = handle to quiver plot
% currentColormap = e.g. jet;
% OPTIONAL INPUT ('Field',value):
% 'range' = [min,max]; % Range of the magnitude in the colorbar
% (used to possibly saturate or expand the color used compared to the vectors)
% 'mags' = magnitude; % Actual magnitude of the vectors
%
% Example:
% [x,y] = meshgrid(-2:.2:2,-1:.15:1);
% z = x .* exp(-x.^2 - y.^2);
% [u,v,w] = surfnorm(x,y,z);
% q = quiver3(x,y,z,u,v,w);
% mag = 1+3.*rand(size(u)); % Creates number between 1 and 4
% colormap(jet);
% colorbar;
% SetQuiverColor(q,jet,'mags',mag,'range',[-2 8]); % Color range between -2 8 => all colors are not used
% caxis([-2 8]);
% set(gca,'Color','k');
%
%--------------------------------------------------
% Authorship:
% This code is heavily based from the answer by the user Suever on Stackoverflow forum
% at: https://stackoverflow.com/questions/29632430/quiver3-arrow-color-corresponding-to-magnitude
%
% I, Alexandre De Spiegeleer, only added minor changes to the original answer to have a bit more flexibility.
%--------------------------------------------------
%// Set default values
range = [];
mags = [];
%// Read the optional range value
if find(strcmp('range',varargin))
range = varargin{ find(strcmp('range',varargin))+1 };
end
qU = q.UData(~isnan(q.UData));
qV = q.VData(~isnan(q.VData));
qW = q.WData(~isnan(q.WData));
%// Compute/read the magnitude of the vectors
if find(strcmp('mags',varargin))
mags = varargin{ find(strcmp('mags',varargin))+1 };
mags = mags(~isnan(mags)&~isnan(q.UData)); % This reshapes automatically
else
mags = sqrt(sum(cat(2, qU, qV, ...
reshape(qW, numel(qU), [])).^2, 2));
end
%// If range is auto, take range as the min and max of mags
if isstr(range) & strcmp(range,'auto')
range = [min(mags) max(mags)];
end
%// Change value depending on the desired range
if ~isempty(range) & isnumeric(range) & numel(range)==2
range = sort(range);
mags(mags>range(2)) = range(2);
mags(magsend
%// Now determine the color to make each arrow using a colormap
if ~isempty(range) & isnumeric(range) & numel(range)==2
Edges = linspace(range(1),range(2),size(currentColormap, 1)+1);
[~, ~, ind] = histcounts(mags, Edges);
else
[~, ~, ind] = histcounts(mags, size(currentColormap, 1));
end
%// Now map this to a colormap to get RGB
cmap = uint8(ind2rgb(ind(:), currentColormap) * 255);
cmap(:,:,4) = 255;
cmap = permute(repmat(cmap, [1 3 1]), [2 1 3]);
%// Color data
cd_head = reshape(cmap(1:3,:,:), [], 4).';
cd_tail = reshape(cmap(1:2,:,:), [], 4).';
%// We repeat each color 3 times (using 1:3 below) because each arrow has 3 vertices
set(q.Head, 'ColorBinding', 'interpolated', 'ColorData', cd_head);
%// We repeat each color 2 times (using 1:2 below) because each tail has 2 vertices
set(q.Tail, 'ColorBinding', 'interpolated', 'ColorData', cd_tail);
--------------------------------------------------
function SetQuiverLength(q,mags)
Function that sets the length of the quiver
You can give a list of length (in x,y axis units), the function will rescale the vectors to the right length
Input: q = handle to quiver plot, can be quiver or quiver3
mags = Desired length of each vector in units of the x,y axis
If mags is a scalar, all the vector will have that length
Optional inputs: given as ('variable',value)
'HeadLength' = single value for the length of the head in units of the xyz axis.
It is applied to all the vectors
'HeadAngle' = angle between the two lines forming the head
default=28.0724^\circ
'RotHead' = Angle [deg] by which the head will be rotated around the vector axis.
This allows to set the head in different planes
Note
For some unknown reason, MATLAB does not always simply change the length of the vectors and requires a
pause statement towards the end.
Would your vectors not be the right size, increase the duration of the pause
(or even better, suggest a solution that does not require a pause)
Note
Example
[x,y] = meshgrid(-2:.2:2,-1:.15:1);
z = x .* exp(-x.^2 - y.^2);
[u,v,w] = surfnorm(x,y,z);
q = quiver3(x,y,z,u,v,w); hold on; surf(x,y,z); hold off;
drawnow; % This is needed as, if the plot is not plotted, the VertexData for the quiver are not existent.
view(180,0);
mag = 0.1*ones(size(u)); % Length of the vectors : 0.1
SetQuiverLength(q,mag,'HeadLength',0.05,'HeadAngle',90);
Example
--------------------------------------------------
Authorship:
Function made by Alexandre De Spiegeleer. Feel free to use it.
--------------------------------------------------
// Set default values of varargin
function SetQuiverLength(q,mags,varargin)
%--------------------------------------------------
% function SetQuiverLength(q,mags)
%
% Function that sets the length of the quiver
% You can give a list of length (in x,y axis units), the function will rescale the vectors to the right length
%
% Input: q = handle to quiver plot, can be quiver or quiver3
% mags = Desired length of each vector in units of the x,y axis
% If mags is a scalar, all the vector will have that length
% Optional inputs: given as ('variable',value)
% 'HeadLength' = single value for the length of the head in units of the xyz axis.
% It is applied to all the vectors
% 'HeadAngle' = angle between the two lines forming the head
% default=28.0724^\circ
% 'RotHead' = Angle [deg] by which the head will be rotated around the vector axis.
% This allows to set the head in different planes
%
% NOTE: For some unknown reason, MATLAB does not always simply change the length of the vectors and requires a
% pause statement towards the end.
% Would your vectors not be the right size, increase the duration of the pause
% (or even better, suggest a solution that does not require a pause)
%
% Example:
% [x,y] = meshgrid(-2:.2:2,-1:.15:1);
% z = x .* exp(-x.^2 - y.^2);
% [u,v,w] = surfnorm(x,y,z);
% q = quiver3(x,y,z,u,v,w); hold on; surf(x,y,z); hold off;
% drawnow; % This is needed as, if the plot is not plotted, the VertexData for the quiver are not existent.
% view(180,0);
% mag = 0.1*ones(size(u)); % Length of the vectors : 0.1
% SetQuiverLength(q,mag,'HeadLength',0.05,'HeadAngle',90);
%
%--------------------------------------------------
% Authorship:
% Function made by Alexandre De Spiegeleer. Feel free to use it.
%--------------------------------------------------
%// Set default values of varargin
HeadLength = [];
HeadAngle = [];
RotHead = [];
%// Read the optional inputs
if find(strcmp('HeadLength',varargin))
HeadLength = varargin{ find(strcmp('HeadLength',varargin))+1 };
end
if find(strcmp('HeadAngle',varargin))
HeadAngle = varargin{ find(strcmp('HeadAngle',varargin))+1 };
end
if find(strcmp('RotHead',varargin))
RotHead = varargin{ find(strcmp('RotHead',varargin))+1 };
end
%// Start by removing the autoscale option
set(q,'AutoScale','Off');
%// Get the Head and Tail options
H = get(q.Head);
T = get(q.Tail);
%// Handle mags input
if isempty(mags)
warning('No input was given for the length of the vectors. They are now all of length 1.');
mags = ones(size(T.VertexData,2)/2,1);
elseif numel(mags) == 1
mags = repmat(mags,size(T.VertexData,2)/2,1);
elseif size(mags) == size(q.XData)
mags = reshape(mags,[],1);
elseif isrow(mags)
mags = mags';
elseif numel(mags)~=size(T.VertexData,2)/2
warning('Different number of magnitudes and quiver vectors!');
return;
else
warning('Bug when calling SetQuiverLength function!');
return;
end
if find(mags<0)
warning(['At least one of the length is requested to be negative. ' ...
'This will swap the direction of the vector(s)!']);
end
%// The data for which there is no vector should be removed
%// as there is no Head or Tail data for those.
if numel(mags) == numel(q.UData)
mags(isnan(q.UData)) = [];
end
%// Reshape the head and the tail (It is easier to think about it then but requires some repmat and permute)
%// The head has 3 vertices and the tail has 2.
Tail_ori = reshape(T.VertexData,size(T.VertexData,1),2,[]);
Head_ori = reshape(H.VertexData,size(H.VertexData,1),3,[]);
%// Measure original length of Head and Tail
Tail_length = squeeze(sqrt(sum(diff(Tail_ori,1,2).^2,1)));
Head_length = squeeze(sqrt(sum(diff(Head_ori(:,[1,2],:),1,2).^2,1)));
%// Head-Tail original ratio
Length_ratio = Head_length./Tail_length;
%// New length of head
if isempty(HeadLength)
Head_mag = mags.*Length_ratio;
Head_mag = permute(Head_mag(:,:,ones(size(H.VertexData,1),1)),[3 2 1]);
elseif ~isempty(HeadLength)
Head_mag = repmat(HeadLength,[size(H.VertexData,1) 1 size(Head_ori,3)]);
end
%// Direction tail
Tail_dir = diff(Tail_ori,1,2)./permute(Tail_length(:,:,ones(size(T.VertexData,1),1)),[3 2 1]);
%// Directions of head
Head_dir_a = diff(Head_ori(:,[2,1],:),1,2)./permute(Head_length(:,:,ones(size(H.VertexData,1),1)),[3 2 1]);
Head_dir_b = diff(Head_ori(:,[2,3],:),1,2)./permute(Head_length(:,:,ones(size(H.VertexData,1),1)),[3 2 1]);
%// Set new HeadAngle by use of Rodrigues' rotation formula
if ~isempty(HeadAngle)
% rotation axis:
k = cross(Head_dir_b,Head_dir_a,1)./sqrt(sum(cross(Head_dir_b,Head_dir_a,1).^2,1));
% Original angle:
alpha = acosd( dot(Head_dir_a,Head_dir_b,1) );
% Angle of rotation:
theta = (HeadAngle-alpha)/2;
% Rotation
ct = repmat(cosd(theta),size(H.VertexData,1),1,1);
st = repmat(sind(theta),size(H.VertexData,1),1,1);
Head_dir_a_r = Head_dir_a .* ct + ...
cross(k,Head_dir_a,1) .* st + ...
k .* repmat(dot(k,Head_dir_a,1),size(H.VertexData,1),1,1) .* (1-ct);
ct = repmat(cosd(-theta),size(H.VertexData,1),1,1);
st = repmat(sind(-theta),size(H.VertexData,1),1,1);
Head_dir_b_r = Head_dir_b .* ct + ...
cross(k,Head_dir_b,1) .* st + ...
k .* repmat(dot(k,Head_dir_b,1),size(H.VertexData,1),1,1) .* (1-ct);
Head_dir_a = Head_dir_a_r;
Head_dir_b = Head_dir_b_r;
end
%// Set new RotHead by use of Rodrigues' rotation formula
if ~isempty(RotHead)
% rotation axis:
k = Tail_dir;
% Angle of rotation:
theta = RotHead;
% Rotation
ct = repmat(cosd(theta),size(H.VertexData,1),1,1);
st = repmat(sind(theta),size(H.VertexData,1),1,1);
Head_dir_a_r = Head_dir_a .* ct + ...
cross(k,Head_dir_a,1) .* st + ...
k .* repmat(dot(k,Head_dir_a,1),size(H.VertexData,1),1,1) .* (1-ct);
Head_dir_b_r = Head_dir_b .* ct + ...
cross(k,Head_dir_b,1) .* st + ...
k .* repmat(dot(k,Head_dir_b,1),size(H.VertexData,1),1,1) .* (1-ct);
Head_dir_a = Head_dir_a_r;
Head_dir_b = Head_dir_b_r;
end
%// New Tail End = start of tail + magnitude*normalized direction
TailVertex = Tail_ori;
TailVertex(:,2,:) = TailVertex(:,1,:) + ...
permute(mags(:,:,ones(size(T.VertexData,1),1)),[3 2 1]) .* Tail_dir;
%// Middle point of Head is same as end of Tail
HeadVertex = Head_ori;
HeadVertex(:,2,:) = TailVertex(:,2,:);
%// Head vector a
HeadVertex(:,1,:) = HeadVertex(:,2,:) + Head_mag .* Head_dir_a;
%// Head vector b
HeadVertex(:,3,:) = HeadVertex(:,2,:) + Head_mag .* Head_dir_b;
%// If the vector is originally of size 0, the vectors are now NaN.
%// Change those back to have just a simple "single point"
Idx_NaN = find(isnan(squeeze(TailVertex(1,end,:))));
TailVertex(:,end,Idx_NaN) = TailVertex(:,1,Idx_NaN);
HeadVertex(:,:,Idx_NaN) = repmat(TailVertex(:,1,Idx_NaN),1,3,1);
%// Reshape the data
HeadVertex = reshape(HeadVertex,size(H.VertexData,1),[]);
TailVertex = reshape(TailVertex,size(T.VertexData,1),[]);
%// Don't ask me about this line ...
%// Matlab sucks and does not do the change graphically if I don't have a pause or a keyboard ...
pause(0.0102)
%// Set the data to the quiver properties
set(q.Head,'VertexData',HeadVertex);
set(q.Tail,'VertexData',TailVertex);
Second template to retrieve Billy's paper 2 simulation data
rev. 2024/04/29
% Second template to retrieve Billy's paper 2 simulation data
% rev. 2024/04/29
% 2024/04/29 retrive 3D data around the top of the pillar
% SUMMARY
% This MATLAB script is a comprehensive framework for analyzing fluid dynamics simulations, specifically focusing on the distribution and movement of particles or beads in a fluid environment. Key functionalities include:
%
% 1. Environment Setup:
% Initializes the simulation environment by clearing variables, setting up output folders, and defining file paths to simulation data, with adjustments for periodic boundary conditions (PBC).
% 2. Data Retrieval:
% Loads simulation data from specified file paths, handling different configurations and viscosity models.
%% Definitions (it is a script accepting ONE variable and FLAGS)
%
% List of of variables
% tframe <-- use this variable a in for-loop or choose a particular frame before calling this script
% tframelist (not used)
%
% List of available FLAGS
% RESETPREFETCH forces all prefetch files to be regenerated in true (default=false)
% PLOTON enables to plot results (default=true)
% PRINTON prints figures as PNG images for control (in outputfolder) (default=false)
% SAVEON saves the results R0 (original), R1 (informed) (default=true)
% OVERWRITE enables already saved results to overwritte (default=false)
% close, delete everything except variables and FLAGS
clc
close all
clearvars -except tframe tframelist RESETPREFETCH PLOTON PRINTON SAVEON OVERWRITE
t0_ = clock;
% check folders
outputfolder = fullfile(pwd,'preproduction');
savefolder = fullfile(pwd,'results');
prefetchfolder = fullfile(pwd,'prefetch');
if ~exist(outputfolder,'dir'), mkdir(outputfolder); end
if ~exist(prefetchfolder,'dir'), mkdir(prefetchfolder); end
if ~exist(savefolder,'dir'), mkdir(savefolder); end
% Assign default values if needed
if ~exist('RESETPREFETCH','var'), RESETPREFETCH = false; end
if ~exist('PLOTON','var'), PLOTON = true; end
if ~exist('PRINTON','var'), PRINTON = false; end
if ~exist('SAVEON','var'), SAVEON = true; end
if ~exist('OVERWRITE','var'), OVERWRITE = false; end
% Anonymous functions
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
fighandle = @(id) formatfig(figure,'figname',sprintf('t%0.3g_%s',tframe,id));
printhandle = @(hfig) print_png(300,fullfile(outputfolder,[get(hfig,'filename') '.png']),'','',0,0,0);
% results saved in two variables to avoid any confusion
R0 = struct([]); % reference data
R1 = struct([]); % informed ones
%% path and metadata
dispsection('INITIALIZATION')
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
dispf('config: %s | viscosity: %s | source: %s',config,viscosity,dumpfile)
%% extract information
dispsection('OVERVIEW')
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
% Billy choose a reference density of 900 kg/m3 for a physical density of 1000 kg/m3
% Viscosity: 0.13 Pa.s
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
dispf('SUMMARY: natoms: %d | dt: %0.3g s | rho: %0.4g',natoms,dt,rho)
%% selective copy of PREFETCH files (if possible)
if copymode
dispsection('COPY')
myprefetchfile = @(itime) sprintf('%s%09d.mat','TIMESTEP_',itime);
myprefetchfolder = @(d) fullfile(d,sprintf('PREFETCH_%s',lastdir(dumpfile)));
destinationfolder = fullfile(rootlocal,rootdir(simfolder.(config).(viscosity)));
sourcefolderPREFETCH = myprefetchfolder(sourcefolder);
destinationfolderPREFETCH = myprefetchfolder(destinationfolder);
% Choose the frames needed here
tcopy = 0.0:0.01:1.2;
% files to copy
tfile = arrayfun(@(t) myprefetchfile(t), timesteps(unique(nearestpoint(tcopy,times))),'UniformOutput',false);
oksource = all(cellfun(@(f) exist(fullfile(sourcefolderPREFETCH,f),'file'),tfile));
if ~oksource, error('the source folder is corrupted, please check'), end
existingfiles = cellfun(@(f) exist(fullfile(destinationfolderPREFETCH,f),'file'),tfile);
dispf('Number of files to copy: %d (%d already available)',length(find(~existingfiles)),length(find(existingfiles)))
copysuccess = cellfun(@(f) copyfile(fullfile(sourcefolderPREFETCH,f),fullfile(destinationfolderPREFETCH,f)),tfile(~existingfiles));
dispf('%d of %d files have been copied',length(find(copysuccess)),length(copysuccess));
end
%% Estimate bead size from the first frame
% first estimate assuming that the bead is a cube
dispsection('BEAD SIZE')
fluidxyz0 = X0.ATOMS{T==fluidtype,coords};
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
if isprefetch('verletList')
load(prefetchvar('verletList'))
else
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz0,cutoff);
save(prefetchvar('verletList'),'verletList','cutoff','dmin','config','dist')
end
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
dispf('SUMMARY: s: %0.4g m | h: %0.4g m',s,h)
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.0:0.01:1.2; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
fluidxyz = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords};
fluidid = X0.ATOMS{Xframe.ATOMS.isfluid,'id'};
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,coords};
pillarid = X0.ATOMS{Xframe.ATOMS.ispillar,'id'};
spherexyz = Xframe.ATOMS{Xframe.ATOMS.issphere,coords};
sphereid = X0.ATOMS{Xframe.ATOMS.issphere,'id'};
solidxyz = Xframe.ATOMS{Xframe.ATOMS.issolid,coords};
solidid = X0.ATOMS{Xframe.ATOMS.issolid,'id'};
ztop = max(pillarxyz(:,3)); % pillar top
% Definitions based on actual tframe
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
savefile = @() fullfile(savefolder,sprintf('Rt.%0.4f.mat',tframe));
%% Interpolate velocity field at z = ztop
dispsection('REFERENCE VELOCITY FIELD')
% full box (note that atoms may be outside of this box)
box = Xframe.BOX(icoords,:); % note that the order is given by coords, here {'z'} {'x'} {'y'}
boxsize = diff(box,1,2);
% fluidbox (box for atoms to consider)
xmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
xmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{1}});
ymin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
ymax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{2}});
zmin = min(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
zmax = max(Xframe.ATOMS{Xframe.ATOMS.isfluid,coords{3}});
fluidbox = [xmin xmax;ymin ymax;zmin zmax];
% restrict interpolation to the viewbox
viewbox = viewbox3d; %fluidbox; viewbox(3,:) = [ztop-2*h ztop+2*h];
viewboxsize = diff(viewbox,1,2);
insideviewbox = true(height(Xframe.ATOMS),1);
for icoord = 1:3
insideviewbox = insideviewbox ...
& Xframe.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xframe.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
XYZall = Xframe.ATOMS{Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZall = Xframe.ATOMS{Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,coords}; % fluid kernel centers
vXYZ = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.isfluid,vcoords}; % velocity of fluid kernel centers
XYZp = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.ispillar,coords}; % solid kernel centers
XYZs = Xframe.ATOMS{insideviewbox & Xframe.ATOMS.issphere,coords};
rhobeadXYZall = Xframe.ATOMS.c_rho_smd(Xframe.ATOMS.isfluid); % volume of the bead
rhobeadXYZ = Xframe.ATOMS.c_rho_smd(insideviewbox & Xframe.ATOMS.isfluid); % volume of the bead
VbeadXYZall = mbead./rhobeadXYZall;
VbeadXYZ = mbead./rhobeadXYZ;
% Plot
if PLOTON
figure, hold on
plot3D(XYZ,'bo')
plot3D(XYZs,'ro')
plot3D(XYZp,'go')
view(3)
end
Add bead along streamlines (considering quasi steady state)
rev. 2024/03/12
2024/03/12
% Add bead along streamlines (considering quasi steady state)
% rev. 2024/03/12
% 2024/03/12
%%
function [x,y,Vl,l] = add_bead(s,rbead,dt)
nmax = ceil(s.l(end)/(2*rbead)); % maximum number of bead along the streamline
[x,y,l,Vl] = deal(nan(nmax,1)); % creat container
l(1) = rbead; % put first bead
cn = 1; % current number of bead
while l(1)<(s.l(end)-rbead)
x = interp1(s.l,s.x,l);
y = interp1(s.l,s.y,l);
Vl = interp1(s.l,s.Vl,l);
l = l + Vl*dt; % update the positions of beads
if l(cn)>3*rbead
l(cn+1) = rbead;
cn = cn+1;
end
end
x = interp1(s.l,s.x,l);
y = interp1(s.l,s.y,l);
Vl = interp1(s.l,s.Vl,l);
end
ARROW Draw a line with an arrowhead.
ARROW(Start,Stop) draws a line with an arrow from Start to Stop (points
should be vectors of length 2 or 3, or matrices with 2 or 3
columns), and returns the graphics handle of the arrow(s).
ARROW uses the mouse (click-drag) to create an arrow.
ARROW DEMO & ARROW DEMO2 show 3-D & 2-D demos of the capabilities of ARROW.
ARROW may be called with a normal argument list or a property-based list.
ARROW(Start,Stop,Length,BaseAngle,TipAngle,Width,Page,CrossDir) is
the full normal argument list, where all but the Start and Stop
points are optional. If you need to specify a later argument (e.g.,
Page) but want default values of earlier ones (e.g., TipAngle),
pass an empty matrix for the earlier ones (e.g., TipAngle=[]).
ARROW('Property1',PropVal1,'Property2',PropVal2,...) creates arrows with the
given properties, using default values for any unspecified or given as
'default' or NaN. Some properties used for line and patch objects are
used in a modified fashion, others are passed directly to LINE, PATCH,
or SET. For a detailed properties explanation, call ARROW PROPERTIES.
Start The starting points. B
Stop The end points. /|\ ^
Length Length of the arrowhead in pixels. /|||\ |
BaseAngle Base angle in degrees (ADE). //|||\\ L|
TipAngle Tip angle in degrees (ABC). ///|||\\\ e|
Width Width of the base in pixels. ////|||\\\\ n|
Page Use hardcopy proportions. /////|D|\\\\\ g|
CrossDir Vector || to arrowhead plane. //// ||| \\\\ t|
NormalDir Vector out of arrowhead plane. /// ||| \\\ h|
Ends Which end has an arrowhead. //<----->|| \\ |
ObjectHandles Vector of handles to update. / base ||| \ V
E angle||<-------->C
ARROW(H,'Prop1',PropVal1,...), where H is a |||tipangle
vector of handles to previously-created arrows |||
and/or line objects, will update the previously- |||
created arrows according to the current view -->|A|<-- width
and any specified properties, and will convert
two-point line objects to corresponding arrows. ARROW(H) will update
the arrows if the current view has changed. Root, figure, or axes
handles included in H are replaced by all descendant Arrow objects.
A property list can follow any specified normal argument list, e.g.,
ARROW([1 2 3],[0 0 0],36,'BaseAngle',60) creates an arrow from (1,2,3) to
the origin, with an arrowhead of length 36 pixels and 60-degree base angle.
Normally, an ARROW is a PATCH object, so any valid PATCH property/value pairs
can be passed, e.g., ARROW(Start,Stop,'EdgeColor','r','FaceColor','g').
ARROW will use LINE objects when requested by ARROW(...,'Type','line') or,
using LINE property/value pairs, ARROW(Start,Stop,'Type','line','Color','b').
The basic arguments or properties can generally be vectorized to create
multiple arrows with the same call. This is done by passing a property
with one row per arrow, or, if all arrows are to have the same property
value, just one row may be specified.
You may want to execute AXIS(AXIS) before calling ARROW so it doesn't change
the axes on you; ARROW determines the sizes of arrow components BEFORE the
arrow is plotted, so if ARROW changes axis limits, arrows may be malformed.
This version of ARROW uses features of MATLAB 6.x and is incompatible with
earlier MATLAB versions (ARROW for MATLAB 4.2c is available separately);
some problems with perspective plots still exist.
function [h,yy,zz] = arrow(varargin)
% ARROW Draw a line with an arrowhead.
%
% ARROW(Start,Stop) draws a line with an arrow from Start to Stop (points
% should be vectors of length 2 or 3, or matrices with 2 or 3
% columns), and returns the graphics handle of the arrow(s).
%
% ARROW uses the mouse (click-drag) to create an arrow.
%
% ARROW DEMO & ARROW DEMO2 show 3-D & 2-D demos of the capabilities of ARROW.
%
% ARROW may be called with a normal argument list or a property-based list.
% ARROW(Start,Stop,Length,BaseAngle,TipAngle,Width,Page,CrossDir) is
% the full normal argument list, where all but the Start and Stop
% points are optional. If you need to specify a later argument (e.g.,
% Page) but want default values of earlier ones (e.g., TipAngle),
% pass an empty matrix for the earlier ones (e.g., TipAngle=[]).
%
% ARROW('Property1',PropVal1,'Property2',PropVal2,...) creates arrows with the
% given properties, using default values for any unspecified or given as
% 'default' or NaN. Some properties used for line and patch objects are
% used in a modified fashion, others are passed directly to LINE, PATCH,
% or SET. For a detailed properties explanation, call ARROW PROPERTIES.
%
% Start The starting points. B
% Stop The end points. /|\ ^
% Length Length of the arrowhead in pixels. /|||\ |
% BaseAngle Base angle in degrees (ADE). //|||\\ L|
% TipAngle Tip angle in degrees (ABC). ///|||\\\ e|
% Width Width of the base in pixels. ////|||\\\\ n|
% Page Use hardcopy proportions. /////|D|\\\\\ g|
% CrossDir Vector || to arrowhead plane. //// ||| \\\\ t|
% NormalDir Vector out of arrowhead plane. /// ||| \\\ h|
% Ends Which end has an arrowhead. //<----->|| \\ |
% ObjectHandles Vector of handles to update. / base ||| \ V
% E angle||<-------->C
% ARROW(H,'Prop1',PropVal1,...), where H is a |||tipangle
% vector of handles to previously-created arrows |||
% and/or line objects, will update the previously- |||
% created arrows according to the current view -->|A|<-- width
% and any specified properties, and will convert
% two-point line objects to corresponding arrows. ARROW(H) will update
% the arrows if the current view has changed. Root, figure, or axes
% handles included in H are replaced by all descendant Arrow objects.
%
% A property list can follow any specified normal argument list, e.g.,
% ARROW([1 2 3],[0 0 0],36,'BaseAngle',60) creates an arrow from (1,2,3) to
% the origin, with an arrowhead of length 36 pixels and 60-degree base angle.
%
% Normally, an ARROW is a PATCH object, so any valid PATCH property/value pairs
% can be passed, e.g., ARROW(Start,Stop,'EdgeColor','r','FaceColor','g').
% ARROW will use LINE objects when requested by ARROW(...,'Type','line') or,
% using LINE property/value pairs, ARROW(Start,Stop,'Type','line','Color','b').
%
% The basic arguments or properties can generally be vectorized to create
% multiple arrows with the same call. This is done by passing a property
% with one row per arrow, or, if all arrows are to have the same property
% value, just one row may be specified.
%
% You may want to execute AXIS(AXIS) before calling ARROW so it doesn't change
% the axes on you; ARROW determines the sizes of arrow components BEFORE the
% arrow is plotted, so if ARROW changes axis limits, arrows may be malformed.
%
% This version of ARROW uses features of MATLAB 6.x and is incompatible with
% earlier MATLAB versions (ARROW for MATLAB 4.2c is available separately);
% some problems with perspective plots still exist.
% Copyright (c)1995-2016, Dr. Erik A. Johnson , 5/25/2016
% http://www.usc.edu/civil_eng/johnsone/
% Revision history:
% 5/25/16 EAJ Add documentation of 'Type','line'
% Add documentation of how to set color
% Add 'Color' property (which sets both 'EdgeColor' and 'FaceColor' for patch objects)
% 5/24/16 EAJ Remove 'EraseMode' in HG2
% 7/16/14 EAJ R2014b HandleGraphics2 compatibility
% 7/14/14 EAJ 5/20/13 patch extension didn't work right in HG2
% so break the arrow along its length instead
% 5/20/13 EAJ Extend patch line one more segment so EPS/PDF printed versions
% have nice rounded tips when the LineWidth is wider
% 2/06/13 EAJ Add ShortenLength property to shorten length if arrow is short
% 1/24/13 EAJ Remove some old comments.
% 5/20/09 EAJ Fix view direction in (3D) demo.
% 6/26/08 EAJ Replace eval('trycmd','catchcmd') with try, trycmd; catch,
% catchcmd; end; -- break's MATLAB 5 compatibility.
% 8/26/03 EAJ Eliminate OpenGL attempted fix since it didn't fix anyway.
% 11/15/02 EAJ Accomodate how MATLAB 6.5 handles NaN and logicals
% 7/28/02 EAJ Tried (but failed) work-around for MATLAB 6.x / OpenGL bug
% if zero 'Width' or not double-ended
% 11/10/99 EAJ Add logical() to eliminate zero index problem in MATLAB 5.3.
% 11/10/99 EAJ Corrected warning if axis limits changed on multiple axes.
% 11/10/99 EAJ Update e-mail address.
% 2/10/99 EAJ Some documentation updating.
% 2/24/98 EAJ Fixed bug if Start~=Stop but both colinear with viewpoint.
% 8/14/97 EAJ Added workaround for MATLAB 5.1 scalar logical transpose bug.
% 7/21/97 EAJ Fixed a few misc bugs.
% 7/14/97 EAJ Make arrow([],'Prop',...) do nothing (no old handles)
% 6/23/97 EAJ MATLAB 5 compatible version, release.
% 5/27/97 EAJ Added Line Arrows back in. Corrected a few bugs.
% 5/26/97 EAJ Changed missing Start/Stop to mouse-selected arrows.
% 5/19/97 EAJ MATLAB 5 compatible version, beta.
% 4/13/97 EAJ MATLAB 5 compatible version, alpha.
% 1/31/97 EAJ Fixed bug with multiple arrows and unspecified Z coords.
% 12/05/96 EAJ Fixed one more bug with log plots and NormalDir specified
% 10/24/96 EAJ Fixed bug with log plots and NormalDir specified
% 11/13/95 EAJ Corrected handling for 'reverse' axis directions
% 10/06/95 EAJ Corrected occasional conflict with SUBPLOT
% 4/24/95 EAJ A major rewrite.
% Fall 94 EAJ Original code.
% Things to be done:
% - in the arrow_clicks section, prompt by printing to the screen so that
% the user knows what's going on; also make sure the figure is brought
% to the front.
% - segment parsing, computing, and plotting into separate subfunctions
% - change computing from Xform to Camera paradigms
% + this will help especially with 3-D perspective plots
% + if the WarpToFill section works right, remove warning code
% + when perpsective works properly, remove perspective warning code
% - add cell property values and struct property name/values (like get/set)
% - get rid of NaN as the "default" data label
% + perhaps change userdata to a struct and don't include (or leave
% empty) the values specified as default; or use a cell containing
% an empty matrix for a default value
% - add functionality of GET to retrieve current values of ARROW properties
%
% New list of things to be done:
% - rewrite as a graphics or class object that updates itself in real time
% (but have a 'Static' or 'DoNotUpdate' property to avoid updating)
% Permission is granted to distribute ARROW with the toolboxes for the book
% "Solving Solid Mechanics Problems with MATLAB 5", by F. Golnaraghi et al.
% (Prentice Hall, 1999).
% Permission is granted to Dr. Josef Bigun to distribute ARROW with his
% software to reproduce the figures in his image analysis text.
% global variable initialization
persistent ARROW_PERSP_WARN ARROW_STRETCH_WARN ARROW_AXLIMITS ARROW_AX
if isempty(ARROW_PERSP_WARN ), ARROW_PERSP_WARN =1; end;
if isempty(ARROW_STRETCH_WARN), ARROW_STRETCH_WARN=1; end;
% Handle callbacks
if (nargin>0 & isstr(varargin{1}) & strcmp(lower(varargin{1}),'callback')),
arrow_callback(varargin{2:end}); return;
end;
% Are we doing the demo?
c = sprintf('\n');
if (nargin==1 & isstr(varargin{1})),
arg1 = lower(varargin{1});
if strncmp(arg1,'prop',4), arrow_props;
elseif strncmp(arg1,'demo',4)
clf reset
demo_info = arrow_demo;
if ~strncmp(arg1,'demo2',5),
hh=arrow_demo3(demo_info);
else,
hh=arrow_demo2(demo_info);
end;
if (nargout>=1), h=hh; end;
elseif strncmp(arg1,'fixlimits',3),
arrow_fixlimits(ARROW_AX,ARROW_AXLIMITS);
ARROW_AXLIMITS=[]; ARROW_AX=[];
elseif strncmp(arg1,'help',4),
disp(help(mfilename));
else,
error([upper(mfilename) ' got an unknown single-argument string ''' deblank(arg1) '''.']);
end;
return;
end;
% Check # of arguments
if (nargout>3), error([upper(mfilename) ' produces at most 3 output arguments.']); end;
% find first property number
firstprop = nargin+1;
for k=1:length(varargin), if ~isnumeric(varargin{k}) && ~all(ishandle(varargin{k})), firstprop=k; break; end; end; %eaj 5/24/16 for k=1:length(varargin), if ~isnumeric(varargin{k}), firstprop=k; break; end; end;
lastnumeric = firstprop-1;
% check property list
if (firstprop<=nargin),
for k=firstprop:2:nargin,
curarg = varargin{k};
if ~isstr(curarg) | sum(size(curarg)>1)>1,
error([upper(mfilename) ' requires that a property name be a single string.']);
end;
end;
if (rem(nargin-firstprop,2)~=1),
error([upper(mfilename) ' requires that the property ''' ...
varargin{nargin} ''' be paired with a property value.']);
end;
end;
% default output
if (nargout>0), h=[]; end;
if (nargout>1), yy=[]; end;
if (nargout>2), zz=[]; end;
% set values to empty matrices
start = [];
stop = [];
len = [];
baseangle = [];
tipangle = [];
wid = [];
page = [];
crossdir = [];
ends = [];
shorten = [];
ax = [];
oldh = [];
ispatch = [];
defstart = [NaN NaN NaN];
defstop = [NaN NaN NaN];
deflen = 16;
defbaseangle = 90;
deftipangle = 16;
defwid = 0;
defpage = 0;
defcrossdir = [NaN NaN NaN];
defends = 1;
defshorten = 0;
defoldh = [];
defispatch = 1;
% The 'Tag' we'll put on our arrows
ArrowTag = 'Arrow';
% check for oldstyle arguments
if (firstprop==2),
% assume arg1 is a set of handles
oldh = varargin{1}(:);
if isempty(oldh), return; end;
elseif (firstprop>9),
error([upper(mfilename) ' takes at most 8 non-property arguments.']);
elseif (firstprop>2),
{start,stop,len,baseangle,tipangle,wid,page,crossdir};
args = [varargin(1:firstprop-1) cell(1,length(ans)-(firstprop-1))];
[start,stop,len,baseangle,tipangle,wid,page,crossdir] = deal(args{:});
end;
% parse property pairs
extraprops={};
for k=firstprop:2:nargin,
prop = varargin{k};
val = varargin{k+1};
prop = [lower(prop(:)') ' '];
if strncmp(prop,'start' ,5), start = val;
elseif strncmp(prop,'stop' ,4), stop = val;
elseif strncmp(prop,'len' ,3), len = val(:);
elseif strncmp(prop,'base' ,4), baseangle = val(:);
elseif strncmp(prop,'tip' ,3), tipangle = val(:);
elseif strncmp(prop,'wid' ,3), wid = val(:);
elseif strncmp(prop,'page' ,4), page = val;
elseif strncmp(prop,'cross' ,5), crossdir = val;
elseif strncmp(prop,'norm' ,4), if (isstr(val)), crossdir=val; else, crossdir=val*sqrt(-1); end;
elseif strncmp(prop,'end' ,3), ends = val;
elseif strncmp(prop,'shorten',5), shorten = val;
elseif strncmp(prop,'object' ,6), oldh = val(:);
elseif strncmp(prop,'handle' ,6), oldh = val(:);
elseif strncmp(prop,'type' ,4), ispatch = val;
elseif strncmp(prop,'userd' ,5), %ignore it
else,
% make sure it is a valid patch or line property
try
get(0,['DefaultPatch' varargin{k}]);
catch
errstr = lasterr;
try
get(0,['DefaultLine' varargin{k}]);
catch
errstr(1:max(find(errstr==char(13)|errstr==char(10)))) = '';
error([upper(mfilename) ' got ' errstr]);
end
end;
extraprops={extraprops{:},varargin{k},val};
end;
end;
% Check if we got 'default' values
start = arrow_defcheck(start ,defstart ,'Start' );
stop = arrow_defcheck(stop ,defstop ,'Stop' );
len = arrow_defcheck(len ,deflen ,'Length' );
baseangle = arrow_defcheck(baseangle,defbaseangle,'BaseAngle' );
tipangle = arrow_defcheck(tipangle ,deftipangle ,'TipAngle' );
wid = arrow_defcheck(wid ,defwid ,'Width' );
crossdir = arrow_defcheck(crossdir ,defcrossdir ,'CrossDir' );
page = arrow_defcheck(page ,defpage ,'Page' );
ends = arrow_defcheck(ends ,defends ,'' );
shorten = arrow_defcheck(shorten ,defshorten ,'' );
oldh = arrow_defcheck(oldh ,[] ,'ObjectHandles');
ispatch = arrow_defcheck(ispatch ,defispatch ,'' );
% check transpose on arguments
[m,n]=size(start ); if any(m==[2 3])&(n==1|n>3), start = start'; end;
[m,n]=size(stop ); if any(m==[2 3])&(n==1|n>3), stop = stop'; end;
[m,n]=size(crossdir); if any(m==[2 3])&(n==1|n>3), crossdir = crossdir'; end;
% convert strings to numbers
if ~isempty(ends) & isstr(ends),
endsorig = ends;
[m,n] = size(ends);
col = lower([ends(:,1:min(3,n)) ones(m,max(0,3-n))*' ']);
ends = NaN*ones(m,1);
oo = ones(1,m);
ii=find(all(col'==['non']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*0; end;
ii=find(all(col'==['sto']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*1; end;
ii=find(all(col'==['sta']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*2; end;
ii=find(all(col'==['bot']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*3; end;
if any(isnan(ends)),
ii = min(find(isnan(ends)));
error([upper(mfilename) ' does not recognize ''' deblank(endsorig(ii,:)) ''' as a valid ''Ends'' value.']);
end;
else,
ends = ends(:);
end;
if ~isempty(ispatch) & isstr(ispatch),
col = lower(ispatch(:,1));
patchchar='p'; linechar='l'; defchar=' ';
mask = col~=patchchar & col~=linechar & col~=defchar;
if any(mask),
error([upper(mfilename) ' does not recognize ''' deblank(ispatch(min(find(mask)),:)) ''' as a valid ''Type'' value.']);
end;
ispatch = (col==patchchar)*1 + (col==linechar)*0 + (col==defchar)*defispatch;
else,
ispatch = ispatch(:);
end;
oldh = oldh(:);
% check object handles
if ~all(ishandle(oldh)), error([upper(mfilename) ' got invalid object handles.']); end;
% expand root, figure, and axes handles
if ~isempty(oldh),
ohtype = get(oldh,'Type');
mask = strcmp(ohtype,'root') | strcmp(ohtype,'figure') | strcmp(ohtype,'axes');
if any(mask),
oldh = num2cell(oldh);
for ii=find(mask)',
oldh(ii) = {findobj(oldh{ii},'Tag',ArrowTag)};
end;
oldh = cat(1,oldh{:});
if isempty(oldh), return; end; % no arrows to modify, so just leave
end;
end;
% largest argument length
[mstart,junk]=size(start); [mstop,junk]=size(stop); [mcrossdir,junk]=size(crossdir);
argsizes = [length(oldh) mstart mstop ...
length(len) length(baseangle) length(tipangle) ...
length(wid) length(page) mcrossdir length(ends) length(shorten)];
args=['length(ObjectHandle) '; ...
'#rows(Start) '; ...
'#rows(Stop) '; ...
'length(Length) '; ...
'length(BaseAngle) '; ...
'length(TipAngle) '; ...
'length(Width) '; ...
'length(Page) '; ...
'#rows(CrossDir) '; ...
'#rows(Ends) '; ...
'length(ShortenLength) '];
if (any(imag(crossdir(:))~=0)),
args(9,:) = '#rows(NormalDir) ';
end;
if isempty(oldh),
narrows = max(argsizes);
else,
narrows = length(oldh);
end;
if (narrows<=0), narrows=1; end;
% Check size of arguments
ii = find((argsizes~=0)&(argsizes~=1)&(argsizes~=narrows));
if ~isempty(ii),
s = args(ii',:);
while ((size(s,2)>1)&((abs(s(:,size(s,2)))==0)|(abs(s(:,size(s,2)))==abs(' ')))),
s = s(:,1:size(s,2)-1);
end;
s = [ones(length(ii),1)*[upper(mfilename) ' requires that '] s ...
ones(length(ii),1)*[' equal the # of arrows (' num2str(narrows) ').' c]];
s = s';
s = s(:)';
s = s(1:length(s)-1);
error(setstr(s));
end;
% check element length in Start, Stop, and CrossDir
if ~isempty(start),
[m,n] = size(start);
if (n==2),
start = [start NaN*ones(m,1)];
elseif (n~=3),
error([upper(mfilename) ' requires 2- or 3-element Start points.']);
end;
end;
if ~isempty(stop),
[m,n] = size(stop);
if (n==2),
stop = [stop NaN*ones(m,1)];
elseif (n~=3),
error([upper(mfilename) ' requires 2- or 3-element Stop points.']);
end;
end;
if ~isempty(crossdir),
[m,n] = size(crossdir);
if (n<3),
crossdir = [crossdir NaN*ones(m,3-n)];
elseif (n~=3),
if (all(imag(crossdir(:))==0)),
error([upper(mfilename) ' requires 2- or 3-element CrossDir vectors.']);
else,
error([upper(mfilename) ' requires 2- or 3-element NormalDir vectors.']);
end;
end;
end;
% fill empty arguments
if isempty(start ), start = [Inf Inf Inf]; end;
if isempty(stop ), stop = [Inf Inf Inf]; end;
if isempty(len ), len = Inf; end;
if isempty(baseangle ), baseangle = Inf; end;
if isempty(tipangle ), tipangle = Inf; end;
if isempty(wid ), wid = Inf; end;
if isempty(page ), page = Inf; end;
if isempty(crossdir ), crossdir = [Inf Inf Inf]; end;
if isempty(ends ), ends = Inf; end;
if isempty(shorten ), shorten = Inf; end;
if isempty(ispatch ), ispatch = Inf; end;
% expand single-column arguments
o = ones(narrows,1);
if (size(start ,1)==1), start = o * start ; end;
if (size(stop ,1)==1), stop = o * stop ; end;
if (length(len )==1), len = o * len ; end;
if (length(baseangle )==1), baseangle = o * baseangle ; end;
if (length(tipangle )==1), tipangle = o * tipangle ; end;
if (length(wid )==1), wid = o * wid ; end;
if (length(page )==1), page = o * page ; end;
if (size(crossdir ,1)==1), crossdir = o * crossdir ; end;
if (length(ends )==1), ends = o * ends ; end;
if (length(shorten )==1), shorten = o * shorten ; end;
if (length(ispatch )==1), ispatch = o * ispatch ; end;
ax = repmat(gca,narrows,1); %eaj 7/16/14 ax=gca; if ~isnumeric(ax), ax=double(ax); end; ax=o*ax;
% if we've got handles, get the defaults from the handles
if ~isempty(oldh),
for k=1:narrows,
oh = oldh(k);
ud = get(oh,'UserData');
ax(k) = get(oh,'Parent'); %eaj 7/16/14 get(oh,'Parent'); if ~isnumeric(ans), double(ans); end; ax(k)=ans;
ohtype = get(oh,'Type');
if strcmp(get(oh,'Tag'),ArrowTag), % if it's an arrow already
if isinf(ispatch(k)), ispatch(k)=strcmp(ohtype,'patch'); end;
% arrow UserData format: [start' stop' len base tip wid page crossdir' ends shorten]
start0 = ud(1:3);
stop0 = ud(4:6);
if (isinf(len(k))), len(k) = ud( 7); end;
if (isinf(baseangle(k))), baseangle(k) = ud( 8); end;
if (isinf(tipangle(k))), tipangle(k) = ud( 9); end;
if (isinf(wid(k))), wid(k) = ud(10); end;
if (isinf(page(k))), page(k) = ud(11); end;
if (isinf(crossdir(k,1))), crossdir(k,1) = ud(12); end;
if (isinf(crossdir(k,2))), crossdir(k,2) = ud(13); end;
if (isinf(crossdir(k,3))), crossdir(k,3) = ud(14); end;
if (isinf(ends(k))), ends(k) = ud(15); end;
if (isinf(shorten(k))), shorten(k) = ud(16); end;
elseif strcmp(ohtype,'line')|strcmp(ohtype,'patch'), % it's a non-arrow line or patch
convLineToPatch = 1; %set to make arrow patches when converting from lines.
if isinf(ispatch(k)), ispatch(k)=convLineToPatch|strcmp(ohtype,'patch'); end;
x=get(oh,'XData'); x=x(~isnan(x(:))); if isempty(x), x=NaN; end;
y=get(oh,'YData'); y=y(~isnan(y(:))); if isempty(y), y=NaN; end;
z=get(oh,'ZData'); z=z(~isnan(z(:))); if isempty(z), z=NaN; end;
start0 = [x(1) y(1) z(1) ];
stop0 = [x(end) y(end) z(end)];
else,
error([upper(mfilename) ' cannot convert ' ohtype ' objects.']);
end;
ii=find(isinf(start(k,:))); if ~isempty(ii), start(k,ii)=start0(ii); end;
ii=find(isinf(stop( k,:))); if ~isempty(ii), stop( k,ii)=stop0( ii); end;
end;
end;
% convert Inf's to NaN's
start( isinf(start )) = NaN;
stop( isinf(stop )) = NaN;
len( isinf(len )) = NaN;
baseangle( isinf(baseangle)) = NaN;
tipangle( isinf(tipangle )) = NaN;
wid( isinf(wid )) = NaN;
page( isinf(page )) = NaN;
crossdir( isinf(crossdir )) = NaN;
ends( isinf(ends )) = NaN;
shorten( isinf(shorten )) = NaN;
ispatch( isinf(ispatch )) = NaN;
% set up the UserData data (here so not corrupted by log10's and such)
ud = [start stop len baseangle tipangle wid page crossdir ends shorten];
% Set Page defaults
page = ~isnan(page) & trueornan(page);
% Get axes limits, range, min; correct for aspect ratio and log scale
axm = zeros(3,narrows);
axr = zeros(3,narrows);
axrev = zeros(3,narrows);
ap = zeros(2,narrows);
xyzlog = zeros(3,narrows);
limmin = zeros(2,narrows);
limrange = zeros(2,narrows);
oldaxlims = zeros(6,narrows);
oneax = all(ax==ax(1));
if (oneax),
T = zeros(4,4);
invT = zeros(4,4);
else,
T = zeros(16,narrows);
invT = zeros(16,narrows);
end;
axnotdone = true(size(ax));
while (any(axnotdone)),
ii = find(axnotdone,1);
curax = ax(ii);
curpage = page(ii);
% get axes limits and aspect ratio
axl = [get(curax,'XLim'); get(curax,'YLim'); get(curax,'ZLim')];
ax==curax; oldaxlims(:,ans)=repmat(reshape(axl',[],1),1,sum(ans));
% get axes size in pixels (points)
u = get(curax,'Units');
axposoldunits = get(curax,'Position');
really_curpage = curpage & strcmp(u,'normalized');
if (really_curpage),
curfig = get(curax,'Parent');
pu = get(curfig,'PaperUnits');
set(curfig,'PaperUnits','points');
pp = get(curfig,'PaperPosition');
set(curfig,'PaperUnits',pu);
set(curax,'Units','pixels');
curapscreen = get(curax,'Position');
set(curax,'Units','normalized');
curap = pp.*get(curax,'Position');
else,
set(curax,'Units','pixels');
curapscreen = get(curax,'Position');
curap = curapscreen;
end;
set(curax,'Units',u);
set(curax,'Position',axposoldunits);
% handle non-stretched axes position
str_stretch = { 'DataAspectRatioMode' ; ...
'PlotBoxAspectRatioMode' ; ...
'CameraViewAngleMode' };
str_camera = { 'CameraPositionMode' ; ...
'CameraTargetMode' ; ...
'CameraViewAngleMode' ; ...
'CameraUpVectorMode' };
notstretched = strcmp(get(curax,str_stretch),'manual');
manualcamera = strcmp(get(curax,str_camera),'manual');
if ~arrow_WarpToFill(notstretched,manualcamera,curax),
% give a warning that this has not been thoroughly tested
if 0 & ARROW_STRETCH_WARN,
ARROW_STRETCH_WARN = 0;
strs = {str_stretch{1:2},str_camera{:}};
strs = [char(ones(length(strs),1)*sprintf('\n ')) char(strs)]';
warning([upper(mfilename) ' may not yet work quite right ' ...
'if any of the following are ''manual'':' strs(:).']);
end;
% find the true pixel size of the actual axes
texttmp = text(axl(1,[1 2 2 1 1 2 2 1]), ...
axl(2,[1 1 2 2 1 1 2 2]), ...
axl(3,[1 1 1 1 2 2 2 2]),'');
set(texttmp,'Units','points');
textpos = get(texttmp,'Position');
delete(texttmp);
textpos = cat(1,textpos{:});
textpos = max(textpos(:,1:2)) - min(textpos(:,1:2));
% adjust the axes position
if (really_curpage),
% adjust to printed size
textpos = textpos * min(curap(3:4)./textpos);
curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos];
else,
% adjust for pixel roundoff
textpos = textpos * min(curapscreen(3:4)./textpos);
curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos];
end;
end;
if ARROW_PERSP_WARN & ~strcmp(get(curax,'Projection'),'orthographic'),
ARROW_PERSP_WARN = 0;
warning([upper(mfilename) ' does not yet work right for 3-D perspective projection.']);
end;
% adjust limits for log scale on axes
curxyzlog = strcmp(get(curax,{'XScale' 'YScale' 'ZScale'})','log');
if (any(curxyzlog)),
ii = find([curxyzlog;curxyzlog]);
if (any(axl(ii)<=0)),
error([upper(mfilename) ' does not support non-positive limits on log-scaled axes.']);
else,
axl(ii) = log10(axl(ii));
end;
end;
% correct for 'reverse' direction on axes;
curreverse = strcmp(get(curax,{'XDir' 'YDir' 'ZDir'})','reverse');
ii = find(curreverse);
if ~isempty(ii),
axl(ii,[1 2])=-axl(ii,[2 1]);
end;
% compute the range of 2-D values
try, curT=get(curax,'Xform'); catch, num2cell(get(curax,'View')); curT=viewmtx(ans{:}); end;
lim = curT*[0 1 0 1 0 1 0 1;0 0 1 1 0 0 1 1;0 0 0 0 1 1 1 1;1 1 1 1 1 1 1 1];
lim = lim(1:2,:)./([1;1]*lim(4,:));
curlimmin = min(lim')';
curlimrange = max(lim')' - curlimmin;
curinvT = inv(curT);
if (~oneax),
curT = curT.';
curinvT = curinvT.';
curT = curT(:);
curinvT = curinvT(:);
end;
% check which arrows to which cur corresponds
ii = find((ax==curax)&(page==curpage));
oo = ones(1,length(ii));
axr(:,ii) = diff(axl')' * oo;
axm(:,ii) = axl(:,1) * oo;
axrev(:,ii) = curreverse * oo;
ap(:,ii) = curap(3:4)' * oo;
xyzlog(:,ii) = curxyzlog * oo;
limmin(:,ii) = curlimmin * oo;
limrange(:,ii) = curlimrange * oo;
if (oneax),
T = curT;
invT = curinvT;
else,
T(:,ii) = curT * oo;
invT(:,ii) = curinvT * oo;
end;
axnotdone(ii) = zeros(1,length(ii));
end;
% correct for log scales
curxyzlog = xyzlog.';
ii = find(curxyzlog(:));
if ~isempty(ii),
start( ii) = real(log10(start( ii)));
stop( ii) = real(log10(stop( ii)));
if (all(imag(crossdir)==0)), % pulled (ii) subscript on crossdir, 12/5/96 eaj
crossdir(ii) = real(log10(crossdir(ii)));
end;
end;
% correct for reverse directions
ii = find(axrev.');
if ~isempty(ii),
start( ii) = -start( ii);
stop( ii) = -stop( ii);
crossdir(ii) = -crossdir(ii);
end;
% transpose start/stop values
start = start.';
stop = stop.';
% take care of defaults, page was done above
ii=find(isnan(start(:) )); if ~isempty(ii), start(ii) = axm(ii)+axr(ii)/2; end;
ii=find(isnan(stop(:) )); if ~isempty(ii), stop(ii) = axm(ii)+axr(ii)/2; end;
ii=find(isnan(crossdir(:) )); if ~isempty(ii), crossdir(ii) = zeros(length(ii),1); end;
ii=find(isnan(len )); if ~isempty(ii), len(ii) = ones(length(ii),1)*deflen; end;
ii=find(isnan(baseangle )); if ~isempty(ii), baseangle(ii) = ones(length(ii),1)*defbaseangle; end;
ii=find(isnan(tipangle )); if ~isempty(ii), tipangle(ii) = ones(length(ii),1)*deftipangle; end;
ii=find(isnan(wid )); if ~isempty(ii), wid(ii) = ones(length(ii),1)*defwid; end;
ii=find(isnan(ends )); if ~isempty(ii), ends(ii) = ones(length(ii),1)*defends; end;
ii=find(isnan(shorten )); if ~isempty(ii), shorten(ii) = ones(length(ii),1)*defshorten; end;
% transpose rest of values
len = len.';
baseangle = baseangle.';
tipangle = tipangle.';
wid = wid.';
page = page.';
crossdir = crossdir.';
ends = ends.';
shorten = shorten.';
ax = ax.';
% given x, a 3xN matrix of points in 3-space;
% want to convert to X, the corresponding 4xN 2-space matrix
%
% tmp1=[(x-axm)./axr; ones(1,size(x,1))];
% if (oneax), X=T*tmp1;
% else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1;
% tmp2=zeros(4,4*N); tmp2(:)=tmp1(:);
% X=zeros(4,N); X(:)=sum(tmp2)'; end;
% X = X ./ (ones(4,1)*X(4,:));
% for all points with start==stop, start=stop-(verysmallvalue)*(up-direction);
ii = find(all(start==stop));
if ~isempty(ii),
% find an arrowdir vertical on screen and perpendicular to viewer
% transform to 2-D
tmp1 = [(stop(:,ii)-axm(:,ii))./axr(:,ii);ones(1,length(ii))];
if (oneax), twoD=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T(:,ii).*tmp1;
tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:);
twoD=zeros(4,length(ii)); twoD(:)=sum(tmp2)'; end;
twoD=twoD./(ones(4,1)*twoD(4,:));
% move the start point down just slightly
tmp1 = twoD + [0;-1/1000;0;0]*(limrange(2,ii)./ap(2,ii));
% transform back to 3-D
if (oneax), threeD=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT(:,ii).*tmp1;
tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:);
threeD=zeros(4,length(ii)); threeD(:)=sum(tmp2)'; end;
start(:,ii) = (threeD(1:3,:)./(ones(3,1)*threeD(4,:))).*axr(:,ii)+axm(:,ii);
end;
% compute along-arrow points
% transform Start points
tmp1=[(start-axm)./axr;ones(1,narrows)];
if (oneax), X0=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
X0=zeros(4,narrows); X0(:)=sum(tmp2)'; end;
X0=X0./(ones(4,1)*X0(4,:));
% transform Stop points
tmp1=[(stop-axm)./axr;ones(1,narrows)];
if (oneax), Xf=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
Xf=zeros(4,narrows); Xf(:)=sum(tmp2)'; end;
Xf=Xf./(ones(4,1)*Xf(4,:));
% compute pixel distance between points
D = sqrt(sum(((Xf(1:2,:)-X0(1:2,:)).*(ap./limrange)).^2));
D = D + (D==0); %eaj new 2/24/98
% shorten the length if requested % added 2/6/2013
numends = (ends==1) + (ends==2) + 2*(ends==3);
mask = shorten & D% compute and modify along-arrow distances
len1 = len;
len2 = len - (len.*tan(tipangle/180*pi)-wid/2).*tan((90-baseangle)/180*pi);
slen0 = zeros(1,narrows);
slen1 = len1 .* ((ends==2)|(ends==3));
slen2 = len2 .* ((ends==2)|(ends==3));
len0 = zeros(1,narrows);
len1 = len1 .* ((ends==1)|(ends==3));
len2 = len2 .* ((ends==1)|(ends==3));
% for no start arrowhead
ii=find((ends==1)&(Dif ~isempty(ii),
slen0(ii) = D(ii)-len2(ii);
end;
% for no end arrowhead
ii=find((ends==2)&(Dif ~isempty(ii),
len0(ii) = D(ii)-slen2(ii);
end;
len1 = len1 + len0;
len2 = len2 + len0;
slen1 = slen1 + slen0;
slen2 = slen2 + slen0;
% note: the division by D below will probably not be accurate if both
% of the following are true:
% 1. the ratio of the line length to the arrowhead
% length is large
% 2. the view is highly perspective.
% compute stoppoints
tmp1=X0.*(ones(4,1)*(len0./D))+Xf.*(ones(4,1)*(1-len0./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
stoppoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute tippoints
tmp1=X0.*(ones(4,1)*(len1./D))+Xf.*(ones(4,1)*(1-len1./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
tippoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute basepoints
tmp1=X0.*(ones(4,1)*(len2./D))+Xf.*(ones(4,1)*(1-len2./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
basepoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute startpoints
tmp1=X0.*(ones(4,1)*(1-slen0./D))+Xf.*(ones(4,1)*(slen0./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
startpoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute stippoints
tmp1=X0.*(ones(4,1)*(1-slen1./D))+Xf.*(ones(4,1)*(slen1./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
stippoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute sbasepoints
tmp1=X0.*(ones(4,1)*(1-slen2./D))+Xf.*(ones(4,1)*(slen2./D));
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT.*tmp1;
tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,narrows); tmp3(:)=sum(tmp2)'; end;
sbasepoint = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)).*axr+axm;
% compute cross-arrow directions for arrows with NormalDir specified
if (any(imag(crossdir(:))~=0)),
ii = find(any(imag(crossdir)~=0));
crossdir(:,ii) = cross((stop(:,ii)-start(:,ii))./axr(:,ii), ...
imag(crossdir(:,ii))).*axr(:,ii);
end;
% compute cross-arrow directions
basecross = crossdir + basepoint;
tipcross = crossdir + tippoint;
sbasecross = crossdir + sbasepoint;
stipcross = crossdir + stippoint;
ii = find(all(crossdir==0)|any(isnan(crossdir)));
if ~isempty(ii),
numii = length(ii);
% transform start points
tmp1 = [basepoint(:,ii) tippoint(:,ii) sbasepoint(:,ii) stippoint(:,ii)];
tmp1 = (tmp1-axm(:,[ii ii ii ii])) ./ axr(:,[ii ii ii ii]);
tmp1 = [tmp1; ones(1,4*numii)];
if (oneax), X0=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T(:,[ii ii ii ii]).*tmp1;
tmp2=zeros(4,16*numii); tmp2(:)=tmp1(:);
X0=zeros(4,4*numii); X0(:)=sum(tmp2)'; end;
X0=X0./(ones(4,1)*X0(4,:));
% transform stop points
tmp1 = [(2*stop(:,ii)-start(:,ii)-axm(:,ii))./axr(:,ii);ones(1,numii)];
tmp1 = [tmp1 tmp1 tmp1 tmp1];
if (oneax), Xf=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T(:,[ii ii ii ii]).*tmp1;
tmp2=zeros(4,16*numii); tmp2(:)=tmp1(:);
Xf=zeros(4,4*numii); Xf(:)=sum(tmp2)'; end;
Xf=Xf./(ones(4,1)*Xf(4,:));
% compute perpendicular directions
pixfact = ((limrange(1,ii)./limrange(2,ii)).*(ap(2,ii)./ap(1,ii))).^2;
pixfact = [pixfact pixfact pixfact pixfact];
pixfact = [pixfact;1./pixfact];
[dummyval,jj] = max(abs(Xf(1:2,:)-X0(1:2,:)));
jj1 = ((1:4)'*ones(1,length(jj))==ones(4,1)*jj);
jj2 = ((1:4)'*ones(1,length(jj))==ones(4,1)*(3-jj));
jj3 = jj1(1:2,:);
Xf(jj1)=Xf(jj1)+(Xf(jj1)-X0(jj1)==0); %eaj new 2/24/98
Xp = X0;
Xp(jj2) = X0(jj2) + ones(sum(jj2(:)),1);
Xp(jj1) = X0(jj1) - (Xf(jj2)-X0(jj2))./(Xf(jj1)-X0(jj1)) .* pixfact(jj3);
% inverse transform the cross points
if (oneax), Xp=invT*Xp;
else, tmp1=[Xp;Xp;Xp;Xp]; tmp1=invT(:,[ii ii ii ii]).*tmp1;
tmp2=zeros(4,16*numii); tmp2(:)=tmp1(:);
Xp=zeros(4,4*numii); Xp(:)=sum(tmp2)'; end;
Xp=(Xp(1:3,:)./(ones(3,1)*Xp(4,:))).*axr(:,[ii ii ii ii])+axm(:,[ii ii ii ii]);
basecross(:,ii) = Xp(:,0*numii+(1:numii));
tipcross(:,ii) = Xp(:,1*numii+(1:numii));
sbasecross(:,ii) = Xp(:,2*numii+(1:numii));
stipcross(:,ii) = Xp(:,3*numii+(1:numii));
end;
% compute all points
% compute start points
axm11 = [axm axm axm axm axm axm axm axm axm axm axm];
axr11 = [axr axr axr axr axr axr axr axr axr axr axr];
st = [stoppoint tippoint basepoint sbasepoint stippoint startpoint stippoint sbasepoint basepoint tippoint stoppoint];
tmp1 = (st - axm11) ./ axr11;
tmp1 = [tmp1; ones(1,size(tmp1,2))];
if (oneax), X0=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=[T T T T T T T T T T T].*tmp1;
tmp2=zeros(4,44*narrows); tmp2(:)=tmp1(:);
X0=zeros(4,11*narrows); X0(:)=sum(tmp2)'; end;
X0=X0./(ones(4,1)*X0(4,:));
% compute stop points
tmp1 = ([start tipcross basecross sbasecross stipcross stop stipcross sbasecross basecross tipcross start] ...
- axm11) ./ axr11;
tmp1 = [tmp1; ones(1,size(tmp1,2))];
if (oneax), Xf=T*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=[T T T T T T T T T T T].*tmp1;
tmp2=zeros(4,44*narrows); tmp2(:)=tmp1(:);
Xf=zeros(4,11*narrows); Xf(:)=sum(tmp2)'; end;
Xf=Xf./(ones(4,1)*Xf(4,:));
% compute lengths
len0 = len.*((ends==1)|(ends==3)).*tan(tipangle/180*pi);
slen0 = len.*((ends==2)|(ends==3)).*tan(tipangle/180*pi);
le = [zeros(1,narrows) len0 wid/2 wid/2 slen0 zeros(1,narrows) -slen0 -wid/2 -wid/2 -len0 zeros(1,narrows)];
aprange = ap./limrange;
aprange = [aprange aprange aprange aprange aprange aprange aprange aprange aprange aprange aprange];
D = sqrt(sum(((Xf(1:2,:)-X0(1:2,:)).*aprange).^2));
Dii=find(D==0); if ~isempty(Dii), D=D+(D==0); le(Dii)=zeros(1,length(Dii)); end; %should fix DivideByZero warnings
tmp1 = X0.*(ones(4,1)*(1-le./D)) + Xf.*(ones(4,1)*(le./D));
% inverse transform
if (oneax), tmp3=invT*tmp1;
else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=[invT invT invT invT invT invT invT invT invT invT invT].*tmp1;
tmp2=zeros(4,44*narrows); tmp2(:)=tmp1(:);
tmp3=zeros(4,11*narrows); tmp3(:)=sum(tmp2)'; end;
pts = tmp3(1:3,:)./(ones(3,1)*tmp3(4,:)) .* axr11 + axm11;
% correct for ones where the crossdir was specified
ii = find(~(all(crossdir==0)|any(isnan(crossdir))));
if ~isempty(ii),
D1 = [pts(:,1*narrows+ii)-pts(:,9*narrows+ii) ...
pts(:,2*narrows+ii)-pts(:,8*narrows+ii) ...
pts(:,3*narrows+ii)-pts(:,7*narrows+ii) ...
pts(:,4*narrows+ii)-pts(:,6*narrows+ii) ...
pts(:,6*narrows+ii)-pts(:,4*narrows+ii) ...
pts(:,7*narrows+ii)-pts(:,3*narrows+ii) ...
pts(:,8*narrows+ii)-pts(:,2*narrows+ii) ...
pts(:,9*narrows+ii)-pts(:,1*narrows+ii)]/2;
ii = ii'*ones(1,8) + ones(length(ii),1)*[1:4 6:9]*narrows;
ii = ii(:)';
pts(:,ii) = st(:,ii) + D1;
end;
% readjust for reverse directions
iicols=(1:narrows)'; iicols=iicols(:,ones(1,11)); iicols=iicols(:).';
tmp1=axrev(:,iicols);
ii = find(tmp1(:)); if ~isempty(ii), pts(ii)=-pts(ii); end;
% change from starting/ending at the stop point to doing it at the midpoint %eaj 7/14/2014
(pts(:,2*narrows+1:3*narrows)+pts(:,3*narrows+1:4*narrows))/2; %eaj 7/14/2014
pts = [ans pts(:,[3*narrows+1:end narrows+1:3*narrows]) ans]; %eaj 7/14/2014
% readjust for log scale on axes
tmp1=xyzlog(:,iicols);
ii = find(tmp1(:)); if ~isempty(ii), pts(ii)=10.^pts(ii); end;
% compute the x,y,z coordinates of the patches;
ii = narrows*(0:size(pts,2)/narrows-1)'*ones(1,narrows) + ones(size(pts,2)/narrows,1)*(1:narrows);
ii = ii(:)';
x = zeros(size(pts,2)/narrows,narrows);
y = zeros(size(pts,2)/narrows,narrows);
z = zeros(size(pts,2)/narrows,narrows);
x(:) = pts(1,ii)';
y(:) = pts(2,ii)';
z(:) = pts(3,ii)';
% do the output
if (nargout<=1),
% % create or modify the patches
newpatch = trueornan(ispatch) & (isempty(oldh)|~strcmp(get(oldh,'Type'),'patch'));
newline = ~trueornan(ispatch) & (isempty(oldh)|~strcmp(get(oldh,'Type'),'line'));
if isempty(oldh), H=zeros(narrows,1); else, H=oldh; end;
% % make or modify the arrows
for k=1:narrows,
if all(isnan(ud(k,[3 6])))&arrow_is2DXY(ax(k)), zz=[]; else, zz=z(:,k); end;
xx=x(:,k); yy=y(:,k);
if (0), % this fix didn't work, so let's not use it -- 8/26/03
% try to work around a MATLAB 6.x OpenGL bug -- 7/28/02
mask=any([ones(1,2+size(zz,2));diff([xx yy zz],[],1)],2);
xx=xx(mask); yy=yy(mask); if ~isempty(zz), zz=zz(mask); end;
end;
% plot the patch or line
if newpatch(k) || trueornan(ispatch(k)) %eaj 7/14/2014, 5/25/2016
% patch is closed so don't need endpoints %eaj 7/14/2014
if ~isempty(xx), xx(end)=[]; end; %eaj 7/14/2014
if ~isempty(yy), yy(end)=[]; end; %eaj 7/14/2014
if ~isempty(zz), zz(end)=[]; end; %eaj 7/14/2014
end %eaj 7/14/2014
xyz = {'XData',xx,'YData',yy,'ZData',zz,'Tag',ArrowTag};
if newpatch(k)|newline(k),
if newpatch(k),
H(k) = patch(xyz{:});
else,
H(k) = line(xyz{:});
end;
if ~isempty(oldh), arrow_copyprops(oldh(k),H(k)); end;
else,
if strcmp(get(H(k),'Type'),'patch') %eaj 5/25/16 if ispatch(k)
xyz = {xyz{:},'CData',[]};
end;
set(H(k),xyz{:});
end;
end;
if ~isempty(oldh), delete(oldh(oldh~=H)); end;
% % additional properties
set(H,'Clipping','off');
set(H,{'UserData'},num2cell(ud,2));
if length(extraprops)>0
ii = find(strcmpi(extraprops(1:2:end),'color')); %eaj 5/25/16
ispatch = strcmp(get(H,'Type'),'patch');
%eaj start 5/25/16
while ~isempty(ii) && any(ispatch)
if ii>1, set(H,extraprops{1:2*ii-2}); end;
c = extraprops{2*ii};
extraprops(1:2*ii) = [];
ii(1) = [];
if all(ispatch) || ischar(c)&&size(c,1)==1 || isnumeric(c)&&isequal(size(c),[1 3])
set(H,'EdgeColor',c,'FaceColor',c)
elseif iscell(c) && numel(c)~=numel(H)
set(H(ispatch),'EdgeColor',c(ispatch),'FaceColor',c(ispatch));
set(H(~ispatch),'Color',c(~ispatch));
elseif isnumeric(c) && isequal(size(c),[numel(H) 3])
set(H(ispatch),'EdgeColor',num2cell(c(ispatch,:),2),'FaceColor',num2cell(c(ispatch,:),2));
set(H(~ispatch),'Color',num2cell(c(~ispatch,:),2));
else
warning('ignoring unknown or invalid ''Color'' specification');
end
end
if ~isempty(extraprops)
%eaj end 5/25/16
set(H,extraprops{:});
end %eaj 5/25/16
end
% handle choosing arrow Start and/or Stop locations if unspecified
[H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims);
if ~isempty(errstr), error([upper(mfilename) ' got ' errstr]); end;
% set the output
if (nargout>0), h=H; end;
% make sure the axis limits did not change
if isempty(oldaxlims),
ARROW_AXLIMITS = [];
ARROW_AX = [];
else,
lims = get(ax(:),{'XLim','YLim','ZLim'})';
lims = reshape(cat(2,lims{:}),6,size(lims,2));
mask = arrow_is2DXY(ax(:));
oldaxlims(5:6,mask) = lims(5:6,mask);
% store them for possible restoring
mask = any(oldaxlims~=lims,1); ARROW_AX=ax(mask); ARROW_AXLIMITS=oldaxlims(:,mask);
if any(mask),
warning(arrow_warnlimits(ARROW_AX,narrows));
end;
end;
else,
% don't create the patch, just return the data
h=x;
yy=y;
zz=z;
end;
function out = arrow_defcheck(in,def,prop)
% check if we got 'default' values
out = in;
if ~isstr(in), return; end;
if size(in,1)==1 & strncmp(lower(in),'def',3),
out = def;
elseif ~isempty(prop),
error([upper(mfilename) ' does not recognize ''' in(:)' ''' as a valid ''' prop ''' string.']);
end;
function [H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims)
% handle choosing arrow Start and/or Stop locations if necessary
errstr = '';
if isempty(H)|isempty(ud)|isempty(x), return; end;
% determine which (if any) need Start and/or Stop
needStart = all(isnan(ud(:,1:3)'))';
needStop = all(isnan(ud(:,4:6)'))';
mask = any(needStart|needStop);
if ~any(mask), return; end;
ud(~mask,:)=[]; ax(:,~mask)=[];
x(:,~mask)=[]; y(:,~mask)=[]; z(:,~mask)=[];
% make them invisible for the time being
set(H,'Visible','off');
% save the current axes and limits modes; set to manual for the time being
oldAx = gca;
limModes=get(ax(:),{'XLimMode','YLimMode','ZLimMode'});
set(ax(:),{'XLimMode','YLimMode','ZLimMode'},{'manual','manual','manual'});
% loop over each arrow that requires attention
jj = find(mask);
for ii=1:length(jj),
h = H(jj(ii));
axes(ax(ii));
% figure out correct call
if needStart(ii), prop='Start'; else, prop='Stop'; end;
[wasInterrupted,errstr] = arrow_click(needStart(ii)&needStop(ii),h,prop,ax(ii));
% handle errors and control-C
if wasInterrupted,
delete(H(jj(ii:end)));
H(jj(ii:end))=[];
oldaxlims(jj(ii:end),:)=[];
break;
end;
end;
% restore the axes and limit modes
axes(oldAx);
set(ax(:),{'XLimMode','YLimMode','ZLimMode'},limModes);
function [wasInterrupted,errstr] = arrow_click(lockStart,H,prop,ax)
% handle the clicks for one arrow
fig = get(ax,'Parent');
% save some things
oldFigProps = {'Pointer','WindowButtonMotionFcn','WindowButtonUpFcn'};
oldFigValue = get(fig,oldFigProps);
oldArrowProps = {'EraseMode'};
if ~isnumeric(fig), oldArrowProps={}; end %eaj 5/24/16 % only use in HG2
oldArrowValue = get(H,oldArrowProps);
if isnumeric(fig), %eaj 5/24/16
set(H,'EraseMode','background'); %because 'xor' makes shaft invisible unless Width>1 -- only use in HG2
end %eaj 5/24/16
global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z
ARROW_CLICK_H=H; ARROW_CLICK_PROP=prop; ARROW_CLICK_AX=ax;
ARROW_CLICK_USE_Z=~arrow_is2DXY(ax)|~arrow_planarkids(ax);
set(fig,'Pointer','crosshair');
% set up the WindowButtonMotion so we can see the arrow while moving around
set(fig,'WindowButtonUpFcn','set(gcf,''WindowButtonUpFcn'','''')', ...
'WindowButtonMotionFcn','');
if ~lockStart,
set(H,'Visible','on');
set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']);
end;
% wait for the button to be pressed
[wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig);
% if we wanted to click-drag, set the Start point
if lockStart & ~wasInterrupted,
pt = arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z);
feval(mfilename,H,'Start',pt,'Stop',pt);
set(H,'Visible','on');
ARROW_CLICK_PROP='Stop';
set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']);
% wait for the mouse button to be released
try
waitfor(fig,'WindowButtonUpFcn','');
catch
errstr = lasterr;
wasInterrupted = 1;
end;
end;
if ~wasInterrupted, feval(mfilename,'callback','motion'); end;
% restore some things
set(gcf,oldFigProps,oldFigValue);
set(H,oldArrowProps,oldArrowValue);
function arrow_callback(varargin)
% handle redrawing callbacks
if nargin==0, return; end;
str = varargin{1};
if ~isstr(str), error([upper(mfilename) ' got an invalid Callback command.']); end;
s = lower(str);
if strcmp(s,'motion'),
% motion callback
global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z
feval(mfilename,ARROW_CLICK_H,ARROW_CLICK_PROP,arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z));
drawnow;
else,
error([upper(mfilename) ' does not recognize ''' str(:).' ''' as a valid Callback option.']);
end;
function out = arrow_point(ax,use_z)
% return the point on the given axes
if nargin==0, ax=gca; end;
if nargin<2, use_z=~arrow_is2DXY(ax)|~arrow_planarkids(ax); end;
out = get(ax,'CurrentPoint');
out = out(1,:);
if ~use_z, out=out(1:2); end;
function [wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig)
% wait for button down ignoring object ButtonDownFcn's
if nargin==0, fig=gcf; end;
errstr = '';
% save ButtonDownFcn values
objs = findobj(fig);
buttonDownFcns = get(objs,'ButtonDownFcn');
mask=~strcmp(buttonDownFcns,''); objs=objs(mask); buttonDownFcns=buttonDownFcns(mask);
set(objs,'ButtonDownFcn','');
% save other figure values
figProps = {'KeyPressFcn','WindowButtonDownFcn'};
figValue = get(fig,figProps);
% do the real work
set(fig,'KeyPressFcn','set(gcf,''KeyPressFcn'','''',''WindowButtonDownFcn'','''');', ...
'WindowButtonDownFcn','set(gcf,''WindowButtonDownFcn'','''')');
lasterr('');
try
waitfor(fig,'WindowButtonDownFcn','');
wasInterrupted = 0;
catch
wasInterrupted = 1;
end
wasKeyPress = ~wasInterrupted & strcmp(get(fig,'KeyPressFcn'),'');
if wasInterrupted, errstr=lasterr; end;
% restore ButtonDownFcn and other figure values
set(objs,'ButtonDownFcn',buttonDownFcns);
set(fig,figProps,figValue);
function [out,is2D] = arrow_is2DXY(ax)
% check if axes are 2-D X-Y plots
% may not work for modified camera angles, etc.
out = false(size(ax)); % 2-D X-Y plots
is2D = out; % any 2-D plots
views = get(ax(:),{'View'});
views = cat(1,views{:});
out(:) = abs(views(:,2))==90;
is2D(:) = out(:) | all(rem(views',90)==0)';
function out = arrow_planarkids(ax)
% check if axes descendents all have empty ZData (lines,patches,surfaces)
out = true(size(ax));
allkids = get(ax(:),{'Children'});
for k=1:length(allkids),
kids = get([findobj(allkids{k},'flat','Type','line')
findobj(allkids{k},'flat','Type','patch')
findobj(allkids{k},'flat','Type','surface')],{'ZData'});
for j=1:length(kids),
if ~isempty(kids{j}), out(k)=logical(0); break; end;
end;
end;
function arrow_fixlimits(ax,lims)
% reset the axis limits as necessary
if isempty(ax) || isempty(lims), disp([upper(mfilename) ' does not remember any axis limits to reset.']); end;
for k=1:numel(ax),
if any(get(ax(k),'XLim')~=lims(1:2,k)'), set(ax(k),'XLim',lims(1:2,k)'); end;
if any(get(ax(k),'YLim')~=lims(3:4,k)'), set(ax(k),'YLim',lims(3:4,k)'); end;
if any(get(ax(k),'ZLim')~=lims(5:6,k)'), set(ax(k),'ZLim',lims(5:6,k)'); end;
end;
function out = arrow_WarpToFill(notstretched,manualcamera,curax)
% check if we are in "WarpToFill" mode.
out = strcmp(get(curax,'WarpToFill'),'on');
% 'WarpToFill' is undocumented, so may need to replace this by
% out = ~( any(notstretched) & any(manualcamera) );
function out = arrow_warnlimits(ax,narrows)
% create a warning message if we've changed the axis limits
msg = '';
switch (numel(ax))
case 1, msg='';
case 2, msg='on two axes ';
otherwise, msg='on several axes ';
end;
msg = [upper(mfilename) ' changed the axis limits ' msg ...
'when adding the arrow'];
if (narrows>1), msg=[msg 's']; end;
out = [msg '.' sprintf('\n') ' Call ' upper(mfilename) ...
' FIXLIMITS to reset them now.'];
function arrow_copyprops(fm,to)
% copy line properties to patches
props = {'EraseMode','LineStyle','LineWidth','Marker','MarkerSize',...
'MarkerEdgeColor','MarkerFaceColor','ButtonDownFcn', ...
'Clipping','DeleteFcn','BusyAction','HandleVisibility', ...
'Selected','SelectionHighlight','Visible'};
if ~isnumeric(findobj('Type','root')), props(strcmp(props,'EraseMode'))=[]; end; %eaj 5/24/16
lineprops = {'Color', props{:}};
patchprops = {'EdgeColor',props{:}};
patch2props = {'FaceColor',patchprops{:}};
fmpatch = strcmp(get(fm,'Type'),'patch');
topatch = strcmp(get(to,'Type'),'patch');
set(to( fmpatch& topatch),patch2props,get(fm( fmpatch& topatch),patch2props)); %p->p
set(to(~fmpatch&~topatch),lineprops, get(fm(~fmpatch&~topatch),lineprops )); %l->l
set(to( fmpatch&~topatch),lineprops, get(fm( fmpatch&~topatch),patchprops )); %p->l
set(to(~fmpatch& topatch),patchprops, get(fm(~fmpatch& topatch),lineprops) ,'FaceColor','none'); %l->p
function arrow_props
% display further help info about ARROW properties
c = sprintf('\n');
disp([c ...
'ARROW Properties: Default values are given in [square brackets], and other' c ...
' acceptable equivalent property names are in (parenthesis).' c c ...
' Start The starting points. For N arrows, B' c ...
' this should be a Nx2 or Nx3 matrix. /|\ ^' c ...
' Stop The end points. For N arrows, this /|||\ |' c ...
' should be a Nx2 or Nx3 matrix. //|||\\ L|' c ...
' Length Length of the arrowhead (in pixels on ///|||\\\ e|' c ...
' screen, points on a page). [16] (Len) ////|||\\\\ n|' c ...
' BaseAngle Angle (degrees) of the base angle /////|D|\\\\\ g|' c ...
' ADE. For a simple stick arrow, use //// ||| \\\\ t|' c ...
' BaseAngle=TipAngle. [90] (Base) /// ||| \\\ h|' c ...
' TipAngle Angle (degrees) of tip angle ABC. //<----->|| \\ |' c ...
' [16] (Tip) / base ||| \ V' c ...
' Width Width of the base in pixels. Not E angle ||<-------->C' c ...
' the ''LineWidth'' prop. [0] (Wid) |||tipangle' c ...
' Page If provided, non-empty, and not NaN, |||' c ...
' this causes ARROW to use hardcopy |||' c ...
' rather than onscreen proportions. A' c ...
' This is important if screen aspect --> <-- width' c ...
' ratio and hardcopy aspect ratio are ----CrossDir---->' c ...
' vastly different. []' c...
' CrossDir A vector giving the direction towards which the fletches' c ...
' on the arrow should go. [computed such that it is perpen-' c ...
' dicular to both the arrow direction and the view direction' c ...
' (i.e., as if it was pasted on a normal 2-D graph)] (Note' c ...
' that CrossDir is a vector. Also note that if an axis is' c ...
' plotted on a log scale, then the corresponding component' c ...
' of CrossDir must also be set appropriately, i.e., to 1 for' c ...
' no change in that direction, >1 for a positive change, >0' c ...
' and <1 for negative change.)' c ...
' NormalDir A vector normal to the fletch direction (CrossDir is then' c ...
' computed by the vector cross product [Line]x[NormalDir]). []' c ...
' (Note that NormalDir is a vector. Unlike CrossDir,' c ...
' NormalDir is used as is regardless of log-scaled axes.)' c ...
' Ends Set which end has an arrowhead. Valid values are ''none'',' c ...
' ''stop'', ''start'', and ''both''. [''stop''] (End)' c...
' ShortenLength Shorten length of arrowhead(s) if line is too short' c ...
' ObjectHandles Vector of handles to previously-created arrows to be' c ...
' updated or line objects to be converted to arrows.' c ...
' [] (Object,Handle)' c ...
' Type ''patch'' creates the arrow with a PATCH object (the default)' c ...
' and ''line'' creates it with a LINE object [''patch''].' c ...
' Color For patch arrows (the default), set both ''FaceColor'' and' c ...
' ''EdgeColor'' to the given value. For line arrows, set' c ...
' the ''Color'' property to the given value.' c ...
]);
function out = arrow_demo
% demo
% create the data
[x,y,z] = peaks;
[ddd,out.iii]=max(z(:));
out.axlim = [min(x(:)) max(x(:)) min(y(:)) max(y(:)) min(z(:)) max(z(:))];
% modify it by inserting some NaN's
[m,n] = size(z);
m = floor(m/2);
n = floor(n/2);
z(1:m,1:n) = NaN*ones(m,n);
% graph it
clf('reset');
out.hs=surf(x,y,z);
out.x=x; out.y=y; out.z=z;
xlabel('x'); ylabel('y');
function h = arrow_demo3(in)
% set the view
axlim = in.axlim;
axis(axlim);
zlabel('z');
%set(in.hs,'FaceColor','interp');
view(3); % view(viewmtx(-37.5,30,20));
title(['Demo of the capabilities of the ARROW function in 3-D']);
% Normal blue arrow
h1 = feval(mfilename,[axlim(1) axlim(4) 4],[-.8 1.2 4], ...
'EdgeColor','b','FaceColor','b');
% Normal white arrow, clipped by the surface
h2 = feval(mfilename,axlim([1 4 6]),[0 2 4]);
t=text(-2.4,2.7,7.7,'arrow clipped by surf');
% Baseangle<90
h3 = feval(mfilename,[3 .125 3.5],[1.375 0.125 3.5],30,50);
t2=text(3.1,.125,3.5,'local maximum');
% Baseangle<90, fill and edge colors different
h4 = feval(mfilename,axlim(1:2:5)*.5,[0 0 0],36,60,25, ...
'EdgeColor','b','FaceColor','c');
t3=text(axlim(1)*.5,axlim(3)*.5,axlim(5)*.5-.75,'origin');
set(t3,'HorizontalAlignment','center');
% Baseangle>90, black fill
h5 = feval(mfilename,[-2.9 2.9 3],[-1.3 .4 3.2],30,120,[],6, ...
'EdgeColor','r','FaceColor','k','LineWidth',2);
% Baseangle>90, no fill
h6 = feval(mfilename,[-2.9 2.9 1.3],[-1.3 .4 1.5],30,120,[],6, ...
'EdgeColor','r','FaceColor','none','LineWidth',2);
% Stick arrow
h7 = feval(mfilename,[-1.6 -1.65 -6.5],[0 -1.65 -6.5],[],16,16);
t4=text(-1.5,-1.65,-7.25,'global mininum');
set(t4,'HorizontalAlignment','center');
% Normal, black fill
h8 = feval(mfilename,[-1.4 0 -7.2],[-1.4 0 -3],'FaceColor','k');
t5=text(-1.5,0,-7.75,'local minimum');
set(t5,'HorizontalAlignment','center');
% Gray fill, crossdir specified, 'LineStyle' --
h9 = feval(mfilename,[-3 2.2 -6],[-3 2.2 -.05],36,[],27,6,[],[0 -1 0], ...
'EdgeColor','k','FaceColor',.75*[1 1 1],'LineStyle','--');
% a series of normal arrows, linearly spaced, crossdir specified
h10y=(0:4)'/3;
h10 = feval(mfilename,[-3*ones(size(h10y)) h10y -6.5*ones(size(h10y))], ...
[-3*ones(size(h10y)) h10y -.05*ones(size(h10y))], ...
12,[],[],[],[],[0 -1 0]);
% a series of normal arrows, linearly spaced
h11x=(1:.33:2.8)';
h11 = feval(mfilename,[h11x -3*ones(size(h11x)) 6.5*ones(size(h11x))], ...
[h11x -3*ones(size(h11x)) -.05*ones(size(h11x))]);
% series of magenta arrows, radially oriented, crossdir specified
h12x=2; h12y=-3; h12z=axlim(5)/2; h12xr=1; h12zr=h12z; ir=.15;or=.81;
h12t=(0:11)'/6*pi;
h12 = feval(mfilename, ...
[h12x+h12xr*cos(h12t)*ir h12y*ones(size(h12t)) ...
h12z+h12zr*sin(h12t)*ir],[h12x+h12xr*cos(h12t)*or ...
h12y*ones(size(h12t)) h12z+h12zr*sin(h12t)*or], ...
10,[],[],[],[], ...
[-h12xr*sin(h12t) zeros(size(h12t)) h12zr*cos(h12t)],...
'FaceColor','none','EdgeColor','m');
% series of normal arrows, tangentially oriented, crossdir specified
or13=.91; h13t=(0:.5:12)'/6*pi;
locs = [h12x+h12xr*cos(h13t)*or13 h12y*ones(size(h13t)) h12z+h12zr*sin(h13t)*or13];
h13 = feval(mfilename,locs(1:end-1,:),locs(2:end,:),6);
% arrow with no line ==> oriented downwards
h14 = feval(mfilename,[3 3 .100001],[3 3 .1],30);
t6=text(3,3,3.6,'no line'); set(t6,'HorizontalAlignment','center');
% arrow with arrowheads at both ends
h15 = feval(mfilename,[-.5 -3 -3],[1 -3 -3],'Ends','both','FaceColor','g', ...
'Length',20,'Width',3,'CrossDir',[0 0 1],'TipAngle',25);
h=[h1;h2;h3;h4;h5;h6;h7;h8;h9;h10;h11;h12;h13;h14;h15];
function h = arrow_demo2(in)
axlim = in.axlim;
dolog = 1;
if (dolog), set(in.hs,'YData',10.^get(in.hs,'YData')); end;
shading('interp');
view(2);
title(['Demo of the capabilities of the ARROW function in 2-D']);
hold on; [C,H]=contour(in.x,in.y,in.z,20,'-'); hold off;
for k=H',
set(k,'ZData',(axlim(6)+1)*ones(size(get(k,'XData'))),'Color','k');
if (dolog), set(k,'YData',10.^get(k,'YData')); end;
end;
if (dolog), axis([axlim(1:2) 10.^axlim(3:4)]); set(gca,'YScale','log');
else, axis(axlim(1:4)); end;
% Normal blue arrow
start = [axlim(1) axlim(4) axlim(6)+2];
stop = [in.x(in.iii) in.y(in.iii) axlim(6)+2];
if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end;
h1 = feval(mfilename,start,stop,'EdgeColor','b','FaceColor','b');
% three arrows with varying fill, width, and baseangle
start = [-3 -3 10; -3 -1.5 10; -1.5 -3 10];
stop = [-.03 -.03 10; -.03 -1.5 10; -1.5 -.03 10];
if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end;
h2 = feval(mfilename,start,stop,24,[90;60;120],[],[0;0;4],'Ends',str2mat('both','stop','stop'));
set(h2(2),'EdgeColor',[0 .35 0],'FaceColor',[0 .85 .85]);
set(h2(3),'EdgeColor','r','FaceColor',[1 .5 1]);
h=[h1;h2];
function out = trueornan(x)
if isempty(x),
out=x;
else,
out = isnan(x);
out(~out) = x(~out);
end;
BUILDVERLETLIST build the Verlet list of X
USAGE: verletList = buildVerletList(X [, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors])
[verletList,cutoff,dmin,config,distances] = buildVerletList(...)
verletList = buildVerletList(X,config)
WARNING use selfVerletList to add self in the list
verletList = selfVerletlist(buildVerletList(...))
Inputs:
X: n x 3 or n x 2 matrix containing the coordinates of particles
a table with columns 'x' 'y' 'z'
a table with columns 'x' 'y'
a cell {Xgrid X} to search the neighbors X around Xgrid
cutoff: the cutoff distance for building the Verlet list
by default: cutoff = distribution mode (1000 classes)
sorton: flag to force the VerletList to be sorted in the increasing order
by default: sorton = true
nblocks: number of blocks to reduce the amount memory required (typically max 8 GB)
verbose: set it to false to remove messages
excludedfromsearch: nx1 logical array (true if the coordinate does not need to be included in the search).
excludedneighbors: nx1 logical array (true if the coordinate is not a possible neihgbor)
Additional inputs (internal use)
entity: 'atom' (default) or 'grid point'
Outputs:
verletList: n x 1 cell coding for the Verlet list
verletList{i} list all indices j wihtin the cutoff distance
if sorton is used, verletList{i}(1) is the closest neighbor
cutoff: cutoff distance as provided or estimated
dmin: minimum distance (off-diagonal term)
config: configuration structure to be used with updateVerletList()
distances: pair distance matrix (=NaN if nblocks>1)
See also
updateVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
function [verletList,cutoffout,dminout,config,distout] = buildVerletList(X, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors,entity)
%BUILDVERLETLIST build the Verlet list of X
%
% USAGE: verletList = buildVerletList(X [, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors])
% [verletList,cutoff,dmin,config,distances] = buildVerletList(...)
% verletList = buildVerletList(X,config)
%
% WARNING use selfVerletList to add self in the list
% verletList = selfVerletlist(buildVerletList(...))
%
%
% Inputs:
% X: n x 3 or n x 2 matrix containing the coordinates of particles
% a table with columns 'x' 'y' 'z'
% a table with columns 'x' 'y'
% a cell {Xgrid X} to search the neighbors X around Xgrid
% cutoff: the cutoff distance for building the Verlet list
% by default: cutoff = distribution mode (1000 classes)
% sorton: flag to force the VerletList to be sorted in the increasing order
% by default: sorton = true
% nblocks: number of blocks to reduce the amount memory required (typically max 8 GB)
% verbose: set it to false to remove messages
% excludedfromsearch: nx1 logical array (true if the coordinate does not need to be included in the search).
% excludedneighbors: nx1 logical array (true if the coordinate is not a possible neihgbor)
%
% Additional inputs (internal use)
% entity: 'atom' (default) or 'grid point'
%
% Outputs:
% verletList: n x 1 cell coding for the Verlet list
% verletList{i} list all indices j wihtin the cutoff distance
% if sorton is used, verletList{i}(1) is the closest neighbor
% cutoff: cutoff distance as provided or estimated
% dmin: minimum distance (off-diagonal term)
% config: configuration structure to be used with updateVerletList()
% distances: pair distance matrix (=NaN if nblocks>1)
%
%
% See also: updateVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
% Example
%{
n = 3e4; %number of particles
% 3D example
X = rand(n, 3)*100;
cutoff = 10;
% 3D neighbors within cutoff distance
v = buildVerletList(X,cutoff);
figure, hold on
nsamples = 10;
samples = round(rand(nsamples,1)*n);
colors = parula(nsamples);
for i = 1:nsamples
plot3D(X(samples(i),:),'o','MarkerFaceColor',colors(i,:),'MarkerEdgeColor',colors(i,:))
plot3D(X(v{samples(i)},:),'o','MarkerFaceColor','none','MarkerEdgeColor',colors(i,:))
end
% 2D neighbors within cutoff distance
v2 = buildVerletList(X(:,1:2),cutoff);
for i = 1:nsamples
plot(X(samples(i),1),X(samples(i),2),'o','MarkerFaceColor',colors(i,:),'MarkerEdgeColor',colors(i,:))
plot(X(v2{samples(i)},1),X(v2{samples(i)},2),'o','MarkerFaceColor','none','MarkerEdgeColor',colors(i,:))
end
view(3), axis equal
%}
% MS 3.0 | 2023-03-25 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-04
% Revision history
% 2023-03-29 add nblocks and verbose
% 2023-03-31 improved code with validation
% 2023-04-01 return and accept config, accept X as a table
% 2023-05-16 accept {Xgrid X} as input #1 to list neigbors X around a grid (Xgrid)
% 2023-05-17 updated help
% 2023-08-31 fix time counter
% 2023-09-09 add entity
% 2024-02-22 manage 2D data and tables also with blocks
% 2024-02-23 reduce memoryloadGB in 2D
% 2024-03-04 update the block decomposition to fit a perfect square if needed in 2D (nB)
%% Constants
targetedNumberOfNeighbors = 100; % number of maximum neighbors expected (it is a maximum guess to be used to estimate a cutoff)
bytes = struct('single',4,'double',8);
DEBUGON = false; % set it to true to activate the debugging mode
tolerance = 0.1; % to be used by updateVerletList
%% Default
sorton_default = true; % default sorton value
verbose_default = true; % default verbosity
maxmemoryloadGB = 8; % maximum load memeory for pair distance (8 GB)
memoryloadGB = [NaN 1/(2*3)^2 1] * maxmemoryloadGB; % maximum GB to be used (memoryloadGB(3) for 3D and memoryloadGB(2) for 2D)
%% Check arguments
if nargin<1, error('one argument is required'); end
if istable(X)
coords = {'x','y','z'}; % the coordinates are selected in this order based on their availability (e.g. 'x' and 'y' in 2D)
availablecoords = coords(ismember(coords,X.Properties.VariableNames));
X = table2array(X(:,availablecoords));
end % table2array used for safety
if iscell(X)
if length(X)~=2, error('the first argument should be {Xgrid Xpart}'), end
Xgrid = X{1};
X = X{2};
hasgrid = true;
else
Xgrid = [];
hasgrid = false;
end
[n,d] = size(X); % number of particless
typ = class(X); % class of coordinates
if nargin<2, cutoff = []; end
if nargin<3, sorton = []; end
if nargin<4, nblocks = []; end
if nargin<5, verbose = []; end
if nargin<6, excludedfromsearch = []; end
if nargin<7, excludedneighbors = []; end
if nargin<8, entity = ''; end
% reuse a previous configuration
if isstruct(cutoff)
config = cutoff;
if isfield(config,'engine') && strcmp(config.engine,'buildVerletList')
if (config.natoms ~= n) || (config.dimensions~=d)
error('the number of atoms (%d) or/and dimensions (%d) have changed, expected %d x %d',...
n,d,config.natoms,confif.dimensions)
end
cutoff = config.cutoff;
sorton = config.sorton;
nblocks = config.nblocks;
verbose = config.verbose;
tolerance = config.tolerance;
else
error('unrecognized configuration structure')
end
end
% assign default values if needed
if isempty(cutoff) || cutoff<=0, cutoff = NaN; end
if isempty(sorton), sorton = sorton_default; end
if isempty(verbose), verbose = verbose_default; end
if isempty(nblocks)
nblocks = ceil((bytes.(typ) * d * n).^2 / (memoryloadGB(d)*1e9));
if d==2, nblocks = max(nblocks,nextpow2(n)); end % implement 2^nblocks rule for the number of classes
end
if isempty(entity), entity = "atom"; end
%% Grid management (look for neighbors around grid points)
if hasgrid
ngrid = size(Xgrid,1);
[excludedfromsearch,excludedneighbors] = deal(false(n+ngrid,1));
excludedfromsearch(1:n) = true;
excludedneighbors(n+1:end) = true;
[verletList,cutoffout_,dminout_,config_,distout_] = ...
buildVerletList([X;Xgrid], cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors,'grid point');
verletList = verletList(n+1:end);
distout_ = distout_(n+1:end);
if nargout>1, cutoffout = cutoffout_; end
if nargout>2, dminout = dminout_; end
if nargout>3, config = config_; end
if nargout>4, distout = distout_; end
return
else
ngrid = 1;
if isempty(excludedfromsearch), excludedfromsearch = false(n,1); end
if isempty(excludedneighbors), excludedneighbors = false(n,1); end
hasexclusions = any(excludedfromsearch) || any(excludedneighbors);
end
if hasexclusions
ivalid = find(~excludedfromsearch);
jvalid = find(~excludedneighbors);
jvalidreverse(jvalid) = 1:length(jvalid); % reverse operator
else
[ivalid,jvalid] = deal([]); % not used
end
%% main()
% Allocate
verletList = cell(n, 1); % pre-allocate the Verlet list
t__ = clock; t_ = t__;
% Pseudo recursion if nblocks > 1
if (nblocks > 1) && ismember(d,[2,3]) % block splitting limited to 3D and 2D
range = [min(X,[],1); max(X,[],1)]; % [mins;maxs]
drange = diff(range, 1, 1);
nBmax = max(2, floor(drange / (2 * cutoff)));
[~, is] = sort(drange, 'ascend');
nB = zeros(1, 3); %zeros(1, d) (NB(3) is a singleton if d == 2)
nB(is(1)) = max(1,min(nBmax(is(1)), floor(nblocks^(1/d)))); % number of blocks along the smallest dimension
nB(is(2)) = max(1,min(nBmax(is(2)), floor((nblocks / nB(is(1))^(d-1))))); % idem along the intermediate dimension
if d==3
nB(is(3)) = max(1,min(nBmax(is(3)), ceil(nblocks / (nB(is(1)) * nB(is(2)))))); % along the longest one
else
nB(3) = 1; % singleton in 2D but 2 limits to define one box
end
nB = nB + 1; % conversion into the number of separators
xB = linspace(range(1, 1) - drange(1) / 20, range(2, 1) + drange(1) / 20, nB(1));
yB = linspace(range(1, 2) - drange(2) / 20, range(2, 2) + drange(2) / 20, nB(2));
if d==3
zB = linspace(range(1, 3) - drange(3) / 20, range(2, 3) + drange(3) / 20, nB(3));
else
zB = 0; % singleton in 2D (default value 0)
end
% create meshgrid for block indices
[ix, iy, iz] = meshgrid(1:nB(1)-1, 1:nB(2)-1, 1:nB(3)-1);
ix = ix(:); iy = iy(:); iz = iz(:);
nBall = numel(ix); dist = cell(n, 1); dmin = NaN; cutoffsafe = 1.1 * cutoff; screen = '';
if d==3
dispf('buildVerletList splits %d particles into a %d x %d x %d',n, nB(1)-1, nB(2)-1, nB(3)-1)
elseif d==2
dispf('buildVerletList splits %d particles into a %d x %d',n, nB(1)-1, nB(2)-1)
end
% for each block
wasempty = true;
if verbose, dispf('Build Verlet list by searching in blocks...'), end
for iBall = 1:nBall
% points mask of considered entities/particles/beads
okxyz = (X(:, 1) > (xB(ix(iBall)) - cutoffsafe)) & (X(:, 1) < (xB(ix(iBall)+1) + cutoffsafe)) & ...
(X(:, 2) > (yB(iy(iBall)) - cutoffsafe)) & (X(:, 2) < (yB(iy(iBall)+1) + cutoffsafe)); % box + margins
if d==3
okxyz = okxyz & (X(:, 3) > (zB(iz(iBall)) - cutoffsafe)) & (X(:, 3) < (zB(iz(iBall)+1) + cutoffsafe)); % box + margins
end
ind = find(okxyz)'; % their indices (to be used for conversion)
nind = numel(ind); % number of entities
narein = 0; % default value (to be updated)
nneighborsforthosein = 0; % counter for their neighbors
if nind % some neighbors found
okinxyz = (X(ind, 1) >= xB(ix(iBall))) & (X(ind, 1) <= xB(ix(iBall)+1)) & ...
(X(ind, 2) >= yB(iy(iBall))) & (X(ind, 2) <= yB(iy(iBall)+1));
if d==3
okinxyz = okinxyz & (X(ind, 3) >= zB(iz(iBall))) & (X(ind, 3) <= zB(iz(iBall)+1));
end
arein = find(okinxyz); % relative indices of those inside the box
narein = length(arein); % number of entities in the box
[verletListtmp, cutoff, dmintmp, ~, disttmp] = buildVerletList(X(ind, :), cutoff, false, 0, false, excludedfromsearch(ind), excludedneighbors(ind));
dmin = min(dmin,dmintmp);
for eachin = 1:narein % for all elements in the temporary Verlet list
ind_current = ind(arein(eachin)); % scalar index if the current entity
ind_neighbors = ind(verletListtmp{arein(eachin)}); % vector of neighbor indices
nneighborsforthosein = nneighborsforthosein + length(ind_neighbors); % counter of neighbors
verletList{ind_current} = ind_neighbors; % update the verlet list
dist{ind_current} = disttmp{arein(eachin)}; % update the distance matrix
end % next eachin
else
dmintmp = NaN;
end % if nind
if verbose
done = iBall/nBall; currenttime = clock; dt = etime(currenttime,t_);
if (dt>0.5) || wasempty
dt_ = etime(currenttime,t__);
screen = dispb(screen,'[BLOCK %d/%d] %d particles with %d neighbors/part. (%0.1f %% total) | min dist loc:%0.3g glob:%0.3g | elapsed %0.1f of %0.1f s | done %0.1f %% | remaining %0.1f s',...
iBall,nBall,narein,round(nneighborsforthosein/narein),nind/n*100,dmintmp,dmin,dt,dt_,100*done,(1/done-1)*dt_);
wasempty = (nneighborsforthosein==0); t_ = currenttime;
end
end
end % next iBall
if verbose, dispb(screen,'... done in %0.4g s with %d search blocks | minimum distance %0.4g',dt,nBall,dmin); end
% DEBUG MODE (comparison without blocks, for internal validation)
if DEBUGON
V = verletList; %#ok
V0 = buildVerletList(X, cutoff, sorton, 0, true);
figure, hold on
[YB,ZB] = meshgrid(yB,zB); for x=xB, mesh(YB*0+x,YB,ZB,'facecolor','none','edgecolor','r'); end
[XB,ZB] = meshgrid(xB,zB); for y=yB, mesh(XB,ZB*0+y,ZB,'facecolor','none','edgecolor','r'); end
[XB,YB] = meshgrid(xB,yB); for z=zB, mesh(XB,YB,XB*0+z,'facecolor','none','edgecolor','r'); end
plot3D(X((cellfun(@length,V0)-cellfun(@length,V))>0,:),'bs')
xlabel('x'), ylabel('y'),zlabel('z')
view(3), axis equal, axis tight
dispf('if you do not see any bs, it is OK: %d',max(cellfun(@length,V0)-cellfun(@length,V)))
disp('make a break point here')
end
else % --------- vectorized code
% Compute pairwise distances using the built-in pdist2 function
if hasexclusions
if verbose, dispf('Calculate the %d x %d pair distances...',ngrid,n), t_ = clock; end %#ok<*CLOCK>
distances = pdist2(X(ivalid,:), X(jvalid,:));
else
if verbose, dispf('Calculate the %d x %d pair distances...',n,n), t_ = clock; end %#ok<*CLOCK>
distances = pdist2(X, X);
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)), end %#ok<*DETIM>
% Estimate cutoff if not provided (heuristic method)
% cutoff is set to match targetedNumberOfNeighbors
% without exceeding the mode of the distribution based on 1000 classes
% note: if the number of pair distances is very large, subsampling is applied
if isnan(cutoff)
if verbose, dispf('Estimate cutoff...'), t_ = clock; end
d = triu(distances,1); % all pair distances (can be very large)
d = d(d>0); dmin = min(d);
d = d(1:max(1,round(sqrt(length(d))/1000)):end); % subsampling
[c,d] = hist(d,1000); %#ok<*HIST>
% Identify the mode (maximum)
% counts are smoothed with a moving average over 10 classes
% figure, plot(d(1:end-1),diff(filtzero(cumsum(c),10)))
cfit = 1000 * diff(filtzero(cumsum(c),10)) / n;
d = d(1:end-1) - d(1) + dmin;
cfit = cfit-cfit(1); % first value (no, meaning)
[~,imax] = max(cfit);
% cutoff range
cutoff_min = 2 * dmin;
cutoff_max = d(imax);
% estimate the number of neighbors between cutoff_min and cutoff_max
% use nearest and cumulated cfit, discard the first 10 data (poorly estimated)
cutoff = max(cutoff_min,min(cutoff_max,...
d(nearestpoint(targetedNumberOfNeighbors,cumsum(cfit))+10)));
if verbose, dispf('\t ... done in %0.3g s (cutoff = %0.6g)',etime(clock,t_),cutoff), end
end
% Find particle pairs within the cutoff distance
if verbose, dispf('Find atoms within cuttoff...'), t_ = clock; end
if hasexclusions
[rowIndices, colIndices] = find(distances <= cutoff);
rowIndices = ivalid(rowIndices);
colIndices = jvalid(colIndices);
else
[rowIndices, colIndices] = find(triu(distances <= cutoff, 1));
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)); end
% Add the particle indices to the Verlet list
if verbose ,dispf('Build the Verlet list...'), t_ = clock; end
[t0,t1] = deal(clock);
npairs = numel(rowIndices);
screen = '';
for idx = 1:npairs
if mod(idx,100) == 0
if (etime(clock,t1) > 2) && verbose % every two seconds
dt = etime(clock,t0);
done = idx/npairs*100;
screen = dispb(screen,...
'Building the Verlet list [completed: %0.3g%% | elapsed: %0.3g s | remaining: %0.3g s]',...
done,dt,dt*(100/done-1));
t1 = clock;
end
end
i = rowIndices(idx);
j = colIndices(idx);
verletList{i}(end+1) = j;
if ~hasexclusions
verletList{j}(end+1) = i;
end
end
if verbose
verletCounts = cellfun(@length,verletList);
dispf('\t ... done in %0.3g s [min=%d, median=%d, max=%d]',etime(clock,t_),min(verletCounts),round(median(verletCounts)),max(verletCounts))
end
% short distances list (efficient), compute the dmin here (faster) // revised 2023-03-29
dmin = Inf;
dist = cell(n,1);
if hasexclusions
if ~isempty(distances)
for i_=1:length(ivalid)
i = ivalid(i_);
dist{i} = distances(i_,jvalidreverse(verletList{i}));
dmin = min(dmin,min(dist{i}));
end
end
else
for i=1:n
dist{i} = distances(i,verletList{i});
dmin = min(dmin,min(dist{i}));
end
end
end % if nblocks > 2
%% finalization
% Sort the Verlet list if requested
if sorton
if verbose, dispf('\t Sort the Verlet list...'), t_ = clock; end
for i=1:n
[~,u] = sort(dist{i},'ascend');
j = verletList{i};
verletList{i} = j(u);
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)), end
end
% Final elapsed time
timerq = etime(clock,t__);
% Return cuttoff if requested
if nargout>1, cutoffout = cutoff; end
% Return dmin if requested
if nargout>2
if isempty(dmin)
dminout = NaN;
else
dminout = dmin;
end
end
% Return config
if nargout>3
config = struct( ...
'engine','buildVerletList', ...
'elaspedtime',timerq,...
'natoms',n,...
'dimensions',d,...
'nblocks',nblocks,...
'cutoff',cutoff,...
'dmin',dmin,...
'sorton',sorton, ...
'verbose',verbose,...
'tolerance',tolerance,...
'nsamples',ceil(min(200,n*tolerance)) );
end
% Return distances if required
if nargout>4, distout = dist; end
% final display
if verbose
dispf('buildVerletList: all done in %0.3g s for %d %ss',timerq,n,entity)
end
BUILDVERLETLIST3 build the Verlet list of X when X is a nx3 vector, for arbitrary 2D or 3D X, use BUILDVERLIST for 2D datasets
USAGE: verletList = buildVerletList3(X [, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors])
[verletList,cutoff,dmin,config,distances] = buildVerletList3(...)
verletList = buildVerletList3(X,config)
WARNING use selfVerletList to add self in the list
verletList = selfVerletlist(buildVerletList3(...))
Inputs:
X: n x 3 matrix containing the coordinates of particles
a table with columns 'x' 'y' 'z'
a cell {Xgrid X} to search the neighbors X around Xgrid
cutoff: the cutoff distance for building the Verlet list
by default: cutoff = distribution mode (1000 classes)
sorton: flag to force the VerletList to be sorted in the increasing order
by default: sorton = true
nblocks: number of blocks to reduce the amount memory required (typically max 8 GB)
verbose: set it to false to remove messages
excludedfromsearch: nx1 logical array (true if the coordinate does not need to be included in the search).
excludedneighbors: nx1 logical array (true if the coordinate is not a possible neihgbor)
Additional inputs (internal use)
entity: 'atom' (default) or 'grid point'
Outputs:
verletList: n x 1 cell coding for the Verlet list
verletList{i} list all indices j wihtin the cutoff distance
if sorton is used, verletList{i}(1) is the closest neighbor
cutoff: cutoff distance as provided or estimated
dmin: minimum distance (off-diagonal term)
config: configuration structure to be used with updateVerletList()
distances: pair distance matrix (=NaN if nblocks>1)
See also
updateVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
function [verletList,cutoffout,dminout,config,distout] = buildVerletList3(X, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors,entity)
%BUILDVERLETLIST3 build the Verlet list of X when X is a nx3 vector, for arbitrary 2D or 3D X, use BUILDVERLIST for 2D datasets
%
% USAGE: verletList = buildVerletList3(X [, cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors])
% [verletList,cutoff,dmin,config,distances] = buildVerletList3(...)
% verletList = buildVerletList3(X,config)
%
% WARNING use selfVerletList to add self in the list
% verletList = selfVerletlist(buildVerletList3(...))
%
%
% Inputs:
% X: n x 3 matrix containing the coordinates of particles
% a table with columns 'x' 'y' 'z'
% a cell {Xgrid X} to search the neighbors X around Xgrid
% cutoff: the cutoff distance for building the Verlet list
% by default: cutoff = distribution mode (1000 classes)
% sorton: flag to force the VerletList to be sorted in the increasing order
% by default: sorton = true
% nblocks: number of blocks to reduce the amount memory required (typically max 8 GB)
% verbose: set it to false to remove messages
% excludedfromsearch: nx1 logical array (true if the coordinate does not need to be included in the search).
% excludedneighbors: nx1 logical array (true if the coordinate is not a possible neihgbor)
%
% Additional inputs (internal use)
% entity: 'atom' (default) or 'grid point'
%
% Outputs:
% verletList: n x 1 cell coding for the Verlet list
% verletList{i} list all indices j wihtin the cutoff distance
% if sorton is used, verletList{i}(1) is the closest neighbor
% cutoff: cutoff distance as provided or estimated
% dmin: minimum distance (off-diagonal term)
% config: configuration structure to be used with updateVerletList()
% distances: pair distance matrix (=NaN if nblocks>1)
%
%
% See also: updateVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
% MS 3.0 | 2023-03-25 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2023-09-09
% Revision history
% 2023-03-29 add nblocks and verbose
% 2023-03-31 improved code with validation
% 2023-04-01 return and accept config, accept X as a table
% 2023-05-16 accept {Xgrid X} as input #1 to list neigbors X around a grid (Xgrid)
% 2023-05-17 updated help
% 2023-08-31 fix time counter
% 2023-09-09 add entity
% 2024-02-22 original preserved 3D version, intended to be replaced by buildverletlist()
%% Constants
targetedNumberOfNeighbors = 100; % number of maximum neighbors expected (it is a maximum guess to be used to estimate a cutoff)
bytes = struct('single',4,'double',8);
DEBUGON = false; % set it to true to activate the debugging mode
tolerance = 0.1; % to be used by updateVerletList
%% Default
sorton_default = true; % default sorton value
verbose_default = true; % default verbosity
memoryloadGB = 8; % maximum GB to be used
%% Check arguments
if nargin<1, error('one argument is required'); end
if istable(X), X = table2array(X(:,{'x','y','z'})); end % table2array used for safety
if iscell(X)
if length(X)~=2, error('the first argument should be {Xgrid Xpart}'), end
Xgrid = X{1};
X = X{2};
hasgrid = true;
else
Xgrid = [];
hasgrid = false;
end
[n,d] = size(X); % number of particless
typ = class(X); % class of coordinates
if nargin<2, cutoff = []; end
if nargin<3, sorton = []; end
if nargin<4, nblocks = []; end
if nargin<5, verbose = []; end
if nargin<6, excludedfromsearch = []; end
if nargin<7, excludedneighbors = []; end
if nargin<8, entity = ''; end
% reuse a previous configuration
if isstruct(cutoff)
config = cutoff;
if isfield(config,'engine') && strcmp(config.engine,'buildVerletList')
if (config.natoms ~= n) || (config.dimensions~=d)
error('the number of atoms (%d) or/and dimensions (%d) have changed, expected %d x %d',...
n,d,config.natoms,confif.dimensions)
end
cutoff = config.cutoff;
sorton = config.sorton;
nblocks = config.nblocks;
verbose = config.verbose;
tolerance = config.tolerance;
else
error('unrecognized configuration structure')
end
end
% assign default values if needed
if isempty(cutoff) || cutoff<=0, cutoff = NaN; end
if isempty(sorton), sorton = sorton_default; end
if isempty(verbose), verbose = verbose_default; end
if isempty(nblocks), nblocks = ceil((bytes.(typ) * d * n).^2 / (memoryloadGB*1e9)); end
if isempty(entity), entity = "atom"; end
%% Grid management (look for neighbors around grid points)
if hasgrid
ngrid = size(Xgrid,1);
[excludedfromsearch,excludedneighbors] = deal(false(n+ngrid,1));
excludedfromsearch(1:n) = true;
excludedneighbors(n+1:end) = true;
[verletList,cutoffout_,dminout_,config_,distout_] = ...
buildVerletList3([X;Xgrid], cutoff, sorton, nblocks, verbose, excludedfromsearch, excludedneighbors,'grid point');
verletList = verletList(n+1:end);
distout_ = distout_(n+1:end);
if nargout>1, cutoffout = cutoffout_; end
if nargout>2, dminout = dminout_; end
if nargout>3, config = config_; end
if nargout>4, distout = distout_; end
return
else
ngrid = 1;
if isempty(excludedfromsearch), excludedfromsearch = false(n,1); end
if isempty(excludedneighbors), excludedneighbors = false(n,1); end
hasexclusions = any(excludedfromsearch) || any(excludedneighbors);
end
if hasexclusions
ivalid = find(~excludedfromsearch);
jvalid = find(~excludedneighbors);
jvalidreverse(jvalid) = 1:length(jvalid); % reverse operator
else
[ivalid,jvalid] = deal([]); % not used
end
%% main()
% Allocate
verletList = cell(n, 1); % pre-allocate the Verlet list
t__ = clock; t_ = t__;
% Pseudo recursion if nblocks > 1
if (nblocks > 1) && (d == 3) % block splitting limited to 3D
range = [min(X,[],1); max(X,[],1)]; % [mins;maxs]
drange = diff(range, 1, 1);
nBmax = max(2, floor(drange / (2 * cutoff)));
[~, is] = sort(drange, 'ascend');
nB = zeros(1, d);
nB(is(1)) = 1 + min(nBmax(is(1)), floor(nblocks^(1/3))); % number of blocks along the smallest dimension
nB(is(2)) = 1 + min(nBmax(is(2)), floor(sqrt(nblocks / nB(is(1))))); % idem along the intermediate dimension
nB(is(3)) = 1 + min(nBmax(is(3)), ceil(nblocks / (nB(is(1)) * nB(is(2))))); % along the longest one
xB = linspace(range(1, 1) - drange(1) / 20, range(2, 1) + drange(1) / 20, nB(1));
yB = linspace(range(1, 2) - drange(2) / 20, range(2, 2) + drange(2) / 20, nB(2));
zB = linspace(range(1, 3) - drange(3) / 20, range(2, 3) + drange(3) / 20, nB(3));
% create meshgrid for block indices
[ix, iy, iz] = meshgrid(1:nB(1)-1, 1:nB(2)-1, 1:nB(3)-1);
ix = ix(:); iy = iy(:); iz = iz(:);
nBall = numel(ix); dist = cell(n, 1); dmin = NaN; cutoffsafe = 1.1 * cutoff; screen = '';
% for each block
wasempty = true;
if verbose, dispf('Build Verlet list by searching in blocks...'), end
for iBall = 1:nBall
% points mask of considered entities/particles/beads
okxyz = (X(:, 1) > (xB(ix(iBall)) - cutoffsafe)) & (X(:, 1) < (xB(ix(iBall)+1) + cutoffsafe)) & ...
(X(:, 2) > (yB(iy(iBall)) - cutoffsafe)) & (X(:, 2) < (yB(iy(iBall)+1) + cutoffsafe)) & ...
(X(:, 3) > (zB(iz(iBall)) - cutoffsafe)) & (X(:, 3) < (zB(iz(iBall)+1) + cutoffsafe)); % box + margins
ind = find(okxyz)'; % their indices (to be used for conversion)
nind = numel(ind); % number of entities
narein = 0; % default value (to be updated)
nneighborsforthosein = 0; % counter for their neighbors
if nind % some neighbors found
arein = find(... % relative indices of those inside the box
(X(ind, 1) >= xB(ix(iBall))) & (X(ind, 1) <= xB(ix(iBall)+1)) & ...
(X(ind, 2) >= yB(iy(iBall))) & (X(ind, 2) <= yB(iy(iBall)+1)) & ...
(X(ind, 3) >= zB(iz(iBall))) & (X(ind, 3) <= zB(iz(iBall)+1)) ...
);
narein = length(arein); % number of entities in the box
[verletListtmp, cutoff, dmintmp, ~, disttmp] = buildVerletList3(X(ind, :), cutoff, false, 0, false, excludedfromsearch(ind), excludedneighbors(ind));
dmin = min(dmin,dmintmp);
for eachin = 1:narein % for all elements in the temporary Verlet list
ind_current = ind(arein(eachin)); % scalar index if the current entity
ind_neighbors = ind(verletListtmp{arein(eachin)}); % vector of neighbor indices
nneighborsforthosein = nneighborsforthosein + length(ind_neighbors); % counter of neighbors
verletList{ind_current} = ind_neighbors; % update the verlet list
dist{ind_current} = disttmp{arein(eachin)}; % update the distance matrix
end % next eachin
end % if nind
if verbose
done = iBall/nBall; currenttime = clock; dt = etime(currenttime,t_);
if (dt>0.5) || wasempty
dt_ = etime(currenttime,t__);
screen = dispb(screen,'[BLOCK %d/%d] %d particles with %d neighbors (%0.1f %% total) | min dist loc:%0.3g glob:%0.3g | elapsed %0.1f of %0.1f s | done %0.1f %% | remaining %0.1f s',...
iBall,nBall,narein,nneighborsforthosein,nind/n*100,dmintmp,dmin,dt,dt_,100*done,(1/done-1)*dt_);
wasempty = (nneighborsforthosein==0); t_ = currenttime;
end
end
end % next iBall
if verbose, dispb(screen,'... done in %0.4g s with %d search blocks | minimum distance %0.4g',dt,nBall,dmin); end
% DEBUG MODE (comparison without blocks, for internal validation)
if DEBUGON
V = verletList; %#ok
V0 = buildVerletList3(X, cutoff, sorton, 0, true);
figure, hold on
[YB,ZB] = meshgrid(yB,zB); for x=xB, mesh(YB*0+x,YB,ZB,'facecolor','none','edgecolor','r'); end
[XB,ZB] = meshgrid(xB,zB); for y=yB, mesh(XB,ZB*0+y,ZB,'facecolor','none','edgecolor','r'); end
[XB,YB] = meshgrid(xB,yB); for z=zB, mesh(XB,YB,XB*0+z,'facecolor','none','edgecolor','r'); end
plot3D(X((cellfun(@length,V0)-cellfun(@length,V))>0,:),'bs')
xlabel('x'), ylabel('y'),zlabel('z')
view(3), axis equal, axis tight
dispf('if you do not see any bs, it is OK: %d',max(cellfun(@length,V0)-cellfun(@length,V)))
disp('make a break point here')
end
else % --------- vectorized code
% Compute pairwise distances using the built-in pdist2 function
if hasexclusions
if verbose, dispf('Calculate the %d x %d pair distances...',ngrid,n), t_ = clock; end %#ok<*CLOCK>
distances = pdist2(X(ivalid,:), X(jvalid,:));
else
if verbose, dispf('Calculate the %d x %d pair distances...',n,n), t_ = clock; end %#ok<*CLOCK>
distances = pdist2(X, X);
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)), end %#ok<*DETIM>
% Estimate cutoff if not provided (heuristic method)
% cutoff is set to match targetedNumberOfNeighbors
% without exceeding the mode of the distribution based on 1000 classes
% note: if the number of pair distances is very large, subsampling is applied
if isnan(cutoff)
if verbose, dispf('Estimate cutoff...'), t_ = clock; end
d = triu(distances,1); % all pair distances (can be very large)
d = d(d>0); dmin = min(d);
d = d(1:max(1,round(sqrt(length(d))/1000)):end); % subsampling
[c,d] = hist(d,1000); %#ok<*HIST>
% Identify the mode (maximum)
% counts are smoothed with a moving average over 10 classes
% figure, plot(d(1:end-1),diff(filtzero(cumsum(c),10)))
cfit = 1000 * diff(filtzero(cumsum(c),10)) / n;
d = d(1:end-1) - d(1) + dmin;
cfit = cfit-cfit(1); % first value (no, meaning)
[~,imax] = max(cfit);
% cutoff range
cutoff_min = 2 * dmin;
cutoff_max = d(imax);
% estimate the number of neighbors between cutoff_min and cutoff_max
% use nearest and cumulated cfit, discard the first 10 data (poorly estimated)
cutoff = max(cutoff_min,min(cutoff_max,...
d(nearestpoint(targetedNumberOfNeighbors,cumsum(cfit))+10)));
if verbose, dispf('\t ... done in %0.3g s (cutoff = %0.6g)',etime(clock,t_),cutoff), end
end
% Find particle pairs within the cutoff distance
if verbose, dispf('Find atoms within cuttoff...'), t_ = clock; end
if hasexclusions
[rowIndices, colIndices] = find(distances <= cutoff);
rowIndices = ivalid(rowIndices);
colIndices = jvalid(colIndices);
else
[rowIndices, colIndices] = find(triu(distances <= cutoff, 1));
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)); end
% Add the particle indices to the Verlet list
if verbose ,dispf('Build the Verlet list...'), t_ = clock; end
[t0,t1] = deal(clock);
npairs = numel(rowIndices);
screen = '';
for idx = 1:npairs
if mod(idx,100) == 0
if (etime(clock,t1) > 2) && verbose % every two seconds
dt = etime(clock,t0);
done = idx/npairs*100;
screen = dispb(screen,...
'Building the Verlet list [completed: %0.3g%% | elapsed: %0.3g s | remaining: %0.3g s]',...
done,dt,dt*(100/done-1));
t1 = clock;
end
end
i = rowIndices(idx);
j = colIndices(idx);
verletList{i}(end+1) = j;
if ~hasexclusions
verletList{j}(end+1) = i;
end
end
if verbose
verletCounts = cellfun(@length,verletList);
dispf('\t ... done in %0.3g s [min=%d, median=%d, max=%d]',etime(clock,t_),min(verletCounts),round(median(verletCounts)),max(verletCounts))
end
% short distances list (efficient), compute the dmin here (faster) // revised 2023-03-29
dmin = Inf;
dist = cell(n,1);
if hasexclusions
if ~isempty(distances)
for i_=1:length(ivalid)
i = ivalid(i_);
dist{i} = distances(i_,jvalidreverse(verletList{i}));
dmin = min(dmin,min(dist{i}));
end
end
else
for i=1:n
dist{i} = distances(i,verletList{i});
dmin = min(dmin,min(dist{i}));
end
end
end % if nblocks > 2
%% finalization
% Sort the Verlet list if requested
if sorton
if verbose, dispf('\t Sort the Verlet list...'), t_ = clock; end
for i=1:n
[~,u] = sort(dist{i},'ascend');
j = verletList{i};
verletList{i} = j(u);
end
if verbose, dispf('\t ... done in %0.3g s',etime(clock,t_)), end
end
% Final elapsed time
timerq = etime(clock,t__);
% Return cuttoff if requested
if nargout>1, cutoffout = cutoff; end
% Return dmin if requested
if nargout>2, if isempty(dmin), dminout = NaN; else dminout = dmin; end, end
if nargout>3
config = struct( ...
'engine','buildVerletList', ...
'elaspedtime',timerq,...
'natoms',n,...
'dimensions',d,...
'nblocks',nblocks,...
'cutoff',cutoff,...
'dmin',dmin,...
'sorton',sorton, ...
'verbose',verbose,...
'tolerance',tolerance,...
'nsamples',ceil(min(200,n*tolerance)) );
end
% Return distances if required
if nargout>4, distout = dist; end
% final display
if verbose
dispf('buildVerletList: all done in %0.3g s for %d %ss',timerq,n,entity)
end
checkfiles
2023-04-23 checkfiles: generic script to check lamdumpread prefetch and split (frame) files
outputs in Markdown for Mermaid and Markmap
%checkfiles
% 2023-04-23 checkfiles: generic script to check lamdumpread prefetch and split (frame) files
% outputs in Markdown for Mermaid and Markmap
%% look for gz files
switch localname
case 'LX-Olivier2021'
local = '/media/olivi/MODIC4TB_WSJ2/'; %#ok<*NASGU>
local = '/media/olivi/MODIC4TB_WSJ1/';
case 'LP-OLIVIER2022'
local = 'E:\';
otherwise
dispf('implement you machine by adding\n\n\tCASE ''%s''\n\t\tlocal = ''this is my path''\n',localname)
error('unknown machine and local path')
end
target = '*.tar.gz';
fgz = explore(target,local,100,'abbreviate');
ngz = length(fgz);
%% look for dump files without the extension tar.gz
% templates
makeprefetch = @(fn) char(fullfile(rootdir(fn),['PREFETCH_' lastdir(fn) '.mat'])); % added 2023/03/23
makesplitdir = @(fn) char(fullfile(rootdir(fn),['PREFETCH_' lastdir(fn)])); % added 2023/03/23
statenfo = @(done,dt) sprintf('| elapsed %0.3g s | done %0.3g %% | remaining %0.3g s |',dt,done*100,(1/done-1)*dt);
SPLITPREFIX = 'TIMESTEP_';
MB = 1024^2;
GB = 1024^3;
screen = ''; t0 = clock;
for i=1:ngz
done = (i-1)/ngz; dt = etime(clock,t0); %#ok<*DETIM>
screen = dispb(screen,'[%d/%d] search for dump files and frames ... %s',i,ngz,statenfo(done,dt));
dfi = explore('dump.*',fgz(i).path,10,'abbreviate');
dfi=dfi(cellfun(@(e) ~any(strcmpi(e,{'gz','mat'})),{dfi.ext})); % remove gz extensions
ndfi = length(dfi);
% for each
for j = 1:ndfi
f = fullfile(dfi(j).path,dfi(j).file);
dfi(j).hasprefetch = exist(makeprefetch(f),'file')==2;
dfi(j).hassplitdir = exist(makesplitdir(f),'dir')==7;
% counts frames
if dfi(j).hassplitdir
dfi(j).frames = explore(sprintf('%s*.mat',SPLITPREFIX),makesplitdir(f),0,'abbreviate');
dfi(j).nframes = length(dfi(j).frames);
t1 = clock;
done = (i-1+(j-1)/ndfi)/ngz; dt = etime(clock,t0);
screen = dispb(screen,'[%d/%d] %d of %d dump files with %d frames ... %s',i,ngz,j,ndfi,dfi(j).nframes,statenfo(done,dt));
for iframe = 1:dfi(j).nframes
dfi(j).frames(iframe).timestep = str2double(regexprep(dfi(j).frames(iframe).name,SPLITPREFIX,''));
end
else
dfi(j).nframes = 0;
ispb(screen,'[%d/%d: %d] no frame found !',i,ngz,j); screen = '';
end
end
% save
fgz(i).ndump = ndfi;
if ndfi
fgz(i).dump = dfi;
fgz(i).nprefetch = length(find([dfi.hasprefetch]));
fgz(i).nsplitdir = length(find([dfi.hassplitdir]));
else
fgz(i).dump = struct([]);
fgz(i).nprefetch = 0;
fgz(i).nsplitdir = 0;
dispb(screen,'[%d/%d] no dump found !',i,ngz); screen = '';
end
end
%% Files not untar and ungzipped
untar = fgz([fgz.ndump]<1);
dispf('%d files are not untarred and ungzipped',length(untar))
%% create a database
ndumps = sum([fgz.ndump]);
dispf('%d dump filmes have been found',ndumps)
% review the tags (based on ext)
nmaxtags = 0;
idump = 0;
for i=1:length(fgz)
for j=1:length(fgz(i).dump)
idump = idump + 1;
name = fgz(i).dump(j).ext;
nmaxtags = max(nmaxtags,length(strsplit(name,'_')));
end
end
tagfields = arrayfun(@(t) sprintf('tag%d',t),1:nmaxtags,'UniformOutput',false);
tagtypes = repmat({'categorical'},1,nmaxtags);
% preallocate the table
T = table( ...
'Size',[ndumps,22+length(tagfields)], ...
'VariableTypes',[{'string','datetime'} tagtypes {'logical','logical' ,'string' ,'double' ,'double' ,'cell' , 'double','double','string' ,'string' ,'cell' ,'double' ,'double' ,'string' ,'string' ,'string' ,'double','string','string','double'}],...
'VariableNames',[{'name','date'} tagfields {'hasprefetch' ,'hassplitdir','prefetchfile','prefetchMB','nframes','tframe','tfirst' ,'tlast' ,'firstframe','lastframe' ,'frames' ,'frameMB','framesGB','framespath','dumpfile','dumppath','dumpGB','gzfile','gzpath','gzGB'}]);
% fill the table
idump = 0;
for i=1:length(fgz)
for j=1:length(fgz(i).dump)
idump = idump + 1;
% name
name = fgz(i).dump(j).ext;
T.name(idump) = name;
% date
T.date(idump) = datetime(fgz(i).dump(j).datenum,'ConvertFrom','datenum','format','yyyy-MM-dd');
% add tags
tags = strsplit(name,'_'); ntags = length(tags);
for itag = 1:ntags
T.(tagfields{itag})(idump) = tags(itag);
end
% prefetch/split flags
T.hasprefetch(idump) = fgz(i).dump(j).hasprefetch;
T.hassplitdir(idump) = fgz(i).dump(j).hassplitdir;
% dump file
T.dumppath(idump) = fgz(i).dump(j).path;
T.dumpfile(idump) = fgz(i).dump(j).file;
T.dumpGB(idump) = fgz(i).dump(j).bytes/GB;
% prefetch file
if T.hasprefetch(idump)
T.prefetchfile(idump) = makeprefetch(char(fullfile(T.dumppath(idump),T.dumpfile(idump))));
tmp = dir(T.prefetchfile(idump));
T.prefetchMB(idump) = tmp.bytes/MB;
else
dispf('dump #%d: missing prefetch !',idump)
end
% frames
timesteps = [fgz(i).dump(j).frames.timestep];
frames = string({fgz(i).dump(j).frames.file});
[tfirst,kfirst] = min(timesteps);
[tlast,klast] = max(timesteps);
T.nframes(idump) = fgz(i).dump(j).nframes;
T.tframe{idump} = timesteps;
T.tfirst(idump) = tfirst;
T.tlast(idump) = tlast;
T.firstframe(idump) = frames(kfirst);
T.lastframe(idump) = frames(klast);
T.frames{idump} = frames;
T.framespath(idump) = fgz(i).dump(j).frames(1).path;
T.framesGB(idump) = sum([fgz(i).dump(j).frames.bytes])/GB;
T.frameMB(idump) = (T.framesGB(idump)/T.nframes(idump)) * (GB/MB);
% gz info
T.gzpath(idump) = fgz(i).path;
T.gzfile(idump) = fgz(i).file;
T.gzGB(idump) = fgz(i).bytes/GB;
end
end
% remove any duplicate
idT = cellfun(@(p,f) fullfile(p,f) ,T.dumppath,T.dumpfile,'uniformoutput',false);
[~,kept] = unique(idT,'stable');
T = T(kept,:);
nT = size(T,1);
%% TAGs
ntags = length(tagfields);
for i=1:ntags
T.(tagfields{i})(ismissing(T.(tagfields{i}))) = sprintf('UndefTag%02d',i);
end
Ttagsafe = T(:,tagfields);
tagvalues = cellfun(@(t) categories(T.(t)),tagfields,'UniformOutput',false);
tagvaluessafe = tagvalues;
taginternal = tagvalues;
repeatedtags = [];
knowntags = {};
for i=1:ntags
for j=1:length(tagvalues{i})
if ismember(tagvalues{i}{j},knowntags)
k = find(ismember(knowntags,tagvalues{i}{j}));
repeatedtags(k) = repeatedtags(k) + 1; %#ok<*SAGROW>
tagvaluessafe{i}{j} = sprintf('%s_%0d',tagvaluessafe{i}{j},repeatedtags(k));
tobereplaced = T.(tagfields{i})==tagvalues{i}{j};
Ttagsafe.(tagfields{i})(tobereplaced)=tagvaluessafe{i}{j};
end
knowntags{end+1} = tagvaluessafe{i}{j};
repeatedtags(end+1) = 1;
taginternal{i}{j} = sprintf('TAG%02d%02d',i,j);
end
end
%% Generate the diGraph
rootnode = 'root';
nodes = arrayfun(@(n) sprintf('dump%03d',n),(1:nT)','UniformOutput',false);
tagnodes = cat(1,taginternal{:});
tagrealnodes= cat(1,tagvaluessafe{:});
% defintions (should match mermaidones)
definitions = [
{rootnode}
tagrealnodes
nodes
];
ndefintions = length(definitions);
% mermaid defintions
mermaiddefinitions = [
{sprintf('%s[%s]',rootnode,strrep(local,'\','_'))}
cellfun(@(n,d) sprintf('%s[%s]',n,d),tagnodes,cat(1,tagvalues{:}),'UniformOutput',false);
arrayfun(@(i) sprintf('%s[%d: %s]',nodes{i},i,T.dumpfile(i)),(1:nT)','UniformOutput',false)
];
mermaidnodes = [
{rootnode}
tagnodes
nodes
];
% ID
ID = zeros(nT,ntags+1);
[~,ID(:,end)] = intersect(definitions,nodes,'stable');
for i=1:ntags
[usetags,iusetags] = intersect(definitions,Ttagsafe.(tagfields{i}),'stable');
for j = 1:length(usetags)
ID(ismember(T.(tagfields{i}),usetags(j)),i) = iusetags(j);
end
end
ID = [ones(nT,1) ID];
C = false(ndefintions,ndefintions);
for i = 1:nT
ind = ID(i,ID(i,:)>0);
for j=2:length(ind)
C(ind(j-1),ind(j)) = true;
end
end
g = digraph(C,definitions); figure, plot(g,'nodelabel',definitions,'layout','force3');
%% Generate the Mermaid document
mermaidheader = {
'```mermaid'
'graph LR;'
'linkStyle default interpolate basis'
''
};
mermaidfooter = {'```'};
[i,j] = ind2sub(size(C),find(C));
mermaidlinks = arrayfun(@(a,b) sprintf('%s --> %s',mermaidnodes{a},mermaidnodes{b}),i,j,'UniformOutput',false);
mermaid = [mermaidheader;mermaiddefinitions;mermaidlinks;mermaidfooter];
clipboard('copy',sprintf('%s\n',mermaid{:}))
%% Generate the Markdown document
markdown = {
'---'
'markmap:'
' colorFreezeLevel: 5'
' initialExpandLevel: 3'
' maxWidth: 400'
'---'
};
level = @(position,txt) sprintf('%s [tag%0d] %s',repmat('#',position,1),position,txt);
vec = zeros(1,ntags); vec(1)=1;
lengthvec = cellfun(@length,tagvalues);
position = 1;
ok = false(nT,ntags);
finished = false;
newlevel = true;
nfound = 0;
founditems = [];
action = 'init';
DEBUGON = true; DEBUGCOMB = NaN(prod(lengthvec),ntags); DEBUGITER = 0;
isprintedT = false(nT,1);
if DEBUGON, clc, end
while (position>0) && any(vec)
terminal = (position==ntags);
% refresh flags
currenttag = tagvalues{position}{vec(position)};
ok(:,position) = (T.(tagfields{position}) == currenttag);
currentfound = find(all(ok(:,1:position),2));
completed = false;
if position>1 && isempty(currentfound)
previousfound = find(all(ok(:,1:position-1),2));
if ~isempty(previousfound)
completed = all(ismissing(T.(tagfields{position})(previousfound)));
if completed
currentfound = previousfound;
end
end
end
ncurrentfound = length(currentfound);
% display (debugging purposes)
% check duplicates with this line
%[~,ia]=unique(DEBUGCOMB,'rows','first'); [~,ib]=unique(DEBUGCOMB,'rows','last'); [ia(find(ia-ib,1,'first')) ib(find(ia-ib,1,'first'))]
% check missing
%{
arrays = arrayfun(@(i) 1:lengthvec(i), 1:ntags,'UniformOutput',false);
[grid{1:ntags}] = ndgrid(arrays{:});
grid_arrays = cellfun(@(x) x(:), grid, 'UniformOutput', false);
combinations = [grid_arrays{:}]; disp(size(combinations));
missing = combinations(~ismember(combinations,DEBUGCOMB,'rows'),:);
findfirst = find(sum(missing(:,1:3)-[1 1 2],2)==0)
%}
if DEBUGON %&& ismember(action,{'init','keep','keep+reset'})
if terminal, DEBUGITER = DEBUGITER+1; DEBUGCOMB(DEBUGITER,:) = vec; end
vecstr = arrayfun(@(v) num2str(v),vec,'UniformOutput',false);
vecstr{position} = sprintf('[%s]%',vecstr{position});
if newlevel, newstr = '*'; else newstr = '-'; end %#ok<*SEPEX>
if terminal, newstr = [newstr newstr]; end %#ok<*AGROW>
dispf('%d> %s%s <-- %s (%d found [%s], total = %d)',DEBUGITER,sprintf('%s ',vecstr{:}),newstr,action,ncurrentfound,currenttag,nfound)
end
% do
if ncurrentfound>0
if newlevel %(newlevel || terminal || (ncurrentfound == 1)) && ~completed
markdown{end+1} = level(position,tagvalues{position}{vec(position)});
end
if terminal %(ncurrentfound < 2) %|| (ncurrentfound>1 && (terminal || completed))
founditems = union(founditems,currentfound);
for ifound = 1:ncurrentfound
nfound = nfound + 1;
mycurentnode = currentfound(ifound);
if ~isprintedT(mycurentnode)
markdown{end+1} = sprintf('> **`%s`**',T.dumpfile(mycurentnode));
markdown{end+1} = sprintf('> ORIGINAL file size: %0.2f GB',T.dumpGB(mycurentnode));
if ~ismissing(T.prefetchfile(mycurentnode))
markdown{end+1} = sprintf('> PREFETCH file (%0.2f MB): %s',T.prefetchMB(mycurentnode),strrep(T.prefetchfile(mycurentnode),'\','/'));
else
markdown{end+1} = '> ==missing PREFETCH file==';
end
if ~ismissing(T.framespath(mycurentnode))
markdown{end+1} = sprintf('> FRAMES folder (%0.2f GB): %s',T.framesGB(mycurentnode),strrep(T.framespath(mycurentnode),'\','/'));
markdown{end+1} = sprintf('> NUMBER of frames: *%d* (%0.2f MB/frame)',T.nframes(mycurentnode),T.frameMB(mycurentnode));
markdown{end+1} = sprintf('> FIRST TIMESTEP: *%d*',T.tfirst(mycurentnode));
markdown{end+1} = sprintf('> LAST TIMESTEP: *%d*',T.tlast(mycurentnode));
markdown{end+1} = sprintf('> FIRST FRAME: %s',strrep(T.firstframe(mycurentnode),'\','//'));
markdown{end+1} = sprintf('> FIRST LAST: %s',strrep(T.lastframe(mycurentnode),'\','//'));
else
markdown{end+1} = '> ==missing FRAMES folder==';
end
markdown{end+1} = newline;
isprintedT(mycurentnode)=true;
end % already printed
end % next found
end
end
% shift operations
if (position% descent into the next level
vec(position) = 1;
if positionend
newlevel = true;
action = 'descent';
else
if vec(position)% keep the same level
if (position'keep+reset';
else
newlevel = false;
action = 'keep';
end
else
position = position - 1; % raise the level (nothing done)
newlevel = false;
action = 'raise';
end
end
end
DEBUGCOMB(any(isnan(DEBUGCOMB),2),:) = [];
clipboard('copy',sprintf('%s\n',markdown{:}))
color_line3 plots a 3-D "line" with c-data as color
h = color_line(x, y, z, c)
by default: 'LineStyle','-' and 'Marker','none'
or
h = color_line(x, y, z, c, mark)
or
h = color_line(x, y, z, c, 'Property','value'...)
with valid 'Property','value' pairs for a surface object
in: x x-data
y y-data
z z-data
c 4th dimension for colouring
mark for scatter plots with no connecting line
out: h handle of the surface object
function h = color_line3(x, y, z, c, varargin)
% color_line3 plots a 3-D "line" with c-data as color
%
% h = color_line(x, y, z, c)
% by default: 'LineStyle','-' and 'Marker','none'
%
% or
% h = color_line(x, y, z, c, mark)
% or
% h = color_line(x, y, z, c, 'Property','value'...)
% with valid 'Property','value' pairs for a surface object
%
% in: x x-data
% y y-data
% z z-data
% c 4th dimension for colouring
% mark for scatter plots with no connecting line
%
% out: h handle of the surface object
h = surface(...
'XData',[x(:) x(:)],...
'YData',[y(:) y(:)],...
'ZData',[z(:) z(:)],...
'CData',[c(:) c(:)],...
'FaceColor','none',...
'EdgeColor','flat',...
'Marker','none');
if nargin ==5
switch varargin{1}
case {'+' 'o' '*' '.' 'x' 'square' 'diamond' 'v' '^' '>' '<' 'pentagram' 'p' 'hexagram' 'h'}
set(h,'LineStyle','none','Marker',varargin{1})
otherwise
error(['Invalid marker: ' varargin{1}])
end
elseif nargin > 5
set(h,varargin{:})
end
CURVE2TANGENT Calculate the tangent of a curve XY using a second-order and centered approximation scheme
Syntax: XYt = curve2tagent(XY)
XY is a Nx2 or Nx3 matrix where N is the number of points
XYt is a Nx2 or Nx3 matrix containing the coordinates of the tangent vector at each point
The tangent vector is computed using a centered difference method for interior points.
For the endpoints, forward and backward differences are used.
MS 3.0 | 2024-03-27 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev.
function XYt = curve2tangent(XY)
%CURVE2TANGENT Calculate the tangent of a curve XY using a second-order and centered approximation scheme
%
% Syntax: XYt = curve2tagent(XY)
% XY is a Nx2 or Nx3 matrix where N is the number of points
% XYt is a Nx2 or Nx3 matrix containing the coordinates of the tangent vector at each point
%
% The tangent vector is computed using a centered difference method for interior points.
% For the endpoints, forward and backward differences are used.
%
% MS 3.0 | 2024-03-27 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev.
% Determine the number of points and dimensionality
[N, dim] = size(XY);
% Initialize the output matrix
XYt = zeros(N, dim);
% Calculate tangents for interior points using centered differences
for i = 2:N-1
XYt(i, :) = (XY(i+1, :) - XY(i-1, :)) / 2;
end
% Calculate tangent for the first point using forward difference
XYt(1, :) = (XY(2, :) - XY(1, :));
% Calculate tangent for the last point using backward difference
XYt(N, :) = (XY(N, :) - XY(N-1, :));
% Normalize the tangent vectors
for i = 1:N
XYt(i, :) = XYt(i, :) / norm(XYt(i, :));
end
DEFGRAD calculates the solid deformation gradient using the displacement field u according to Eq. 17-24 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
Syntax:
out = defgradSPH(u,shapeSPHout) <--- syntax 1 (preferred)
out = defgradSPH(u,correctedgradW [,V, config, silent]) <--- syntax 2
Inputs: (syntax 1)
u : kxd displacement field of the kernel centers
shapeSPHout : ouput (structure) of shapeSPH
Inputs: (syntax 2)
u : kxd displacement field of the kernel centers
correctedgradW : 3xkxk corrected kernel gradient (reference frame), calculated with shapeSPH (3rd output)
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
forcesilent: flag to force silence mode (default = false)
config: structure with fields coding for Lamé parameters
lambda (default = 30 000 Pa)
mu (default = 3000 Pa)
Output: out a structure with fields:
F : k x d^2 deformation gradient
C : k x d^2 Cauchy-Green deformation tensor
E : k x d^2 Green-Lagrange strain tensor
S : k x d^2 Second Piola-Kirchoff stress tensor replacing Cauchy stress (Elastic stress = config.lambda*trace(E) + 2*config.mu*E)
P : k x d^2 First Piola-Kirchoff stress
f : k x d pairwise forces
G : k * d von Mises stress
description : tensor description
k,d,V,correctedgradW,config are also included
engine : 'defragSPH'
Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
See also
shapeSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
See also
function out = defgradSPH(u,correctedgradW,V,config,forcesilent)
%DEFGRAD calculates the solid deformation gradient using the displacement field u according to Eq. 17-24 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
%
% Syntax:
% out = defgradSPH(u,shapeSPHout) <--- syntax 1 (preferred)
% out = defgradSPH(u,correctedgradW [,V, config, silent]) <--- syntax 2
%
% Inputs: (syntax 1)
% u : kxd displacement field of the kernel centers
% shapeSPHout : ouput (structure) of shapeSPH
%
% Inputs: (syntax 2)
% u : kxd displacement field of the kernel centers
% correctedgradW : 3xkxk corrected kernel gradient (reference frame), calculated with shapeSPH (3rd output)
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
% forcesilent: flag to force silence mode (default = false)
% config: structure with fields coding for Lamé parameters
% lambda (default = 30 000 Pa)
% mu (default = 3000 Pa)
%
% Output: out a structure with fields:
% F : k x d^2 deformation gradient
% C : k x d^2 Cauchy-Green deformation tensor
% E : k x d^2 Green-Lagrange strain tensor
% S : k x d^2 Second Piola-Kirchoff stress tensor replacing Cauchy stress (Elastic stress = config.lambda*trace(E) + 2*config.mu*E)
% P : k x d^2 First Piola-Kirchoff stress
% f : k x d pairwise forces
% G : k * d von Mises stress
% description : tensor description
% k,d,V,correctedgradW,config are also included
% engine : 'defragSPH'
%
% Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
% ~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
%
%
% See also: shapeSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
%
% 2023-10-31 | INRAE\Olivier Vitrac | rev. 2023-11-01
%{
% Example:
r = 0.5;
X0 = packSPH(10,r);
X0(sqrt(sum((X0-mean(X0,1)).^2,2))>10*r,:) = [];
% deformation (vertical compression + shearing)
Xc = mean(X0,1); Xmin = min(X0,[],1); Xmax = max(X0,[],1)
% compression along z, with support at zmin, compression rate = 20%
X = X0; X(:,3) = 0.8*(X(:,3)-Xmin(1,3)) + Xmin(1,3);
% shearing 20% along y
X(:,2) = X(:,2) + 0.2 * (Xmax(1,2)-Xmin(1,2)) * (X(:,3)-Xmin(1,3))/(Xmax(1,3)-Xmin(1,3));
% displacement
u = X-X0;
% shape matrix
gradW = kernelSPH(2*r,'lucyder',3);
shapeout = shapeSPH(X0,gradW)
% calculates stresses and forces
defgradout = defgradSPH(u,shapeout)
% visualization
figure, hold on
scatter3(X(:,1),X(:,2),X(:,3),40,defgradout.G)
f= defgradout.f; fn=sqrt(sum(f.^2,2)); f = f./fn;
f90 = prctile(fn,90); fn(fn>f90) = f90; f = f .* f90;
quiver3(X(:,1),X(:,2),X(:,3),f(:,1),f(:,2),f(:,3))
%}
%Revision history
% 2023-10-31 alpha version
% 2023-11-01 collect all outputs and inputs into out, add f and G
% 2023-11-13 fixes, RC, full example
% Default Lamé parameters
config_default = struct(...
'lambda',3e4, ...first Lamé parameter
'mu',3e3 ... shear modulus (second Lamé parameterà
);
%% arg check
if nargin<2, error('2 arguments are required at least'), end
[k,d] = size(u); if k==0, error('please supply some displacements centers'), end
if isstruct(correctedgradW) && strcmpi(correctedgradW.engine,'shapeSPH') % -- syntax 1: we reuse the arguments of shapeSPH
args = correctedgradW;
if (args.d~=d) || (args.k~=k), error('%dx%d u is not compatible with previous %dx%d centers',k,d,args.k,args.d), end
correctedgradW = args.correctedgradW;
V = args.V;
config = args.config;
forcesilent = args.forcesilent;
if nargin>2, warning('extra arguments are ignored with syntax 1 (defragSPH)'); end
else % -- syntax 2
if nargin<3, V = []; end
if nargin<4, config = []; end
if nargin<5, forcesilent = []; end
if ~isnumeric(correctedgradW) || ndims(correctedgradW)~=3, error('correctedgradW should be evaluated with shape SPH (3rd output)'), end
if d>3, error('3 dimensions maximum'), end
[dW,k1W,k2W] = size(correctedgradW);
if k1W~=k2W, error('%dx%dx%d correctedgradW is not consistent, dim 2 and dim 3 should be equal',k1W,k2W); end
if dW~=d, error('%dx%dx%d correctedgradW has not the same number of dimensions (%d) than u (%d)',dW,k1W,k2W,dW,d); end
kv = length(V);
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
if isempty(forcesilent), forcesilent = false; end
if isempty(config), config = config_default; end
for f = fieldnames(config_default)'
if ~isfield(config,f{1}) || isempty(config.(f{1}))
config.(f{1}) = config_default.(f{1});
end
end
end
%% initialization
verbosity = (k>1e3) & ~forcesilent;
largek = k>200;
t0_ = clock; t1_=t0_; screen='';
% coding linearized outer product (https://en.wikipedia.org/wiki/Outer_product)
[left,right] = ndgrid(1:d,1:d);
outerproductindex = struct('u',left(:),'v',right(:)); % u and n are notations of wiki, not related to displacements
% vec2tensor: indices to convert a vector to a 2D tensor (faster than many reshapes)
vec2tensor_reshape = reshape(1:d^2,d,d);
vec2tensor = @(x) x(vec2tensor_reshape);
%% output F (deformation gradient)
% loop over all j for summation
I = eye(d,class(u));
F = repmat(I(:)',k,1); % we initialize to identity (see Eqs. 17 and 6 F = I + du/dX)
if verbosity, dispf('DEFRAGSPH calculates the deformation gradient for all %d kernels (K) in %d dimensions...',k,d), end
for j=1:k
% verbosity
if verbosity
if largek
t_ = clock; %#ok<*CLOCK>
if mod(j,10)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_;
dt_ = etime(t_,t0_); done_ = j/k;
screen = dispb(screen,'[K%d:%d] DEFRAGSPH | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
j,k,dt_,100*done_,(1/done_-1)*dt_);
end
else
dispf('... DEFRAGSPH - deformation gradient - respectively to kernel %d of %d',j,k);
end
end
% Deformation gradient for atom i due to j
uij = u(j,:)-u; % j - all i
for i=1:k
% Eq. 17, noticing that I is the initial value (no need to add it)
% u and v are left and right indices with repetitions
F(i,:) = F(i,:) + V(j) * uij(i,outerproductindex.u).*correctedgradW(outerproductindex.v,i,j)';
end
end
%% output C (Cauchy-Green deformation tensor)
C = zeros(size(F),class(u));
for i = 1:k
if verbosity, screen = dispb(screen,'[K%d:%d] DEFRAGSPH, Cauchy-Green tensor...',i,k); end
Fi = vec2tensor(F(i,:));
tmp = Fi'*Fi;
C(i,:) = tmp(:)';
end
%% output E (Green-Lagrange strain)
E = 0.5 * (C - repmat(I(:)',k,1)); % Eq. 18
%% output S (Cauchy stress tensor)
% trace operator is replaced by a sum along dim 2 for indices 1, d+2, 2*d+3
S = config.lambda * sum(E(:,1:d+1:end),2) + 2 * config.mu * E; %Eq. 19 config.lambda*tr(E)+2*config.mu*E
%% output P (first Piola-Kirchoff stress)
P = zeros(size(F),class(u));
for i = 1:k
if verbosity, screen = dispb(screen,'[K%d:%d] DEFRAGSPH, first Piola-Kirchoff stress tensor...',i,k); end
tmp = vec2tensor(F(i,:))*vec2tensor(S(i,:));
P(i,:) = tmp(:)';
end
%% output f (pairwise forces) - Eq. 24
% initialization
f = zeros(k,d,class(u));
% summation loop
for j=1:k
if verbosity, screen = dispb(screen,'[K%d:%d] DEFRAGSPH, summation pairwise forces...',i,k); end
Pj = vec2tensor(P(j,:));
% kernel loop
for i=1:k
Pi = vec2tensor(P(i,:));
f(i,:) = f(i,:) + ...: means x,y,z
( V(i)*V(j)*(Pi*correctedgradW(:,i,j)-Pj*correctedgradW(:,j,i)) )';
end
end
%% output G (von Mises stress)
if d==3
% https://en.wikipedia.org/wiki/Von_Mises_yield_criterion
% https://www.continuummechanics.org/vonmisesstress.html
[ij11,ij22,ij33,ij23,ij31,ij12] = deal(1,5,9,8,3,2); % indices 11,22,33,12,31,12 as (i,j)
G = sqrt(0.5*( ...
(S(:,ij11)-S(:,ij22)).^2+...
(S(:,ij22)-S(:,ij33)).^2+...
(S(:,ij33)-S(:,ij11)).^2+...
6 * (S(:,ij23).^2+S(:,ij31).^2+S(:,ij12).^2) ...
));
elseif d==2
% https://www.omnicalculator.com/physics/von-mises-stress
[ij11,ij22,ij12] = deal(1,4,2); % indices 11,22,12 as (i,j)
G = sqrt(...
S(:,ij11).^2 -...
S(:,ij11).*S(:,ij22) + ...
S(:,ij22).^2 +...
6 * S(:,ij12).^2 ...
);
end
%% verbosity
if verbosity
dispb(screen,'DEFRAGSPH %d L matrix calculated in %0.4g s',k,etime(clock,t0_));
end
% collect all outputs
description = struct(...
'F','deformation gradient',...
'C','Cauchy-Green deformation tensor',...
'E','Green-Lagrange strain',...
'S','Cauchy stress tensor',...
'P','first Piola-Kirchoff stress', ...
'f','pairwise forces, so-called nodal forces', ...
'G','von Mises stress' ...
);
out = struct( ...
'k',k,'d',d,'V',V,'correctedgradW',correctedgradW,'config',config,...
'F',F,'C',C,'E',E,'S',S,'P',P,'f',f,'G',G, ...
'description',description,'engine','defragSPH','forcesilent',forcesilent);
DISPB wrapper of disp with linefeed
syntax: screenline=dispb(screenline,'string with codes %s %d',value1,value2,...)
see help on SPRINTF
function update=dispb(old,varargin)
%DISPB wrapper of disp with linefeed
% syntax: screenline=dispb(screenline,'string with codes %s %d',value1,value2,...)
% see help on SPRINTF
% MS 2.1 - 22/03/09 - INRA\Olivier Vitrac rev.
% Revision history
update = sprintf(varargin{:});
if any(old)
varargin{1} = [repmat('\b',1,length(old)+1) varargin{1}];
end
dispf(varargin{:})
DISPF fast wrapper of disp(sprintf(...))
see help on SPRINTF
see also FPRINTF (the main difference is that LF is used after disp)
function out = dispf(varargin)
%DISPF fast wrapper of disp(sprintf(...))
% see help on SPRINTF
% see also FPRINTF (the main difference is that LF is used after disp)
% MS 2.1 - 16/03/08 - INRA\Olivier Vitrac rev. 14/09/19
% Revision history
% 29/12/12 updated help
% 19/08/18 implement varargin{1} as a cell
% 14/09/19 add output for vectorization
if iscell(varargin{1})
n = 0;
for i=1:numel(varargin{1})
n = n+dispf(varargin{1}{i});
end
else
txt = sprintf(varargin{:});
n = length(txt);
disp(txt)
end
if nargout, out = n; end
WORKSHOP built on Billy production file (suspended particle in a fluid)
This example takes the benefit of a Lagrangian description
% WORKSHOP built on Billy production file (suspended particle in a fluid)
% This example takes the benefit of a Lagrangian description
% INRAE\Olivier Vitrac, Han Chen - rev. 2023-08-25
% MATLAB FILES included in this distribution (either from INRAE/MS or Pizza3 project)
% Main file
% ├── example1.m
% Main features demonstrated
% ├── lamdumpread2.m ==> the Swiss knife for the manipulating HUGE dump files (version 2 as it is the fork for Pizza3)
% ├── buildVerletList.m --> the basic tool for statistical physics, it implement an efficient grid search method
% Other dependencies and future workshop extensions (from Pizza3)
% ├── checkfiles.m
% ├── forceHertzAB.m
% ├── forceHertz.m
% ├── forceLandshoff.m
% ├── interp2SPH.m
% ├── interp3SPH.m
% ├── interp3SPHVerlet.m
% ├── kernelSPH.m
% ├── packing.m
% ├── packing_WJbranch.m
% ├── packSPH.m
% ├── partitionVerletList.m
% ├── selfVerletList.m
% ├── updateVerletList.m
% Advanced scripts written for INRAE\William Jenkinson
% ├── KE_t.m
% ├── particle_flux.m
% └── wallstress.m
% Dependencies from MS (INRAE/Molecular Studio)
% ├── color_line3.m
% ├── dispb.m
% ├── dispf.m
% ├── explore.m
% ├── fileinfo.m
% ├── lastdir.m
% ├── MDunidrnd.m
% ├── plot3D.m
% ├── rootdir.m
% DUMP FILES included in this workshop
% ├── dumps
% │ └── hertz
% │ ├── dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle <== it is the original dump file
% │ └── PREFETCH_dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle <-- the split folder
% │ ├── TIMESTEP_000000000.mat <-- a split file
% │ ├── TIMESTEP_000050000.mat <-- 158 splits (frames)
% │ ├── TIMESTEP_000100000.mat <-- the value represent time
% ...
%% STEP 1 - PREPROCESSING
% preprocess all the dumps (no need to precise the filenames, only the pattern)
% lamdumpread2 include two PREPROCESSORS 'prefetch' and 'split'
% prefetch should be preferred for 2D files (relatively smaller number of particles and many time frames)
% Usage: lamdumpread2('dump.*','prefetch');
% split should be preferred for large 3D files (large number of particles and a relatively smaller number of time frames)
% Usage: lamdumpread2('dump.*','split');
%
% note: this step should be used ONLY once, applying again will overwrite the previous splits (frames)
PREPROCESS_FLAG = false; % set it to true to preprocess your data
if PREPROCESS_FLAG
datafolder = './dumps/';
lamdumpread2(fullfile(datafolder,'dump.*'),'split'); % for large 3D
end
%% STEP 2 - PROCESS SPECIFICALLY ONE FILE
% we do work with one dump file
datafolder = './dumps/';
dumpfile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle';
datafolder = lamdumpread2(fullfile(datafolder,dumpfile),'search'); % fix datafolder based on initial guess
defaultfiles = lamdumpread2(fullfile(datafolder,dumpfile),'default'); % default folder (just for check)
%% Extract the types of atoms and the list of available frames
X0 = lamdumpread2(fullfile(datafolder,dumpfile)); % default frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
%% Extract the middle frame (i.e. in the middle of the simulation duration)
% X0 is too far from steady state for advanced analysis
Xmiddle = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps(ceil(ntimesteps/2))); % middle frame
%% Extract the number of beads for each type
% guess the bead type for the fluid (the most populated)
% same guess for the particle (the less populated)
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,fluidtype] = max(natomspertype);
[~,solidtype] = min(natomspertype);
walltypes = setdiff(atomtypes,[fluidtype,solidtype]);
%% Estimate the fluid bead size
% This step uses for more accuracy the buildVerletList()
fluidxyz = X0.ATOMS{T==fluidtype,{'x','y','z'}};
fluidid = X0.ATOMS{T==fluidtype,'id'};
nfluidatoms = length(fluidid);
nsolidatoms = natomspertype(solidtype);
% first estimate assuming that the bead is a cube
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz,cutoff);
rbead = dmin/2;
%% find the direction of the flow (largest dimension)
[~,iflow] = max(boxdims);
iothers = setdiff(1:size(X0.BOX,1),iflow);
%% separate top and bottom walls
vel = {'vx','vy','vz'};
wall1vel = Xmiddle.ATOMS{Xmiddle.ATOMS.type==walltypes(1),vel{iflow}}; wall1vel = wall1vel(1);
wall2vel = Xmiddle.ATOMS{Xmiddle.ATOMS.type==walltypes(2),vel{iflow}}; wall2vel = wall2vel(1);
[wallvel,iwall] = sort([wall1vel,wall2vel],'descend'); % 1 is top (>0), 2 is bottom;
walltypes = walltypes(iwall);
%% find the position of the particle (i.e., obstacle) placed in the flow (from the middle frame)
solidxyz = Xmiddle.ATOMS{T==solidtype,{'x','y','z'}};
solidid = Xmiddle.ATOMS{T==solidtype,'id'};
solidbox = [min(solidxyz);max(solidxyz)]';
%% Pick n particles randomly from the left inlet and included in the mask of the solid (initial frame)
n = 300;
tol = 0.5; % add 40% particles around
selectionbox = NaN(3,2);
selectionbox(iflow,:) = [X0.BOX(iflow,1), X0.BOX(iflow,1)+2*rbead];
selectionbox(iothers,:) = (1+tol)*(solidbox(iothers,2)-solidbox(iothers,1))*[-1 1]/2 ...
+ (solidbox(iothers,1)+solidbox(iothers,2)) * [1 1]/2;
ok = true(nfluidatoms,1);
for c = 1:3
ok = ok & (fluidxyz(:,c)>=selectionbox(c,1)) & (fluidxyz(:,c)<=selectionbox(c,2));
end
icandidates = find(ok);
iselected = icandidates(MDunidrnd(length(icandidates),n));
selectedid = fluidid(iselected); % ID to be used
% plot selected particles and other ones
figure, hold on
plot3D(fluidxyz,'b.')
plot3D(fluidxyz(iselected,:),'ro','markerfacecolor','r')
plot3D(solidxyz,'ks','markerfacecolor','k')
view(3), axis equal
%% Generate the trajectory for the selected particles (for all frames)
% All data are loaded with Lamdumpread2() with a single call using 'usesplit'
% each split is loaded individually and only atoms matching selectedid are stored
% all atoms are sorted as selectedid (no need to sort them)
Xselection = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps,selectedid);
%% Collect the trajectory of the solid particle for all frames
Xsolid = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps,solidid);
%% Store the trajectories stored in a ntimesteps x 3 x n matrix
% missing data will
[seltraj,selveloc] = deal(NaN(ntimesteps,3,n,'single'));
solidtraj = NaN(ntimesteps,3,nsolidatoms,'double');
goodframes = true(ntimesteps,1);
for it = 1:ntimesteps
% read fluid atoms
selframe = Xselection.ATOMS(Xselection.ATOMS{:,'TIMESTEP'} == timesteps(it),:);
nfoundatoms = size(selframe,1);
if nfoundatoms% incomplete dumped frame (it may happen in LAMMPS)
dispf('incomplete frame %d/%d (t=%0.4g): %d of %d fluid atoms missing',it,ntimesteps,timesteps(it),n-nfoundatoms,n)
[~,iatoms,jatoms] = intersect(selectedid,selframe.id,'stable');
seltraj(it,:,iatoms) = permute(selframe{jatoms,{'x','y','z'}},[3 2 1]);
goodframes(it) = false;
else
seltraj(it,:,:) = permute(selframe{:,{'x','y','z'}},[3 2 1]);
selveloc(it,:,:) = permute(selframe{:,{'vx','vy','vz'}},[3 2 1]);
end
% read solid atoms
solidframe = Xsolid.ATOMS(Xsolid.ATOMS{:,'TIMESTEP'} == timesteps(it),:);
nfoundatoms = size(solidframe,1);
if nfoundatoms% incomplete dumped frame (it may happen in LAMMPS)
dispf('incomplete frame %d/%d (t=%0.4g): %d of %d solid atoms missing',it,ntimesteps,timesteps(it),nsolidatoms-nfoundatoms,nsolidatoms)
[~,iatoms,jatoms] = intersect(solidid,solidframe.id,'stable');
solidtraj(it,:,iatoms) = permute(solidframe{jatoms,{'x','y','z'}},[3 2 1]);
goodframes(it) = false;
else
solidtraj(it,:,:) = permute(solidframe{:,{'x','y','z'}},[3 2 1]);
end
end
% velocity magnitude for the fluid particles
selveloc_magnitude = squeeze(sqrt(sum(selveloc.^2, 2)));
% Interpertation of the solid deformation via Ixx, Iyy, Izz and D
centeredsolidtraj = solidtraj - repmat(nanmean(solidtraj,3),1,1,nsolidatoms);
%% Calculate the approximate major and minor axes (assuming an ellipsoidal shape)
% Building the inertia tensor
% Initialize moments of inertia
Ixx = zeros(ntimesteps, 1);
Iyy = zeros(ntimesteps, 1);
Izz = zeros(ntimesteps, 1);
for it = 1:ntimesteps
% Extract the 3 x natoms matrix for the current timestep
coordinates = squeeze(centeredsolidtraj(it, :, :))';
% Compute the covariance matrix
C = coordinates' * coordinates / nsolidatoms;
% Perform Singular Value Decomposition
[U, S, ~] = svd(C);
% The principal moments of inertia are on the diagonal of S
Ixx(it) = S(1, 1);
Iyy(it) = S(2, 2);
Izz(it) = S(3, 3);
end
% Calculate the approximate major and minor axes (assuming an ellipsoidal shape)
L = sqrt(5 * (Ixx + Iyy - Izz) / 2); % Major axis (approximation)
B = sqrt(5 * (Ixx - Iyy + Izz) / 2); % Minor axis (approximation)
% Calculate the Taylor deformation
D = (L - B) ./ (L + B);
figure;
plot(timesteps, D,'-','linewidth',2);
xlabel('Time'); ylabel('D: Taylor deformation');
title({'\rm{' strrep(dumpfile, '_', '\_') '}:' '\bf{Taylor Deformation Over Times}'}, 'Interpreter', 'tex')
%% Plot the trajectories for the selected particles
% streamlines with color representing velocity magnitude
figure, hold on
col = parula(n);
for i=1:n
jumps = [1;ntimesteps+1];
for d=1:3
jumps = unique([jumps;find(abs(diff(seltraj(:,d,i)))>boxdims(d)/2)+1]);
end
for j=1:length(jumps)-1
u = jumps(j):jumps(j+1)-1;
streamline = seltraj(u, :, i);
color_line3(streamline(:, 1), streamline(:, 2), streamline(:, 3), selveloc_magnitude(u, i), 'linewidth', 3);
% faster method but without streamline
%plot3(traj(u,1,i),traj(u,2,i),traj(u,3,i),'-','linewidth',3,'color',col(i,:))
end
end
plot3D(solidxyz,'ko','markerfacecolor','k','markersize',5)
view(3), axis equal
title({'\rm{' strrep(dumpfile, '_', '\_') '}:' '\bf{Velocity of Selected Particles Along Streamlines}'}, 'Interpreter', 'tex')
xlabel('x (m)'), ylabel('Y (m)'), zlabel('z (m)')
hc = colorbar; hc.Label.String = 'velocity (m/s)';
Workshop Main File - Part 2
This script demonstrates the data preprocessing, analysis, and visualization techniques
using 'lamdumpread2', a custom function for reading LAMMPS dump files.
%% Workshop Main File - Part 2
% This script demonstrates the data preprocessing, analysis, and visualization techniques
% using 'lamdumpread2', a custom function for reading LAMMPS dump files.
% File Structure (change your local path to reflect the content)
% ├── example2.m
% └── ...
% └── data folder (dumps/pub1/)
%INRAE\Olivier Vitrac, Han Chen (rev. 2023-08-30,2023-08-31)
% Revision history
% 2023-09-04,05 implements grid interpolation
%% Initialization and Preprocessing
% -------------------------------------------------
% Turn preprocessing on or off based on `PREPROCESS_FLAG`.
% 'lamdumpread2' splits large 3D dump files if the flag is true.
statvec = @(f,before,after) dispf('%s: %s%d values | average = %0.5g %s', before, ...
cell2mat(cellfun(@(x) sprintf(' %0.1f%%> %10.4g | ', x, prctile(f, x)), {2.5, 25, 50, 75, 97.5}, 'UniformOutput', false)), ...
length(f), mean(f),after); % user function to display statistics on vectors (usage: statvec(f,'myvar','ok'))
PREPROCESS_FLAG = false;
if PREPROCESS_FLAG
datafolder = './dumps/pub1/';
lamdumpread2(fullfile(datafolder,'dump.*'),'split'); % for large 3D
end
%% File-specific Processing (same file as in Example1.m but larger)
% -----------------------------------------------------------------
% A specific dump file is selected and pre-processed to extract data and
% save to default folders for further operations.
datafolder = './dumps/pub1/';
dumpfile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle';
datafolder = lamdumpread2(fullfile(datafolder,dumpfile),'search'); % fix datafolder based on initial guess
defaultfiles = lamdumpread2(fullfile(datafolder,dumpfile),'default'); % default folder (just for check)
%% Data Extraction
% -------------------------------------------------
% Extract the types of atoms and the list of available frames
X0 = lamdumpread2(fullfile(datafolder,dumpfile)); % default frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
% Extract the middle frame (i.e. in the middle of the simulation duration)
% X0 is too far from steady state for advanced analysis
Xmiddle = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps(ceil(ntimesteps/2))); % middle frame
% Extract the number of beads for each type
% guess the bead type for the fluid (the most populated)
% same guess for the particle (the less populated)
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,fluidtype] = max(natomspertype);
[~,solidtype] = min(natomspertype);
walltypes = setdiff(atomtypes,[fluidtype,solidtype]);
% Estimate the fluid bead size
% This step uses for more accuracy the buildVerletList()
fluidxyz = X0.ATOMS{T==fluidtype,{'x','y','z'}};
fluidid = X0.ATOMS{T==fluidtype,'id'};
nfluidatoms = length(fluidid);
nsolidatoms = natomspertype(solidtype);
% first estimate assuming that the bead is a cube
boxdims = X0.BOX(:,2) - X0.BOX(:,1);
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,cutoff,dmin,config,dist] = buildVerletList(fluidxyz,cutoff);
rbead = dmin/2;
% find the direction of the flow (largest dimension)
[~,iflow] = max(boxdims);
iothers = setdiff(1:size(X0.BOX,1),iflow);
% separate top and bottom walls
vel = {'vx','vy','vz'};
wall1vel = Xmiddle.ATOMS{Xmiddle.ATOMS.type==walltypes(1),vel{iflow}}; wall1vel = wall1vel(1);
wall2vel = Xmiddle.ATOMS{Xmiddle.ATOMS.type==walltypes(2),vel{iflow}}; wall2vel = wall2vel(1);
[wallvel,iwall] = sort([wall1vel,wall2vel],'descend'); % 1 is top (>0), 2 is bottom;
walltypes = walltypes(iwall);
% find the position of the particle (i.e., obstacle) placed in the flow (from the middle frame)
solidxyz = Xmiddle.ATOMS{T==solidtype,{'x','y','z'}};
solidid = Xmiddle.ATOMS{T==solidtype,'id'};
solidbox = [min(solidxyz);max(solidxyz)]';
%% Data Preprocessing for Selected Particles
% -------------------------------------------------
% This part is a repetition of the content of example1. It generate the streamlines for particles
% at the left. It requires 5 min and it can be skipped as it not used for stresses.
% note: the duration of the simulation enables the particles in the center (slowest part) to
% cross only half of the cell.
PlotTrajectory = false; % set it to true to execute this section
if PlotTrajectory
% Pick n particles randomly from the left inlet and included in the mask of the solid (initial frame)
n = 300;
tol = 0.5; % add 40% particles around
selectionbox = NaN(3,2);
selectionbox(iflow,:) = [X0.BOX(iflow,1), X0.BOX(iflow,1)+2*rbead];
selectionbox(iothers,:) = (1+tol)*(solidbox(iothers,2)-solidbox(iothers,1))*[-1 1]/2 ...
+ (solidbox(iothers,1)+solidbox(iothers,2)) * [1 1]/2;
ok = true(nfluidatoms,1);
for c = 1:3
ok = ok & (fluidxyz(:,c)>=selectionbox(c,1)) & (fluidxyz(:,c)<=selectionbox(c,2));
end
icandidates = find(ok);
iselected = icandidates(MDunidrnd(length(icandidates),n));
selectedid = fluidid(iselected); % ID to be used
% plot selected particles and other ones
figure, hold on
plot3D(fluidxyz,'b.')
plot3D(fluidxyz(iselected,:),'ro','markerfacecolor','r')
plot3D(solidxyz,'ks','markerfacecolor','k')
view(3), axis equal
% Generate the trajectory for the selected particles (for all frames)
% All data are loaded with Lamdumpread2() with a single call using 'usesplit'
% each split is loaded individually and only atoms matching selectedid are stored
% all atoms are sorted as selectedid (no need to sort them)
Xselection = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps,selectedid);
% Collect the trajectory of the solid particle for all frames
Xsolid = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timesteps,solidid);
% Store the trajectories stored in a ntimesteps x 3 x n matrix
% missing data will
[seltraj,selveloc] = deal(NaN(ntimesteps,3,n,'single'));
solidtraj = NaN(ntimesteps,3,nsolidatoms,'double');
goodframes = true(ntimesteps,1);
for it = 1:ntimesteps
% read fluid atoms
selframe = Xselection.ATOMS(Xselection.ATOMS{:,'TIMESTEP'} == timesteps(it),:);
nfoundatoms = size(selframe,1);
if nfoundatoms% incomplete dumped frame (it may happen in LAMMPS)
dispf('incomplete frame %d/%d (t=%0.4g): %d of %d fluid atoms missing',it,ntimesteps,timesteps(it),n-nfoundatoms,n)
[~,iatoms,jatoms] = intersect(selectedid,selframe.id,'stable');
seltraj(it,:,iatoms) = permute(selframe{jatoms,{'x','y','z'}},[3 2 1]);
goodframes(it) = false;
else
seltraj(it,:,:) = permute(selframe{:,{'x','y','z'}},[3 2 1]);
selveloc(it,:,:) = permute(selframe{:,{'vx','vy','vz'}},[3 2 1]);
end
% read solid atoms
solidframe = Xsolid.ATOMS(Xsolid.ATOMS{:,'TIMESTEP'} == timesteps(it),:);
nfoundatoms = size(solidframe,1);
if nfoundatoms% incomplete dumped frame (it may happen in LAMMPS)
dispf('incomplete frame %d/%d (t=%0.4g): %d of %d solid atoms missing',it,ntimesteps,timesteps(it),nsolidatoms-nfoundatoms,nsolidatoms)
[~,iatoms,jatoms] = intersect(solidid,solidframe.id,'stable');
solidtraj(it,:,iatoms) = permute(solidframe{jatoms,{'x','y','z'}},[3 2 1]);
goodframes(it) = false;
else
solidtraj(it,:,:) = permute(solidframe{:,{'x','y','z'}},[3 2 1]);
end
end
% velocity magnitude for the fluid particles
selveloc_magnitude = squeeze(sqrt(sum(selveloc.^2, 2)));
% Trajectory Visualization
% -------------------------------------------------
% streamlines with color representing velocity magnitude
figure, hold on
col = parula(n);
for i=1:n
jumps = [1;ntimesteps+1];
for d=1:3
jumps = unique([jumps;find(abs(diff(seltraj(:,d,i)))>boxdims(d)/2)+1]);
end
for j=1:length(jumps)-1
u = jumps(j):jumps(j+1)-1;
streamline = seltraj(u, :, i);
color_line3(streamline(:, 1), streamline(:, 2), streamline(:, 3), selveloc_magnitude(u, i), 'linewidth', 3);
% faster method but without streamline
%plot3(traj(u,1,i),traj(u,2,i),traj(u,3,i),'-','linewidth',3,'color',col(i,:))
end
end
plot3D(solidxyz,'ko','markerfacecolor','k','markersize',5)
view(3), axis equal
title({'\rm{' strrep(dumpfile, '_', '\_') '}:' '\bf{Velocity of Selected Particles Along Streamlines}'}, 'Interpreter', 'tex')
xlabel('x (m)'), ylabel('Y (m)'), zlabel('z (m)')
hc = colorbar; hc.Label.String = 'velocity (m/s)';
end % if PlotTrajectory
%% Stress Analysis in Reference Frame (PART 2)
% -------------------------------------------------
% This part focuses on evaluating the mechanical interactions and stress in a particular
% frame. Specifically, we employ a late-stage frame (closer to steady-state conditions) to
% derive insights into the types of contacts between fluid and solid atoms.
%
% * `timestepforstress`: Time step selected for stress analysis.
% * `Xstress`: Data structure containing all relevant atomic information at the chosen time step.
% * `verletListCross`: A specialized Verlet list that segregates contacts based on atom types.
% * `isincontact`, `isincontactwithsolid`, `isincontactwithfluid`: Boolean fields that signify
% whether an atom is in contact, and with which type of atom.
%
% Here we rely heavily on a short-cut-off Verlet list for efficiency, separating cross-terms
% and characterizing specific types of atomic interactions.
% General container for storing data for different timesteps
postdata = repmat(struct('type','Landshoff|Hertz','timestep',NaN,'Xfluid',[],'Xsolid',[],'force',[]),0,0);
% Extract the Specific Frame for Stress Interpretation
% Selects a time step that is presumably closer to the steady-state to perform stress analysis.
list_timestepforstess = unique(timesteps(ceil((0.1:0.1:0.9)*ntimesteps)));
nlist_timestepforstess = length(list_timestepforstess);
consideredtimesteps = 1:nlist_timestepforstess;
consideredtimesteps = nlist_timestepforstess;
% === Loop on all selected time steps ===
altxt = {'no plot','plot'};
DOINTENSIVECALC = true; % set it to false, to prevent intensive calculations
for i_timestepforstess = consideredtimesteps
% Current Frame
timestepforstress = list_timestepforstess(i_timestepforstess);
Xstress = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timestepforstress); % middle frame
doplot = ismember(timestepforstress,list_timestepforstess([1 end]));
dispf('\n%s-\n[%d/%d] TIMESTEP = %d (%s)',repmat('-*',1,40),i_timestepforstess,nlist_timestepforstess,timestepforstress,altxt{doplot+1})
% Categorization of Atom Types
% Identifies the most and least populous atom types to discern between fluid and solid types.
T = Xstress.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,fluidtype] = max(natomspertype);
[~,solidtype] = min(natomspertype);
walltypes = setdiff(atomtypes,[fluidtype,solidtype]);
% Boolean Classification of Atom Types
Xstress.ATOMS.isfluid = Xstress.ATOMS.type==fluidtype;
Xstress.ATOMS.issolid = Xstress.ATOMS.type==solidtype;
% Verlet List Construction with Short Cutoff
% Builds a Verlet list with a short cutoff distance, designed to identify only the closest neighbors.
[verletList,cutoff,dmin,config,dist] = buildVerletList(Xstress.ATOMS,3*rbead);
% Partition Verlet List Based on Atom Types
% This Verlet list is partitioned based on atom types, distinguishing between interactions
% that are exclusively fluid-fluid, solid-fluid, or solid-solid.
verletListCross = partitionVerletList(verletList,Xstress.ATOMS);
% Identify Contacting Atoms
% The atoms that are in contact with each other are identified.
Xstress.ATOMS.isincontact = ~cellfun(@isempty,verletListCross);
Xstress.ATOMS.contacttypes = cellfun(@(v) Xstress.ATOMS.type(v)',verletListCross,'UniformOutput',false);
% Identify Atoms in Contact with Solids and Fluids
% Further filters the atoms in contact to identify which are in contact with solids and which with fluids.
Xstress.ATOMS.isincontactwithsolid = cellfun(@(c) ismember(solidtype,c), Xstress.ATOMS.contacttypes);
Xstress.ATOMS.isincontactwithfluid = cellfun(@(c) ismember(fluidtype,c), Xstress.ATOMS.contacttypes);
% Flag Fluid Atoms in Contact with Solid and Vice Versa
% Identifies fluid atoms that are in contact with solid atoms and solid atoms in contact with fluid atoms.
Xstress.ATOMS.fluidincontactwithsolid = Xstress.ATOMS.isfluid & Xstress.ATOMS.isincontactwithsolid;
Xstress.ATOMS.solidincontactwithfluid = Xstress.ATOMS.issolid & Xstress.ATOMS.isincontactwithfluid;
% Identify Indices for Analysis
% Indices of fluid and solid atoms that are in contact with each other.
ifluidcontact = find(Xstress.ATOMS.fluidincontactwithsolid);
isolidcontact = find(Xstress.ATOMS.solidincontactwithfluid);
% Extend Fluid Atoms Set with Neighbors (Not Directly in Contact)
% Extends the set of fluid atoms under consideration to include their closest neighbors.
ifluidcontact_withneighbors = unique(union(ifluidcontact,cat(2,verletList{ifluidcontact}))); % include neighbors
ifluidcontact_withneighbors = ifluidcontact_withneighbors(T(ifluidcontact_withneighbors)==fluidtype); % filter, keep only fluids
[~,~,ind_withoutneighbors] = intersect(ifluidcontact,ifluidcontact_withneighbors,'stable');
notneighboringcontacts = setdiff(ifluidcontact_withneighbors,ifluidcontact_withneighbors(ind_withoutneighbors));
% Control Visualization for some Frames (default behavior: first and last)
% filled red: solid "atoms" in contact with the fluid
% filled blue: fluid "atoms" in contact with the solid (crown/shell)
% empty blue: fluid "atoms" neighbor of previous ones but not contact
% empty blue are used to calculate Landshoff forces at the position of filled ones
if doplot
figure, hold on
plot3D(Xstress.ATOMS{ifluidcontact,{'x','y','z'}},'bo','markerfacecolor','b')
plot3D(Xstress.ATOMS{notneighboringcontacts,{'x','y','z'}},'bo','markerfacecolor','w')
plot3D(Xstress.ATOMS{isolidcontact,{'x','y','z'}},'ro','markersize',12,'markerfacecolor','r')
axis equal, view(3), drawnow
dispf('\tnumber of fluid atoms in contact with solid %d', length(ifluidcontact))
dispf('\tnumber of solid atoms in contact with fluid %d (neighbors %d)', length(isolidcontact),length(notneighboringcontacts))
end
%% === 3D view of the fluid velocity around the solid ===
% Definition of the Viewbox (for all grid-based visualizations)
coords = {'x','y','z'}; % Cartesian coordinates
vcoords = cellfun(@(c) ['v',c],coords,'UniformOutput',false); % vx, vy, vz
% Extract the box around solid atoms and include fluid ones
fluidbox = [ min(Xstress.ATOMS{Xstress.ATOMS.isfluid,coords})
max(Xstress.ATOMS{Xstress.ATOMS.isfluid,coords}) ]';
solidbox = [ min(Xstress.ATOMS{Xstress.ATOMS.issolid,coords})
max(Xstress.ATOMS{Xstress.ATOMS.issolid,coords}) ]';
[~,iflow] = max(max(Xstress.ATOMS{Xstress.ATOMS.isfluid,vcoords})); % direction of the flow
viewbox = fluidbox; viewbox(iflow,:) = solidbox(iflow,:);
if DOINTENSIVECALC % THIS SECTION IS VERY INTENSIVE (more than 5 mins)
% 3D Cartesian Grid from the widow xw, yw, zw
nresolution = [300 300 30]; resolutionmax = max(nresolution);
for icoord = 1:3
viewbox(icoord,:) = mean(viewbox(icoord,:)) + [-1.2 1.2]*diff(viewbox(icoord,:))/2*nresolution(icoord)/resolutionmax;
viewbox(icoord,1) = max(viewbox(icoord,1),fluidbox(icoord,1));
viewbox(icoord,2) = min(viewbox(icoord,2),fluidbox(icoord,2));
end
hLandshoff = 5*rbead; %1.25e-5; % m
xw = linspace(viewbox(1,1),viewbox(1,2),nresolution(1));
yw = linspace(viewbox(2,1),viewbox(2,2),nresolution(2));
zw = linspace(viewbox(3,1),viewbox(3,2),nresolution(3));
insidewindow = Xstress.ATOMS.isfluid;
for icoord = 1:3
insidewindow = insidewindow ...
& Xstress.ATOMS{:,coords{icoord}}>=viewbox(icoord,1) ...
& Xstress.ATOMS{:,coords{icoord}}<=viewbox(icoord,2);
end
dispf('%d fluid atoms have be found in the cross section around the solid',length(find(insidewindow)));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
% Verlet Grid List: lists the beads close to a grid node/vertex
XYZ = Xstress.ATOMS{insidewindow,coords}; % kernel centers
VXYZ = buildVerletList({XYZgrid XYZ},1.2*hLandshoff); % special grid syntax
% Interpolation of all velocity components (vx, vy, vz)
% v3XYZgrid is the interpolated vectorial field
% vxXYZgrid, vyXYZgrid, vzXYZgrid are the components
% vXYZgrid is the magnitude
% note: For interpolating the density only: interpolates ones(size(XYZ,1),1,'single')
W = kernelSPH(hLandshoff,'lucy',3); % kernel expression
vXYZ = Xstress.ATOMS{insidewindow,vcoords}; % kernel centers
vXYZmag = sqrt(sum(vXYZ.^2,2)); % velocity magnitude
mbead = 9.04e-12;
Vbead = mbead/1000;
v3XYZgrid = interp3SPHVerlet(XYZ,vXYZ,XYZgrid,VXYZ,W,Vbead);
vxXYZgrid = reshape(v3XYZgrid(:,1),size(Xw)); vxXYZgrid(isnan(vxXYZgrid)) = 0;
vyXYZgrid = reshape(v3XYZgrid(:,2),size(Xw)); vyXYZgrid(isnan(vyXYZgrid)) = 0;
vzXYZgrid = reshape(v3XYZgrid(:,3),size(Xw)); vzXYZgrid(isnan(vzXYZgrid)) = 0;
vXYZgrid = reshape(sqrt(sum(v3XYZgrid.^2,2)),size(Xw));
% 3D Figure (for control only, we plot one iso-velocity surface)
% we add the solid phase as a meshed solid
figure, hold on
visocontour = max(vXYZmag)/10;
isosurface(Xw,Yw,Zw,vXYZgrid,visocontour)
DT = delaunayTriangulation(double(Xstress.ATOMS{isolidcontact,{'x','y','z'}}));
K = convexHull(DT);
trisurf(K, DT.Points(:,1), DT.Points(:,2), DT.Points(:,3), 'FaceColor', 'w','Edgecolor','k','FaceAlpha',0.6);
lighting gouraud, camlight left, axis equal, view(3) % shading interp
%% cuts and slices (caps are put on the smallest contour)
figure, hold on
vXYZcut = vXYZgrid;
boxcenter = (fluidbox(:,1)+fluidbox(:,2))/2; % boxcenter = (solidbox(:,1)+solidbox(:,2))/2;
[Xwcut,Ywcut,Zwcut] = deal(Xw,Yw,Zw);
vXYZcut(:,yw>boxcenter(2),:) = [];
Xwcut(:,yw>boxcenter(2),:) = [];
Ywcut(:,yw>boxcenter(2),:) = [];
Zwcut(:,yw>boxcenter(2),:) = [];
% vXYZcut(:,:,zw>boxcenter(3),:) = [];
% Xwcut(:,:,zw>boxcenter(3),:) = [];
% Ywcut(:,:,zw>boxcenter(3),:) = [];
% Zwcut(:,:,zw>boxcenter(3),:) = [];
% iso-surface val
colors = parula(256);
visomax = max(vXYZmag);
visocontour = visomax * [1/20,1/10,1/5, 1/4, 1/3, 1/2];
visocolors = interp1(linspace(0,visomax,size(colors,1)),colors,visocontour);
for icontour = 1:length(visocontour)
patch(isosurface(Xwcut,Ywcut,Zwcut,vXYZcut, visocontour(icontour)),...
'FaceColor',visocolors(icontour,:),'EdgeColor','none','FaceAlpha',0.6);
end
p2 = patch(isocaps(Xwcut,Ywcut,Zwcut,vXYZcut, visocontour(1)),'FaceColor','interp','EdgeColor','none','FaceAlpha',0.6);
colormap(colors), camlight('right'), lighting gouraud, view(3), axis equal
trisurf(K, DT.Points(:,1), DT.Points(:,2), DT.Points(:,3), 'FaceColor', 'w','Edgecolor','k','FaceAlpha',0.6);
hs= slice(Xw,Yw,Zw,vXYZgrid,boxcenter(1), ...
[solidbox(2,1) boxcenter(2) solidbox(2,2)], ...
[viewbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.5)
view(115,34)
% add quiver plot
quiver3(Xw(1:20:end,1:20:end,1:3:end),Yw(1:20:end,1:20:end,1:3:end),Zw(1:20:end,1:20:end,1:3:end), ...
vxXYZgrid(1:20:end,1:20:end,1:3:end),vyXYZgrid(1:20:end,1:20:end,1:3:end),vzXYZgrid(1:20:end,1:20:end,1:3:end), ...
'color','k','LineWidth',1)
% add streamlines (note that all arguments required to be double)
[startX,startY,startZ] = meshgrid(double(xw(1)),double(yw(10:10:end-10)),double(zw(1:2:end)));
vstart = interp3(Xw,Yw,Zw,vxXYZgrid,startX,startY,startZ); startX(vstart<0) = double(xw(end));
hsl = streamline(double(Xw),double(Yw),double(Zw),vxXYZgrid,vyXYZgrid,vzXYZgrid,startX,startY,startZ);
set(hsl,'linewidth',2,'color',[0.4375 0.5000 0.5625])
plot3(startX(:),startY(:),startZ(:),'ro','markerfacecolor',[0.4375 0.5000 0.5625])
end
%% Landshoff forces in the fluid
%
% This section is dedicated to the calculation of Landshoff forces in the fluid medium.
% The computation is confined to a shell or "crown" around the solid particles.
% Here, we build a new Verlet list specific to the fluid particles around solid structures.
%
% * `Xfluidcontact_withneighbors`: Atomic information specifically for the fluid atoms in the vicinity of the solid.
% * `Vfluidcontact_withneighbors`: Verlet list constructed for these neighboring fluid atoms.
% * `Flandshoff`: Landshoff forces calculated based on the new Verlet list.
% * `f`: Magnitude of the Landshoff forces.
%
% Finally, visualization aids are employed to graphically represent these forces and their directions.
% Extract Information for Fluid Atoms in Contact with Neighbors
% Obtains the atomic information for fluid atoms in the vicinity of solid atoms, as identified earlier.
Xfluidcontact_withneighbors = Xstress.ATOMS(ifluidcontact_withneighbors,:);
% Verlet List Construction for Fluid Atoms
% Constructs a Verlet list for these neighboring fluid atoms with a larger cutoff (4*rbead).
Vfluidcontact_withneighbors = buildVerletList(Xfluidcontact_withneighbors,4*rbead);
%%% Compute Landshoff Forces
% Computes Landshoff forces based on the new Verlet list.
% The config structure (configLandshoff) should match the input values of the simulation or equivalent
hLandshoff = 5*rbead; %1.25e-5; % m
configLandshoff = struct( ...
'gradkernel', kernelSPH(hLandshoff,'lucyder',3),...kernel gradient
'h', hLandshoff,...smoothing length (m)
'c0',0.32,...speed of the sound (m/s)
'q1',30,... viscosity coefficient (-)
'rho', 1000, ...density
'm', 9.04e-12 ...
);
Flandshoff = forceLandshoff(Xfluidcontact_withneighbors,[],Vfluidcontact_withneighbors,configLandshoff);
Flandshoff_all = Flandshoff;
% Restrict to the Crown/Shell Around the Solid
% Filters out the Landshoff forces, focusing only on the "crown" or shell around the solid atoms.
Flandshoff = double(Flandshoff(ind_withoutneighbors,:)); % we restrict only to the crown/shell
flandshoff = sqrt(sum(Flandshoff.^2,2));
statvec(flandshoff,'Landshoff',sprintf('<-- TIMESTEP: %d',timestepforstress))
%%% Store the current result
postdata(end+1).type = 'Landshoff'; % end+1 triggers a new object
postdata(end).timestep = timestepforstress; % we fill it with end
postdata(end).Xfluid = Xstress.ATOMS(ifluidcontact,:);
postdata(end).Xsolid = Xstress.ATOMS(isolidcontact,:);
postdata(end).force = Flandshoff;
%%% Compute Hertz contacts
% Extract Information for Fluid Atoms in Contact
Xcontactregion = Xstress.ATOMS(union(ifluidcontact,isolidcontact),:);
% Verlet List Construction for Solid Atoms (only) and considering only Fluid Atoms as neighbors
[Vcontactregion,~,dmincontact] = buildVerletList(Xcontactregion,3*rbead,[],[],[],Xcontactregion.isfluid,Xcontactregion.issolid);
% Compute Hertz Forces
Rfluid = 1.04e-5; % m
Rsolid = 1.56e-5; % m
Rfluid = Rsolid;
configHertz = struct('R',{Rsolid Rfluid},'E',2000);
FHertz = forceHertz(Xcontactregion,Vcontactregion,configHertz);
fhertz = sqrt(sum(FHertz.^2,2));
fhertzcontacts = fhertz(fhertz>0);
statvec(fhertzcontacts,'Hertz',sprintf('<-- TIMESTEP: %d\n\tsubjected to Rsolid=[%0.4g %0.4g] dmin/2=%0.4g',timestepforstress,configHertz(1).R,configHertz(2).R,dmincontact/2))
%%% Store current results
% Store the current result
postdata(end+1).type = 'Hertz'; % end+1 triggers a new object
postdata(end).timestep = timestepforstress; % we fill it with end
postdata(end).Xfluid = [];
postdata(end).Xsolid = Xcontactregion(fhertz>0,:);
postdata(end).force = FHertz(fhertz>0);
end % next i_timestepforstess
%% Visualization (bead-based) of Landshoff forces
% Creates a 3D visualization featuring both the solid structure and the fluid atoms under consideration.
% The Landshoff forces are represented as arrows originating from the fluid atoms.
%
% Two approaches are proposed:
% a rough one based on the forces acting on the beads, which will be subsequently projected on the particle
% a more accurate one based on an interpolation of the forces on a regular grid
% Tesselated Solid Visualization
% Utilizes Delaunay triangulation to represent the solid structure.
DT = delaunayTriangulation(double(Xstress.ATOMS{isolidcontact,{'x','y','z'}}));
K = convexHull(DT);
figure, hold on
trisurf(K, DT.Points(:,1), DT.Points(:,2), DT.Points(:,3), 'FaceColor', 'w','Edgecolor','k','FaceAlpha',0.6);
axis equal; view(3)
%% Basic Visualization of Fluid Atoms and Landshoff Forces
% Landshoff forces are scaled and then visualized as arrows.
% fluid around
% scatter3D(Xfluidcontact_withneighbors{ind_withoutneighbors,{'x','y','z'}},f);
fmedian = median(flandshoff);
fmin = fmedian/50;
fscale = 4*rbead/fmedian;
start = Xfluidcontact_withneighbors{ind_withoutneighbors,{'x','y','z'}};
stop = start + Flandshoff * fscale;
start(flandshoff% non-significant forces are removed
stop(flandshoff%ha = arrow(start,stop,'length',4,'BaseAngle',60,'color','r');
axis equal, view(3)
title('Landshoff Forces around the Tessellated Surface');
xlabel('X'); ylabel('Y'); zlabel('Z')
%% Advanced LandShoff Visualization based on Grid Interpolation
coords = {'x','y','z'};
landshofbox = [ min(Xfluidcontact_withneighbors{:,coords})
max(Xfluidcontact_withneighbors{:,coords}) ]';
xlw = linspace(viewbox(1,1),viewbox(1,2),50);
ylw = linspace(viewbox(2,1),viewbox(2,2),50);
zlw = linspace(viewbox(3,1),viewbox(3,2),50);
[Xlw,Ylw,Zlw] = meshgrid(xlw,ylw,zlw);
XYZlgrid = [Xlw(:),Ylw(:),Zlw(:)];
XYZl = Xfluidcontact_withneighbors{:,coords}; % kernel centers
% Interpolation at Grid Points of Flandshoff_all
VXYZl = buildVerletList({XYZlgrid XYZl},1.2*hLandshoff); % special grid syntax
W = kernelSPH(hLandshoff,'lucy',3); % kernel expression
mbead = 9.04e-12;
Vbead = mbead/1000;
FXYZgrid = interp3SPHVerlet(XYZl,Flandshoff_all,XYZlgrid,VXYZl,W,Vbead);
FXYZgridx = reshape(FXYZgrid(:,1),size(Xlw));
FXYZgridy = reshape(FXYZgrid(:,2),size(Ylw));
FXYZgridz = reshape(FXYZgrid(:,3),size(Zlw));
% Quiver plot to show the forces with an adjusted step
quiver3(...
Xlw(1:2:end,1:2:end,1:2:end), ...
Ylw(1:2:end,1:2:end,1:2:end), ...
Zlw(1:2:end,1:2:end,1:2:end), ...
FXYZgridx(1:2:end,1:2:end,1:2:end), ...
FXYZgridy(1:2:end,1:2:end,1:2:end), ...
FXYZgridz(1:2:end,1:2:end,1:2:end), ...
'color','k','LineWidth',1)
%% Similar Visualization for Hertz Contacts
warning off
figure, hold on
fmedian = median(fhertzcontacts);
fmin = fmedian/50;
fscale = 4*rbead/fmedian;
start = Xcontactregion{fhertz>0,{'x','y','z'}};
stop = start - FHertz(fhertz>0,:) * fscale; % outwards forces (preferred)
start(fhertzcontacts% non-significant forces are removed
stop(fhertzcontacts% Compute pairwise distance
[~, idx] = min(D, [], 2); % Find closest particles on the mesh
trisurf(K, DT.Points(:,1), DT.Points(:,2), DT.Points(:,3), fhertzcontacts(idx),'Edgecolor','k','FaceAlpha',0.6);
axis equal; view(3), camlight('headlight'); camlight('left'); lighting phong; colorbar;
shading interp
ha = arrow(start,stop,'length',4,'BaseAngle',60,'color','r');
axis equal, view(3)
title('Hertz Forces onto the Tessellated Surface');
xlabel('X'); ylabel('Y'); zlabel('Z')
%% Grid Visualization of Hertz Contacts
% === STEP 1/5 === Original Delaunay triangulation and the convex hull
DT = delaunayTriangulation(double(Xstress.ATOMS{isolidcontact, {'x', 'y', 'z'}}));
K = convexHull(DT);
% === STEP 2/5 === refine the initial mesh by adding midpoints
% Extract the convex hull points and faces
hullPoints = DT.Points;
hullFaces = DT.ConnectivityList(K, :);
% Initialize a set to keep track of midpoints to ensure they are unique
midpointSet = zeros(0, 3);
% Calculate midpoints for each edge in each triangle and add to the point list
for faceIdx = 1:size(hullFaces, 1)
face = hullFaces(faceIdx, :);
for i = 1:3
for j = i+1:3
p1 = hullPoints(face(i), :);
p2 = hullPoints(face(j), :);
midpoint = (p1 + p2) / 2;
% Store the midpoint if unique
if isempty(midpointSet) || ~ismember(midpoint, midpointSet, 'rows')
midpointSet = [midpointSet; midpoint]; %#ok
end
end
end
end
% Merge the original points and the new midpoints
newPoints = [hullPoints; midpointSet];
% Re-calculate the Delaunay triangulation and convex hull)
newDT = delaunayTriangulation(newPoints);
newK = convexHull(newDT);
% === STEP 3/5 === Laplacian Smoothing
points = newDT.Points; % === Extract points and faces
faces = newDT.ConnectivityList(newK, :);
n = size(points, 1); % === Initialize new points
newPoints = zeros(size(points));
neighbors = cell(n, 1); % List of neighbors
for faceIdx = 1:size(faces, 1) % === Find the neighbors of each vertex
face = faces(faceIdx, :);
for i = 1:3
vertex = face(i);
vertex_neighbors = face(face ~= vertex);
neighbors{vertex} = unique([neighbors{vertex}; vertex_neighbors(:)]);
end
end
for i = 1:n % === Laplacian smoothing
neighbor_indices = neighbors{i};
if isempty(neighbor_indices) % Keep the point as is if it has no neighbors
newPoints(i, :) = points(i, :);
else % Move the point to the centroid of its neighbors
newPoints(i, :) = mean(points(neighbor_indices, :), 1);
end
end
% Update the Delaunay triangulation with the smoothed points
newDT = delaunayTriangulation(newPoints);
newK = convexHull(newDT);
% === STEP 4/5 === Interpolate the Hertz forces on the triangular mesh
coords = {'x','y','z'};
XYZhtri = newDT.Points;
XYZh = Xcontactregion{:,coords}; % kernel centers
VXYZh = buildVerletList({XYZhtri XYZh},1.2*hLandshoff); % special grid syntax
W = kernelSPH(hLandshoff,'lucy',3); % kernel expression
mbead = 9.04e-12;
Vbead = mbead/1000;
FXYZtri = interp3SPHVerlet(XYZh,FHertz,XYZhtri,VXYZh,W,Vbead);
% FXYZtrix = reshape(FXYZgrid(:,1),size(Xlw)); not used OV 2023-09-09
% FXYZtriy = reshape(FXYZgrid(:,2),size(Ylw));
% FXYZtriz = reshape(FXYZgrid(:,3),size(Zlw));
% === STEP 5/5 === Extract tagential forces
% Calculate face normals and centroids
points = newDT.Points;
faces = newK;
v1 = points(faces(:, 1), :) - points(faces(:, 2), :);
v2 = points(faces(:, 1), :) - points(faces(:, 3), :);
faceNormals = cross(v1, v2, 2);
faceNormals = faceNormals ./ sqrt(sum(faceNormals.^2, 2));
centroids = mean(reshape(points(faces, :), size(faces, 1), 3, 3), 3);
% Interpolate force at each centroid using scatteredInterpolant for each component
FInterp_x = scatteredInterpolant(XYZhtri, double(FXYZtri(:,1)), 'linear', 'nearest');
FInterp_y = scatteredInterpolant(XYZhtri, double(FXYZtri(:,2)), 'linear', 'nearest');
FInterp_z = scatteredInterpolant(XYZhtri, double(FXYZtri(:,3)), 'linear', 'nearest');
FXYZtri_at_centroids = [FInterp_x(centroids), FInterp_y(centroids), FInterp_z(centroids)];
% Calculate normal and tangential components of the force at each face centroid
normalComponent = dot(FXYZtri_at_centroids, faceNormals, 2);
normalForce = repmat(normalComponent, 1, 3) .* faceNormals;
tangentialForce = FXYZtri_at_centroids - normalForce;
% Calculate the magnitude of the tangential force
tangentialMagnitude = sqrt(sum(tangentialForce.^2, 2));
% === FINAL PLOTS ===
figure, hold on
trisurfHandle = trisurf(newK, newDT.Points(:, 1), newDT.Points(:, 2), newDT.Points(:, 3), 'Edgecolor', 'k', 'FaceAlpha', 0.6);
set(trisurfHandle, 'FaceVertexCData', tangentialMagnitude, 'FaceColor', 'flat');
colorbar;
% Quiver plot to show the forces with an adjusted step
quiver3(...
XYZhtri(:,1), ...
XYZhtri(:,2), ...
XYZhtri(:,3), ...
-FXYZtri(:,1), ...
-FXYZtri(:,2), ...
-FXYZtri(:,3), ...
2, ...scale
'color','k','LineWidth',3)
axis equal; view(3), camlight('headlight'); camlight('left'); lighting phong; colorbar;
title('Hertz Forces onto the Tessellated Surface');
xlabel('X'); ylabel('Y'); zlabel('Z')
%% Project Landshoff Forces onto the Tessellated Surface
% Allocate space for the projected forces.
FprojectedN = zeros(size(Flandshoff));
FprojectedT = zeros(size(Flandshoff));
% Extract vertex coordinates from Delaunay Triangulation object.
vertices = DT.Points;
% Loop through all fluid atoms in contact with the solid.
for i = 1:length(ind_withoutneighbors)
% Get the position of the current fluid atom.
fluidPos = Xfluidcontact_withneighbors{ind_withoutneighbors(i),{'x','y','z'}};
% Find the closest vertex on the tessellated surface.
dists = sum((vertices - fluidPos).^2, 2);
[~, closestVertexIdx] = min(dists);
closestVertex = vertices(closestVertexIdx, :);
% Calculate the vector from the fluid atom to the closest surface vertex.
surfaceToFluidVec = fluidPos - closestVertex;
% Normalize the vector.
surfaceToFluidVec = surfaceToFluidVec / norm(surfaceToFluidVec);
% Get the Landshoff force acting on the fluid atom.
landshoffForce = Flandshoff(i, :);
% Project the Landshoff force onto the normal.
% Here, surfaceToFluidVec serves as the approximation of the outward normal at the closest vertex.
projectedForceN = dot(landshoffForce, surfaceToFluidVec) * surfaceToFluidVec;
% Store the projected force.
FprojectedN(i, :) = projectedForceN;
FprojectedT(i, :) = landshoffForce-projectedForceN;
end
% At this point, Fprojected contains the Landshoff forces projected onto the surface normal.
% Interpolation and Stress Magnitude Visualization
% Get faces and vertices from the tessellation.
faces = K; % Faces are given by the convex hull indices
vertices = DT.Points; % Vertex coordinates
% Loop over all faces to interpolate force and stress.
% Compute Barycentric Interpolation of Forces
% Initialize an array to store the interpolated forces and stresses
FNinterp_face = zeros(size(faces, 1), 3);
FTinterp_face = zeros(size(faces, 1), 3);
SNinterp_face = zeros(size(faces, 1), 3); % Normal stress per unit area
STinterp_face = zeros(size(faces, 1), 3); % Tangential stress per unit area
% Loop over all faces.
for i = 1:size(faces, 1)
% Vertices of the triangle.
vertex_indices = faces(i, :);
A = vertices(vertex_indices(1), :);
B = vertices(vertex_indices(2), :);
C = vertices(vertex_indices(3), :);
% Surface area of the triangle using Heron's formula.
AB = norm(A - B);
AC = norm(A - C);
BC = norm(B - C);
s = (AB + AC + BC) / 2;
area_triangle = sqrt(s * (s - AB) * (s - AC) * (s - BC));
% Normal to faces
N = cross(B - A, C - A);
N = N / norm(N); % Normalizing the normal vector
% Forces at the vertices.
F_A = FprojectedN(vertex_indices(1), :) + FprojectedT(vertex_indices(1), :);
F_B = FprojectedN(vertex_indices(2), :) + FprojectedT(vertex_indices(2), :);
F_C = FprojectedN(vertex_indices(3), :) + FprojectedT(vertex_indices(3), :);
% The centroid (P) of the triangle for interpolation.
P = (A + B + C) / 3;
% Barycentric weights.
ABC_inv = inv([A; B; C]);
w = ABC_inv * P';
% Interpolate the force at the centroid.
Finterp = w(1) * F_A + w(2) * F_B + w(3) * F_C;
% Decompose the force into normal and tangential components
F_N = dot(Finterp, N) * N; % only the normal force is trivial to compute
F_T = Finterp - F_N; % by subtraction we get the tangential
% Store interpolated force.
Finterp_face(i, :) = Finterp;
% Compute normal and tangential stress per unit area (Force/Area)
SNinterp_face(i, :) = F_N / area_triangle;
STinterp_face(i, :) = F_T / area_triangle;
end
% Compute the magnitude of interpolated stress.
face_force_magnitude = sqrt(sum(Finterp_face.^2, 2));
face_stressN_magnitude = sqrt(sum(SNinterp_face.^2, 2));
face_stressT_magnitude = sqrt(sum(STinterp_face.^2, 2));
% Add the smoothed force at vertices
% In computational geometry, the "valence" of a vertex refers to the number of edges
% (or equivalently, faces for a triangular mesh) incident to it. Valence can serve as
% an important metric in mesh quality and adaptivity considerations.
% In our case, the valence is expected high (up to 11,12 faces per vertex), then poor mesh
uniq_vertex_indices = unique(faces(:)); % Find unique vertices used in faces
uniq_vertices = vertices(uniq_vertex_indices, :); % Create a new vertices array based on these unique indices
mapping = NaN(size(vertices, 1), 1); % Create a mapping from old vertex indices to new ones
mapping(uniq_vertex_indices) = 1:length(uniq_vertex_indices);
uniq_faces = mapping(faces); % Update the faces array to reflect new vertex indices
% Initialize interpolated forces at vertices
Finterp_vertex = zeros(size(uniq_vertices, 1), 3);
SNinterp_vertex = zeros(size(uniq_vertices, 1), 3);
STinterp_vertex = zeros(size(uniq_vertices, 1), 3);
% Create a mapping from vertices to faces
vertex_to_faces = cell(size(uniq_vertices, 1), 1);
for i = 1:size(uniq_faces, 1)
for j = 1:3 % for each Vertex
vertex_to_faces{uniq_faces(i, j)} = [vertex_to_faces{uniq_faces(i, j)}; i]; % we add the ith face
end
end
% Interpolate forces at each vertex
for i = 1:size(uniq_vertices, 1)
adjacent_faces = vertex_to_faces{i};
if ~isempty(adjacent_faces)
Finterp_vertex(i, :) = mean(Finterp_face(adjacent_faces, :), 1);
SNinterp_vertex(i, :) = mean(SNinterp_face(adjacent_faces, :), 1);
STinterp_vertex(i, :) = mean(STinterp_face(adjacent_faces, :), 1);
end
end
vertex_force_magnitude = sqrt(sum(Finterp_vertex.^2, 2));
vertex_stressN_magnitude = sqrt(sum(SNinterp_vertex.^2, 2));
vertex_stressT_magnitude = sqrt(sum(STinterp_vertex.^2, 2));
% Visualization (face-based, less smooth)
figure;
trisurf(faces, vertices(:, 1), vertices(:, 2), vertices(:, 3), face_force_magnitude, 'EdgeColor', 'none');
axis equal; view(3);
colorbar;
title('Interpolated Force Magnitude on Tessellated Surface (face-based)');
xlabel('X'); ylabel('Y'); zlabel('Z')
% Visualization (vertex-based, more smooth)
figure;
trisurf(uniq_faces, uniq_vertices(:, 1), uniq_vertices(:, 2), uniq_vertices(:, 3), vertex_force_magnitude, 'EdgeColor', 'none');
axis equal; view(3);
colorbar;
title('Interpolated Force Magnitude on Tessellated Surface (vertex-based)');
xlabel('X'); ylabel('Y'); zlabel('Z')
% Optimized visualization using 'patch' for stresses
% only vertex_stressT_magnitude is meaningful
figure;
patch_data.Vertices = uniq_vertices; % Prepare data for 'patch' function
patch_data.Faces = uniq_faces;
patch_data.FaceVertexCData = vertex_stressT_magnitude;
patch_data.FaceColor = 'interp';
patch_data.EdgeColor = 'none';
% Calculate vertex normals for smooth shading
patch_data.VertexNormals = -vertexNormal(triangulation(uniq_faces, uniq_vertices));
% Create the surface plot
p = patch(patch_data);
% Set view and lighting
axis equal;
view(3); camlight('headlight'); camlight('left'); lighting phong; colorbar;
xlabel('X'); ylabel('Y'); zlabel('Z');
title('Tangential Landshoff Stress Magnitude onto Solid Surface');
% Option to remove grid for a cleaner look
% grid off;
Workshop Main File - Part 2bis
This script discusses advanced shear details beyond those shown in Part2
Only advanced features are kept
%% Workshop Main File - Part 2bis
% This script discusses advanced shear details beyond those shown in Part2
% Only advanced features are kept
% File Structure (change your local path to reflect the content)
% ├── example2bis.m
% └── ...
% └── data folder (dumps/pub1/)
%INRAE\Olivier Vitrac, Han Chen 2023-09-07
% Revision history
% 2023-09-09 major increment
% 2023-09-10 finalization and copy to notebook/
% 2023-09-13 update for new Landshoff and Hertz virial calculations (fixes)
% 2023-09-20 add vorticity
%% Definitions
% We assume that the dump file has been preprocessed (see example1.m and example2.m)
statvec = @(f,before,after) dispf('%s: %s%d values | average = %0.5g %s', before, ...
cell2mat(cellfun(@(x) sprintf(' %0.1f%%> %10.4g | ', x, prctile(f, x)), {2.5, 25, 50, 75, 97.5}, 'UniformOutput', false)), ...
length(f), mean(f),after); % user function to display statistics on vectors (usage: statvec(f,'myvar','ok'))
coords = {'x','y','z'};
vcoords = cellfun(@(c) ['v',c],coords,'UniformOutput',false); % vx, vy, vz
% dump file and its parameterization
datafolder = './dumps/pub1/';
dumpfile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle';
datafolder = lamdumpread2(fullfile(datafolder,dumpfile),'search'); % fix datafolder based on initial guess
X0 = lamdumpread2(fullfile(datafolder,dumpfile)); % default frame
boxdims = X0.BOX(:,2) - X0.BOX(:,1); % box dims
natoms = X0.NUMBER; % number of atoms
timesteps = X0.TIMESTEPS; % time steps
ntimesteps = length(timesteps); % number of time steps
T = X0.ATOMS.type; % atom types
% === IDENTIFICATION OF ATOMS ===
atomtypes = unique(T); % list of atom types
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,fluidtype] = max(natomspertype); % fluid type
[~,solidtype] = min(natomspertype); % solid type
walltypes = setdiff(atomtypes,[fluidtype,solidtype]); % wall types
nfluidatoms = natomspertype(fluidtype);
nsolidatoms = natomspertype(solidtype);
% === FLOW DIRECTION ===
[~,iflow] = max(boxdims);
iothers = setdiff(1:size(X0.BOX,1),iflow);
% === GUESS BEAD SIZE ===
% not needed anymore since the mass, vol and rho are taken from the dump file
Vbead_guess = prod(boxdims)/natoms;
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,cutoff,dmin,config,dist] = buildVerletList(X0.ATOMS(T==fluidtype,coords),cutoff);
rbead = dmin/2; % based on separation distance
%% Frame and Corresponding ROI for Stress Analysis
list_timestepforstess = unique(timesteps(ceil((0.1:0.1:0.9)*ntimesteps)));
timestepforstress = list_timestepforstess(end);
% stress frame
Xstress = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit',[],timestepforstress); % middle frame
Xstress.ATOMS.isfluid = Xstress.ATOMS.type==fluidtype;
Xstress.ATOMS.issolid = Xstress.ATOMS.type==solidtype;
Xstress.ATOMS.iswall = ismember(Xstress.ATOMS.type,walltypes);
% == control (not used) ==
% average bead volume and bead radius (control, min separation distance was used in the previous section)
fluidbox = [min(Xstress.ATOMS{Xstress.ATOMS.isfluid,coords});max(Xstress.ATOMS{Xstress.ATOMS.isfluid,coords})]';
vbead_est = prod(diff(fluidbox,1,2))/(length(find(Xstress.ATOMS.isfluid))+length(Xstress.ATOMS.issolid));
rbead_est = (3*vbead_est/(4*pi))^(1/3);
mbead_est = vbead_est*1000;
% Verlet List Construction with Short Cutoff
% Builds a Verlet list with a short cutoff distance, designed to identify only the closest neighbors.
[verletList,cutoff,dmin,config,dist] = buildVerletList(Xstress.ATOMS,3*rbead);
% Partition Verlet List Based on Atom Types
% This Verlet list is partitioned based on atom types, distinguishing between interactions
% that are exclusively fluid-fluid, solid-fluid, or solid-solid.
verletListCross = partitionVerletList(verletList,Xstress.ATOMS);
% Identify Contacting Atoms
Xstress.ATOMS.isincontact = ~cellfun(@isempty,verletListCross);
Xstress.ATOMS.contacttypes = cellfun(@(v) Xstress.ATOMS.type(v)',verletListCross,'UniformOutput',false);
% Identify Atoms in Contact with Solids and Fluids
Xstress.ATOMS.isincontactwithsolid = cellfun(@(c) ismember(solidtype,c), Xstress.ATOMS.contacttypes);
Xstress.ATOMS.isincontactwithfluid = cellfun(@(c) ismember(fluidtype,c), Xstress.ATOMS.contacttypes);
Xstress.ATOMS.isincontactwithwalls = cellfun(@(c) ~isempty(intersect(walltypes,c)), Xstress.ATOMS.contacttypes);
% Flag Fluid Atoms in Contact with Solid and Vice Versa
Xstress.ATOMS.fluidincontactwithsolid = Xstress.ATOMS.isfluid & Xstress.ATOMS.isincontactwithsolid;
Xstress.ATOMS.solidincontactwithfluid = Xstress.ATOMS.issolid & Xstress.ATOMS.isincontactwithfluid;
Xstress.ATOMS.fluidincontactwithwalls = Xstress.ATOMS.isfluid & Xstress.ATOMS.isincontactwithwalls;
Xstress.ATOMS.wallsincontactwithfluid = Xstress.ATOMS.iswall & Xstress.ATOMS.isincontactwithfluid;
% Identify Indices for Analysis
ROI = [
min(Xstress.ATOMS{Xstress.ATOMS.fluidincontactwithsolid,coords})
max(Xstress.ATOMS{Xstress.ATOMS.fluidincontactwithsolid,coords})
]';
ROI(iflow,:) = mean(ROI(iflow,:)) + [-1 1] * diff(ROI(iflow,:));
for j = iothers
ROI(j,:) = Xstress.BOX(j,:) ;
end
inROI = true(size(Xstress,1),1);
for c=1:length(coords)
inROI = inROI & (Xstress.ATOMS{:,coords{c}}>=ROI(c,1)) & (Xstress.ATOMS{:,coords{c}}<=ROI(c,2));
end
ifluid = find(inROI & (Xstress.ATOMS.isfluid));
isolid = find(inROI & (Xstress.ATOMS.issolid));
iwall = find(inROI & (Xstress.ATOMS.iswall));
isolidcontact = find(Xstress.ATOMS.solidincontactwithfluid);
iwallcontact = find(inROI & Xstress.ATOMS.wallsincontactwithfluid);
% control
figure, hold on
plot3D(Xstress.ATOMS{ifluid,coords},'bo','markersize',3,'markerfacecolor','b')
plot3D(Xstress.ATOMS{iwallcontact,coords},'ko','markersize',12,'markerfacecolor','k')
plot3D(Xstress.ATOMS{isolidcontact,coords},'ro','markersize',16,'markerfacecolor','r')
axis equal, view(3), drawnow
% triangulation of the solid
DT = delaunayTriangulation(double(Xstress.ATOMS{isolidcontact,{'x','y','z'}}));
K = convexHull(DT);
plotsolid = @()trisurf(K, DT.Points(:,1), DT.Points(:,2), DT.Points(:,3), 'FaceColor', 'w','Edgecolor','k','FaceAlpha',0.6);
%% Landshoff forces and stresses in the fluid
% these forces are the viscous forces in the fluid
% we calculate:
% Flandshoff(i,:) the local Landshoff force (1x3 vector) for the atom i
% Wlandshoff(i,:) is the local virial stress tensor (3x3 matrix stored as 1x9 vector with Matlab conventions)
% row-wise: force component index
% columm-wise: coord component index
% The virial tensor reads:
% \[
% \sigma =
% \begin{pmatrix}
% \sigma_{11} & \sigma_{12} & \sigma_{13} \\
% \sigma_{21} & \sigma_{22} & \sigma_{23} \\
% \sigma_{31} & \sigma_{32} & \sigma_{33}
% \end{pmatrix}
% \]
%
% The viscous tensor component is associated to the flow (x)
% $ \tau_{xy} = \mu \left( \frac{\partial u}{\partial y} \right) $
% it is derived from the virial stress tensor:
% $ \sigma_{\alpha\beta} = \frac{1}{V} \sum_{i
% where $ V $ is the volume, $ r_{ij,\alpha} $ is the $ \alpha $-component of the distance vector between particles $ i $ and $ j $, and $ f_{ij,\beta} $ is the $ \beta $-component of the force between particles $ i $ and $ j $.
%
% For this specific problem, the most relevant components would be those relating to shear and normal forces, specifically $ \sigma_{xy} $ and $ \sigma_{yy} $.
% 1. $ \sigma_{xy} $ corresponds to the shear effects and should be closely related to the SPH-based shear stress $ \tau_{xy} $ in the fluid. This component would be a primary point of comparison.%
% 2. $ \sigma_{yy} $ will capture the effects of the Hertzian contacts along the $ y $-direction (i.e., the direction opposite to which the wall is moving). In your Hertz contact model, the normal force is acting along the $ y $-direction, which contributes to this component.
%
% Therefore, based on the equality of mechanical states between the fluid and the wall, we should expect:
% $\sigma_{xy}^{\text{SPH}} = \sigma_{xy}^{\text{Hertz}}$ (shear)
% $\sigma_{yy}^{\text{SPH}} = \sigma_{yy}^{\text{Hertz}}$ (normal)
% which reads (first index: direction, second index: vcomponent)
% $ \sigma_{21}^{\text{SPH}} = \sigma_{21}^{\text{Hertz}} $
% $ \sigma_{22}^{\text{SPH}} = \sigma_{22}^{\text{Hertz}} $
% === Build the Verlet list consistently with the local Virial Stress Tensor ===
% Changing h can affect the value of viscosity for Landshoff foces and shear stress.
% While keeping the same hLandshoff, the results can be rescaled to the value of h
% applied in the simulations.
Xfluid = Xstress.ATOMS(ifluid,:);
hLandshoff = 4*rbead; %1.25e-5; % m
Vfluid = buildVerletList(Xfluid,hLandshoff);
configLandshoff = struct( ...
' gradkernel', kernelSPH(hLandshoff,'lucyder',3),...kernel gradient
'h', hLandshoff,...smoothing length (m)
'c0',0.32,...speed of the sound (m/s)
'q1',30 ... viscosity coefficient (-)
);
rhofluid = mean(Xfluid.c_rho_smd);
mbead = mean(Xfluid.mass);
Vbead = mean(Xfluid.c_vol);
mu = rhofluid*configLandshoff.q1*configLandshoff.c0*configLandshoff.h/10; % viscosity estimate
dispf('Atificial viscosity: %0.4g Pa.s',mu)
% Landshoff forces and local virial stress
[Flandshoff,Wlandshoff] = forceLandshoff(Xfluid,[],Vfluid,configLandshoff);
flandshoff = sqrt(sum(Flandshoff.^2,2));
statvec(flandshoff,' Force Landshoff',sprintf('<-- TIMESTEP: %d',timestepforstress))
statvec(Wlandshoff(:,2),'Virial Landshoff',sprintf('<-- TIMESTEP: %d',timestepforstress))
% number of grid points along the largest dimension
fluidbox = [ min(Xfluid{:,coords}); max(Xfluid{:,coords}) ]';
boxcenter = mean(fluidbox,2);
resolution = ceil(50 * diff(fluidbox,[],2)'./max(diff(fluidbox,[],2)));
xw = linspace(fluidbox(1,1),fluidbox(1,2),resolution(1));
yw = linspace(fluidbox(2,1),fluidbox(2,2),resolution(2));
zw = linspace(fluidbox(3,1),fluidbox(3,2),resolution(3));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
hLandshoff = 5*rbead; %1.25e-5; % m
% Build the Grid Verlet list
VXYZ = buildVerletList({XYZgrid Xfluid{:,coords}},1.001*hLandshoff); % special grid syntax
% Interpolate Landshoff forces, extract components for plotting
W = kernelSPH(hLandshoff,'lucy',3); % kernel for interpolation (not gradkernel!)
FXYZgrid = interp3SPHVerlet(Xfluid{:,coords},Flandshoff,XYZgrid,VXYZ,W,Vbead);
FXYZgridx = reshape(FXYZgrid(:,1),size(Xw));
FXYZgridy = reshape(FXYZgrid(:,2),size(Yw));
FXYZgridz = reshape(FXYZgrid(:,3),size(Zw));
% Interpolate local virial stress tensor, extract s12 which is stored as s(2,1)
WXYZgrid = interp3SPHVerlet(Xfluid{:,coords},Wlandshoff,XYZgrid,VXYZ,W,Vbead);
s12grid = reshape(WXYZgrid(:,2),size(Xw)); % extract \sigma_{xy} i.e. forces along x across y
% Alternative estimation of the virial from the Cauchy stress tensor
WXYZgrid2 = interp3cauchy(Xw,Yw,Zw,FXYZgridx,FXYZgridy,FXYZgridz);
s12grid2 = reshape(WXYZgrid2(:,:,:,2),size(Xw));
% Interpolate the velocities, extract components for plotting
vXYZgrid = interp3SPHVerlet(Xfluid{:,coords},Xfluid{:,vcoords},XYZgrid,VXYZ,W,Vbead);
vXYZgridx = reshape(vXYZgrid(:,1),size(Xw));
vXYZgridy = reshape(vXYZgrid(:,2),size(Yw));
vXYZgridz = reshape(vXYZgrid(:,3),size(Zw));
vdXYZgridxdy = gradient(vXYZgridx,xw(2)-xw(1),yw(2)-yw(1),zw(2)-zw(1));
s12grid_est = mu * vdXYZgridxdy;
% === Plot Estimated shear stress (it should be the theoretical value)
figure, hold on
hs = slice(Xw,Yw,Zw,s12grid_est,[boxcenter(1) xw(end)],...
[boxcenter(2) yw(end)],...
[fluidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
plotsolid()
lighting gouraud, camlight('left'), axis equal, view(3) % shading interp
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = '\sigma_{XY}';
title('Stress from velocity field: \sigma_{XY}=\eta\cdot\partial v_x/\partial y','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
step = [3 10 5];
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
vXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
vXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
vXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,1,'color','k','LineWidth',2)
npart = 30;
[startX,startY,startZ] = meshgrid( ...
double(xw(1)), ...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))),...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))) ...
);
vstart = interp3(Xw,Yw,Zw,vXYZgridx,startX,startY,startZ); startX(vstart<0) = double(xw(end));
hsl = streamline(double(Xw),double(Yw),double(Zw),vXYZgridx,vXYZgridy,vXYZgridz,startX,startY,startZ);
set(hsl,'linewidth',2,'color',[0.4375 0.5000 0.5625])
plot3(startX(:),startY(:),startZ(:),'ro','markerfacecolor',[0.4375 0.5000 0.5625])
% fix the view
view(-90,90), clim([-1e-1 1e-1])
% === Plot the vorticity
% Calculate the gradients of the velocity components
[dvxdx, dvxdy, dvxdz] = gradient(vXYZgridx,xw(2)-xw(1),yw(2)-yw(1),zw(2)-zw(1));
[dvydx, dvydy, dvydz] = gradient(vXYZgridy,xw(2)-xw(1),yw(2)-yw(1),zw(2)-zw(1));
[dvzdx, dvzdy, dvzdz] = gradient(vXYZgridz,xw(2)-xw(1),yw(2)-yw(1),zw(2)-zw(1));
% Calculate vorticity components
w_x = dvzdy - dvydz;
w_y = dvxdz - dvzdx;
w_z = dvydx - dvxdy;
w_mag = sqrt(w_x.^2+w_y.^2+w_z.^2);
% Plot the vorticity
[stepx,stepy,stepz] = deal(3,3,3);
[plotsx,plotsy,plotsz] = deal(stepx*(xw(2)-xw(1)),stepy*(yw(2)-yw(1)),stepz*(zw(2)-zw(1)));
figure, hold on
hs = slice(Xw,Yw,Zw,w_mag,[boxcenter(1) xw(end)-plotsx],...
[boxcenter(2) yw(end)-plotsy],...
[fluidbox(3,1)+plotsz boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
quiver3( ...
Xw(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
Yw(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
Zw(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
w_x(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
w_y(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
w_z(stepy:stepy:end-stepy,stepx:stepx:end-stepy,stepz:stepz:end-stepz),...
1,'color','r','LineWidth',2);
title('Vorticity Field','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
hc = colorbar('AxisLocation','in','fontsize',14);
hc.Label.String = 'Vorticity Magnitude';
lighting gouraud; camlight('left'); axis equal; view(-112,82); clim([0 20])
% === Plot Estimated shear stress from Cauchy Tensor
figure, hold on
s1299 = prctile(abs(s12grid2(~isnan(s12grid2))),99);
isosurface(Xw,Yw,Zw,s12grid2,s1299)
isosurface(Xw,Yw,Zw,s12grid2,-s1299)
hs = slice(Xw,Yw,Zw,s12grid2,[boxcenter(1) xw(end)],...
[boxcenter(2) yw(end)],...
[fluidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
plotsolid()
lighting gouraud, camlight('left'), axis equal, view(3) % shading interp
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = '\sigma_{XY}';
title('Local Cauchy stress: \sigma_{XY}','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
npart = 20;
[startX,startY,startZ] = meshgrid( ...
double(xw(1)), ...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))),...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))) ...
);
vstart = interp3(Xw,Yw,Zw,vXYZgridx,startX,startY,startZ); startX(vstart<0) = double(xw(end));
hsl = streamline(double(Xw),double(Yw),double(Zw),vXYZgridx,vXYZgridy,vXYZgridz,startX,startY,startZ);
set(hsl,'linewidth',2,'color','k')
plot3(startX(:),startY(:),startZ(:),'ro','markerfacecolor',[0.4375 0.5000 0.5625])
% === Plot local virial stess: s12 (s12 is stored as s(2,1))
figure, hold on
s1299 = prctile(abs(s12grid(~isnan(s12grid))),99);
isosurface(Xw,Yw,Zw,s12grid,s1299)
isosurface(Xw,Yw,Zw,s12grid,-s1299)
hs = slice(Xw,Yw,Zw,s12grid,[boxcenter(1) xw(end)],...
[boxcenter(2) yw(end)],...
[fluidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
plotsolid()
lighting gouraud, camlight('left'), axis equal, view(3) % shading interp
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = '\sigma_{XY}';
title('Local virial stress: \sigma_{XY}','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
npart = 20;
[startX,startY,startZ] = meshgrid( ...
double(xw(1)), ...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))),...
double(yw(unique(round(1+(1+(linspace(-1,1,npart).*linspace(-1,1,npart).^2))*floor(length(yw)/2))))) ...
);
vstart = interp3(Xw,Yw,Zw,vXYZgridx,startX,startY,startZ); startX(vstart<0) = double(xw(end));
hsl = streamline(double(Xw),double(Yw),double(Zw),vXYZgridx,vXYZgridy,vXYZgridz,startX,startY,startZ);
set(hsl,'linewidth',2,'color','k')
plot3(startX(:),startY(:),startZ(:),'ro','markerfacecolor',[0.4375 0.5000 0.5625])
% === PLot Landshoff forces only
figure, hold on
hs = slice(Xw,Yw,Zw,FXYZgridx,[xw(1) boxcenter(1)],...
[yw(2) boxcenter(2) yw(end)],...
[fluidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.9)
axis equal, view(3),
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'Landshoff along X';
xlabel('X'), ylabel('Y'), zlabel('Z')
title('Landshoff forces along X','fontsize',20)
step = [3 5 5];
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,1,'color','k','LineWidth',2)
%% Hertz contact solid-fluid
% ===========================
XFluidSolid = Xstress.ATOMS(union(ifluid,isolid),:);
figure, hold on
plot3D(XFluidSolid{XFluidSolid.isfluid,coords},'bo','markersize',3,'markerfacecolor','b')
plot3D(XFluidSolid{XFluidSolid.issolid,coords},'ro','markersize',16,'markerfacecolor','r')
Rfluid = 1.04e-5; % m
Rsolid = 1.56e-5; % m
Rfluid = Rsolid;
hhertz = 2*Rsolid;
[Vcontactsolid,~,dmincontact] = buildVerletList(XFluidSolid,hhertz,[],[],[],XFluidSolid.isfluid,XFluidSolid.issolid);
configHertz = struct('R',{Rsolid Rfluid},'E',2000);
[FHertzSolid,WHertzSolid] = forceHertz(XFluidSolid,Vcontactsolid,configHertz);
fhertz = sqrt(sum(FHertzSolid.^2,2));
statvec(fhertz(fhertz>0),'Hertz',sprintf('<-- TIMESTEP: %d\n\tsubjected to Rsolid=[%0.4g %0.4g] dmin/2=%0.4g',timestepforstress,configHertz(1).R,configHertz(2).R,dmincontact/2))
% project Hertz contact on accurate grid (Cartesian)
soliddbox = [ min(Xstress.ATOMS{isolid,coords})-4*rbead
max(Xstress.ATOMS{isolid,coords})+4*rbead ]';
boxcenter = mean(soliddbox,2);
resolution = ceil(50 * diff(soliddbox,[],2)'./max(diff(soliddbox,[],2)));
xw = linspace(soliddbox(1,1),soliddbox(1,2),resolution(1));
yw = linspace(soliddbox(2,1),soliddbox(2,2),resolution(2));
zw = linspace(soliddbox(3,1),soliddbox(3,2),resolution(3));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZ = buildVerletList({XYZgrid XFluidSolid{:,coords}},1.1*hhertz);
W = kernelSPH(hhertz,'lucy',3); % kernel for interpolation (not gradkernel!)
FXYZgrid = interp3SPHVerlet(XFluidSolid{:,coords},FHertzSolid,XYZgrid,VXYZ,W,Vbead);
FXYZgridx = reshape(FXYZgrid(:,1),size(Xw));
FXYZgridy = reshape(FXYZgrid(:,2),size(Yw));
FXYZgridz = reshape(FXYZgrid(:,3),size(Zw));
WXYZgrid2 = interp3cauchy(Xw,Yw,Zw,FXYZgridx,FXYZgridy,FXYZgridz);
s12grid2 = reshape(WXYZgrid2(:,:,:,2),size(Xw));
%% Rough plot of Hertz Contact Tensor (component xy) - SOLID-FLUID
figure, hold on
hs = slice(Xw,Yw,Zw,s12grid2, ...
boxcenter(1),...
boxcenter(2),...
boxcenter(3));
set(hs,'edgecolor','none','facealpha',0.9)
axis equal, view(3),
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'Hertz Tensor xy';
xlabel('X'), ylabel('Y'), zlabel('Z')
title('Hertz tensor','fontsize',20)
step = [2 2 2];
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,1.5,'color','k','LineWidth',2)
%% Advanced plot for Hertz Contacts
% almost same code as example2:
% === STEP 1/5 === Original Delaunay triangulation and the convex hull
% this step has been already done as the begining of example2bis.m
% DT = delaunayTriangulation(double(Xstress.ATOMS{isolidcontact, coords}));
% K = convexHull(DT);
% === STEP 2/5 === refine the initial mesh by adding midpoints
% Extract the convex hull points and faces
hullPoints = DT.Points;
hullFaces = DT.ConnectivityList(K, :);
% Initialize a set to keep track of midpoints to ensure they are unique
midpointSet = zeros(0, 3);
% Calculate midpoints for each edge in each triangle and add to the point list
for faceIdx = 1:size(hullFaces, 1)
face = hullFaces(faceIdx, :);
for i = 1:3
for j = i+1:3
midpoint = (hullPoints(face(i), :) + hullPoints(face(j), :)) / 2;
if isempty(midpointSet) || ~ismember(midpoint, midpointSet, 'rows')
midpointSet = [midpointSet; midpoint]; %#ok
end
end
end
end
% Merge the original points and the new midpoints, update the the Delaunay triangulation
newDT = delaunayTriangulation([hullPoints; midpointSet]);
newK = convexHull(newDT);
% === STEP 3/5 === Laplacian Smoothing
points = newDT.Points; % === Extract points and faces
faces = newDT.ConnectivityList(newK, :);
n = size(points, 1); % === Initialize new points
newPoints = zeros(size(points));
neighbors = cell(n, 1); % List of neighbors
for faceIdx = 1:size(faces, 1) % === Find the neighbors of each vertex
face = faces(faceIdx, :);
for i = 1:3
vertex = face(i);
vertex_neighbors = face(face ~= vertex);
neighbors{vertex} = unique([neighbors{vertex}; vertex_neighbors(:)]);
end
end
for i = 1:n % === Laplacian smoothing
neighbor_indices = neighbors{i};
if isempty(neighbor_indices) % Keep the point as is if it has no neighbors
newPoints(i, :) = points(i, :);
else % Move the point to the centroid of its neighbors
newPoints(i, :) = mean(points(neighbor_indices, :), 1);
end
end
% Update the Delaunay triangulation with the smoothed points
newDT = delaunayTriangulation(newPoints);
newK = convexHull(newDT);
% === STEP 4/5 === Interpolate the Hertz forces on the triangular mesh
XYZhtri = newDT.Points;
XYZh = XFluidSolid{:,coords}; % kernel centers
VXYZh = buildVerletList({XYZhtri XFluidSolid{:,coords}},1.1*hhertz); % special grid syntax
W = kernelSPH(hhertz,'lucy',3); % kernel expression
FXYZtri = interp3SPHVerlet(XFluidSolid{:,coords},FHertzSolid,XYZhtri,VXYZh,W,Vbead);
% === STEP 5/5 === Extract tagential forces
% Calculate face normals and centroids
points = newDT.Points;
faces = newK;
v1 = points(faces(:, 1), :) - points(faces(:, 2), :);
v2 = points(faces(:, 1), :) - points(faces(:, 3), :);
faceNormals = cross(v1, v2, 2);
faceNormals = faceNormals ./ sqrt(sum(faceNormals.^2, 2));
centroids = mean(reshape(points(faces, :), size(faces, 1), 3, 3), 3);
% Interpolate force at each centroid using scatteredInterpolant for each component
FInterp_x = scatteredInterpolant(XYZhtri, double(FXYZtri(:,1)), 'linear', 'nearest');
FInterp_y = scatteredInterpolant(XYZhtri, double(FXYZtri(:,2)), 'linear', 'nearest');
FInterp_z = scatteredInterpolant(XYZhtri, double(FXYZtri(:,3)), 'linear', 'nearest');
FXYZtri_at_centroids = [FInterp_x(centroids), FInterp_y(centroids), FInterp_z(centroids)];
% Calculate normal and tangential components of the force at each face centroid
normalComponent = dot(FXYZtri_at_centroids, faceNormals, 2);
normalForce = repmat(normalComponent, 1, 3) .* faceNormals;
tangentialForce = FXYZtri_at_centroids - normalForce;
tangentialMagnitude = sqrt(sum(tangentialForce.^2, 2));
% Do the figure
figure, hold on
trisurfHandle = trisurf(newK, newDT.Points(:, 1), newDT.Points(:, 2), newDT.Points(:, 3), 'Edgecolor', 'k', 'FaceAlpha', 0.6);
set(trisurfHandle, 'FaceVertexCData', tangentialMagnitude, 'FaceColor', 'flat');
colorbar;
% Quiver plot to show the forces with an adjusted step
step = [2 2 2]*2;
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,1.5,'color','k','LineWidth',2)
axis equal; view(3), camlight('headlight'); camlight('left'); lighting phong; colorbar;
title('Hertz Forces onto the Tessellated Surface','fontsize',20);
xlabel('X'); ylabel('Y'); zlabel('Z')
hc = colorbar('AxisLocation','in','fontsize',14);
%% Hertz contact wall-fluid
% ==========================
XFluidWall = Xstress.ATOMS(union(ifluid,iwall),:);
figure, hold on
plot3D(XFluidWall{XFluidWall.isfluid,coords},'bo','markersize',3,'markerfacecolor','b')
plot3D(XFluidWall{XFluidWall.iswall,coords},'ko','markersize',16,'markerfacecolor','k')
Rfluid = 1.04e-5; % m
Rsolid = 1.56e-5; % m
Rfluid = Rsolid;
hhertz = 2*Rsolid;
[Vcontactwall,~,dmincontact] = buildVerletList(XFluidWall,hhertz,[],[],[],XFluidWall.isfluid,XFluidWall.iswall);
configHertz = struct('R',{Rsolid Rfluid},'E',2000,'rho',1000,'m',9.04e-12,'h',hhertz);
[FHertzWall,WHertzWall] = forceHertz(XFluidWall,Vcontactwall,configHertz);
fhertz = sqrt(sum(FHertzWall.^2,2));
statvec(fhertz(fhertz>0),'Hertz',sprintf('<-- TIMESTEP: %d\n\tsubjected to Rsolid=[%0.4g %0.4g] dmin/2=%0.4g',timestepforstress,configHertz(1).R,configHertz(2).R,dmincontact/2))
figure, stem3(XFluidWall.x,XFluidWall.y,FHertzWall(:,1),'k.')
% project Hertz contact on accurate grid (Cartesian)
[wallbox1,wallbox2] = deal(fluidbox);
wallbox1(2,1) = fluidbox(2,1) - 1.5*hhertz;
wallbox1(2,2) = fluidbox(2,1) + 1.5*hhertz;
wallbox2(2,1) = fluidbox(2,2) - 1.5*hhertz;
wallbox2(2,2) = fluidbox(2,2) + 1.5*hhertz;
boxcenter = mean(fluidbox,2);
boxcenter1 = mean(wallbox1,2);
boxcenter2 = mean(wallbox2,2);
resolution1 = ceil(120 * diff(wallbox1,[],2)'./max(diff(wallbox1,[],2)));
resolution2 = ceil(120 * diff(wallbox2,[],2)'./max(diff(wallbox2,[],2)));
xw = linspace(wallbox1(1,1),wallbox1(1,2),resolution1(1));
yw1 = linspace(wallbox1(2,1),wallbox1(2,2),resolution1(2));
yw2 = linspace(wallbox2(2,1),wallbox2(2,2),resolution2(2));
zw = linspace(wallbox1(3,1),wallbox1(3,2),resolution1(3));
[Xw,Yw,Zw] = meshgrid(xw,[yw1 boxcenter(2) yw2],zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZ = buildVerletList({XYZgrid XFluidWall{:,coords}},1.1*hhertz);
W = kernelSPH(hhertz,'lucy',3); % kernel for interpolation (not gradkernel!)
FXYZgrid = interp3SPHVerlet(XFluidWall{:,coords},FHertzWall,XYZgrid,VXYZ,W,Vbead);
FXYZgridx = reshape(FXYZgrid(:,1),size(Xw));
FXYZgridy = reshape(FXYZgrid(:,2),size(Yw));
FXYZgridz = reshape(FXYZgrid(:,3),size(Zw));
WXYZgrid2 = interp3cauchy(Xw,Yw,Zw,FXYZgridx,FXYZgridy,FXYZgridz);
s12grid2 = reshape(WXYZgrid2(:,:,:,2),size(Xw));
%% Rough plot of Hertz Contact Tensor (component xy)
figure, hold on
hs = slice(Xw,Yw,Zw,s12grid2, ...
single([]),...
[fluidbox(2,1)-[-.1 0 .1 0.5 1]*rbead/2 ,fluidbox(2,2)+[-.1 0 .1 0.5 1]*rbead/2],...
single([]));
set(hs,'edgecolor','none','facealpha',0.9)
axis equal, view(3),
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'Hertz Tensor xy';
xlabel('X'), ylabel('Y'), zlabel('Z')
title('Hertz tensor','fontsize',20)
step = [4 1 4];
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,.5,'color','k','LineWidth',.1)
caxis([-.1 .1]), view([-50,42])
%% Internal Solid stress
Xsolid = Xstress.ATOMS(isolid,:);
hcauchystress = 3*rbead; %1.25e-5; % m
Vsolid = buildVerletList(Xsolid,hcauchystress);
solidbox = [ min(Xsolid{:,coords}); max(Xsolid{:,coords}) ]';
boxcenter = mean(solidbox,2);
Vbead = mean(Xsolid.c_vol);
% interpolate the tensor stress
W = kernelSPH(hcauchystress,'lucy',3); % kernel for interpolation (not gradkernel!)
stresscomponents = arrayfun(@(i) sprintf('c_S[%d]',i),1:7,'UniformOutput',false);
boxcenter = mean(solidbox,2);
resolution = ceil(50 * diff(solidbox,[],2)'./max(diff(solidbox,[],2)));
xw = linspace(solidbox(1,1)-hcauchystress,solidbox(1,2)+hcauchystress,resolution(1));
yw = linspace(solidbox(2,1)-hcauchystress,solidbox(2,2)+hcauchystress,resolution(2));
zw = linspace(solidbox(3,1)-hcauchystress,solidbox(3,2)+hcauchystress,resolution(3));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZ = buildVerletList({XYZgrid Xsolid{:,coords}},1.001*hcauchystress); % special grid syntax
SXYZgrid = interp3SPHVerlet(Xsolid{:,coords},Xsolid{:,stresscomponents},XYZgrid,VXYZ,W,Vbead);
%% Pressure, von Mises equivalent stress
P = 1/3*reshape(sum(SXYZgrid(:,1:3),2),size(Xw));
vMS = reshape(SXYZgrid(:,7),size(Xw));
figure, hold on
hs = slice(Xw,Yw,Zw,vMS,[boxcenter(1) xw(end)],...
[boxcenter(2) yw(end)],...
[solidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
lighting gouraud, camlight('left'), axis equal, view(3) % shading interp
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'von Mises';
title('von Mises Stress','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
figure, hold on
hs = slice(Xw,Yw,Zw,P,[boxcenter(1) xw(end)],...
[boxcenter(2) yw(end)],...
[solidbox(3,1) boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.6)
lighting gouraud, camlight('left'), axis equal, view(3) % shading interp
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'P';
title('Pressure','fontsize',20)
xlabel('X'), ylabel('Y'), zlabel('Z')
EXAMPLE 3
This theoretical example shows how the Landshoff forces and stresses develop in "ideal" shear flows
several packing effects are analyzed: HCP, FCC, SC, SC2, SC3
INRAE\Olivier Vitrac, Han Chen
% EXAMPLE 3
% This theoretical example shows how the Landshoff forces and stresses develop in "ideal" shear flows
% several packing effects are analyzed: HCP, FCC, SC, SC2, SC3
%
% INRAE\Olivier Vitrac, Han Chen
% 2023-09-13 release candidate - scarce comments (agree), read example2bis.m for justifications
%% Input variables
rbead = 1.0414980e-05;
hLandshoff = 1.5*2*rbead; %1.25e-5; % m
configLandshoff = struct( ...
'gradkernel', kernelSPH(hLandshoff,'lucyder',3),...kernel gradient
'h', hLandshoff,...smoothing length (m)
'c0',0.32,...speed of the sound (m/s)
'q1',30,... viscosity coefficient (-)
'rho', 987.3691, ...density
'mass', 9.0418e-12,...bead mass
'vol', 9.1580e-15...bead volume
);
shearrate = 1; % Hz;
mu = configLandshoff.rho*configLandshoff.q1*configLandshoff.c0*configLandshoff.h/10;
% Dynamic simulation
packingmode = 'HCP';
% packingmode = 'FCC';
% packingmode = 'SC';
% packingmode = 'SC2';
% packingmode = 'SC3';
XYZ0 = packSPH(15,rbead,packingmode); nbeads = size(XYZ0,1);
fluidbox = [min(XYZ0);max(XYZ0)]';
XYZ0 = XYZ0 + [0 rbead 0];
fluidbox(2,2) = fluidbox(2,2) + 2*rbead;
fluidboxdim = diff(fluidbox,1,2);
boxcenter = mean(fluidbox,2)';
vmax = shearrate * fluidbox(2,2);
vx = vmax * XYZ0(:,2)/fluidbox(2,2);
[vy,vz] = deal(zeros(size(vx)));
vXYZ = [vx,vy,vz];
[~,i]=min(sum((XYZ0-boxcenter).^2,2));
insearch0 = false(nbeads,1); insearch0(i) = true;
V0 = buildVerletList(XYZ0,hLandshoff,[],[],[],~insearch0,insearch0);
j0 = V0{i}; j = j0; insearch = insearch0; insearch(j) = true; nj = length(j);
V = buildVerletList(XYZ0,hLandshoff,[],[],[],~insearch);
figure, hold on
XYZ = XYZ0;
others = ~insearch; others(j) = false;
hothers = plot3D(XYZ(others,:),'bo','markersize',8);
hj = plot3D(XYZ(j,:),'go','markerfacecolor','g','markersize',16);
hi = plot3D(XYZ(i,:),'ro','markerfacecolor','r','markersize',24);
xlabel('X'), ylabel('Y'),zlabel('Z'), title(sprintf('\\bf%s\\rm configuration',packingmode),'FontSize',18)
view(3), drawnow
tmax = 10*hLandshoff/vXYZ(i,1);
nt = 500; dt = tmax/(nt-1); t = (0:dt:(nt-1)*dt)';
Flandshoff = forceLandshoff(XYZ,vXYZ,V,configLandshoff);
[Fi,Fsum] = deal(zeros(nt,3)); Fj = zeros(nt,nj,3);
Fi(1,:) = Flandshoff(i,:);
Fj(1,:,:) = permute(Flandshoff(j,:),[1 3 2]);
ax = axis;
for it=2:nt
dispf('[%d/%d] iteration t=%0.3g s',it,nt,(it+1)*dt)
XYZ = XYZ + vXYZ*dt;
XYZ = XYZ-(XYZ(i,:)-XYZ0(i,:)); % i is not moving
% noise = (rand(nbeads,1)-0.5).*vXYZ*dt/2;
reflect = (XYZ(:,1)>fluidbox(1,2)); XYZ(reflect) = XYZ(reflect) - fluidbox(1,2) + fluidbox(1,1);
reflect = (XYZ(:,1)XData',XYZ(others,1),'YData',XYZ(others,2),'ZData',XYZ(others,3))
set(hj,'XData',XYZ(j,1),'YData',XYZ(j,2),'ZData',XYZ(j,3))
set(hi,'XData',XYZ(i,1),'YData',XYZ(i,2),'ZData',XYZ(i,3))
axis(ax), drawnow
Flandshoff = forceLandshoff(XYZ,vXYZ,V,configLandshoff);
Fi(it,:) = Flandshoff(i,:);
Fj(it,:,:) = permute(Flandshoff(j0,:),[1 3 2]);
Fsum(it,:) = sum(Flandshoff,1);
end
% plot
col = [0 0.4470 0.7410; 0.8500 0.3250 0.0980; 0.9290 0.6940 0.1250];
hfig=figure; set(hfig,'defaultAxesColorOrder',col), hold on
hp = [
plot(t,Fi,'-','linewidth',6);
plot(t,Fsum,'--','linewidth',4);
];
legend(hp,{'F_x','F_y','F_z','sum F_x','sum F_y','sum F_z'},'fontsize',20,'Location','best','AutoUpdate','off')
plot(t,Fj(:,:,1),':','color',col(1,:),'linewidth',2);
plot(t,Fj(:,:,2),':','color',col(2,:),'linewidth',2);
plot(t,Fj(:,:,3),':','color',col(3,:),'linewidth',2);
xlabel('time (s)','fontsize',20)
ylabel('Landshoff force','fontsize',20)
title(sprintf('\\bf%s\\rm',packingmode),'FontSize',18)
%% Static configuration
XYZ = packSPH(10,rbead,'FCC'); nbeads = size(XYZ,1);
fluidbox = [min(XYZ);max(XYZ)]';
XYZ = XYZ + [0 rbead 0];
fluidbox(2,2) = fluidbox(2,2) + 2*rbead;
fluidboxdim = diff(fluidbox,1,2);
boxcenter = mean(fluidbox,2);
vmax = shearrate * fluidbox(2,2);
vx = vmax * XYZ(:,2)/fluidbox(2,2);
[vy,vz] = deal(zeros(size(vx)));
vXYZ = [vx,vy,vz];
in = true(nbeads,1);
for i=1:3
in = in & (XYZ(:,i)>=(hLandshoff+fluidbox(i,1))) & (XYZ(:,i)<=(fluidbox(i,2)-hLandshoff));
end
% control
close all
figure, plot3D(XYZ,'ro'), view(3)
quiver3(XYZ(:,1),XYZ(:,2),XYZ(:,3),vXYZ(:,1),vXYZ(:,2),vXYZ(:,3),2,'linewidth',2,'color','k')
xlabel('X'), ylabel('Y'),zlabel('Z'), title('initial configuration')
% Landshoff forces
V = buildVerletList(XYZ,hLandshoff); % figure, hist(cellfun(@length,V))
[Flandshoff,Wlandshoff] = forceLandshoff(XYZ,vXYZ,V,configLandshoff);
flandshoff = sqrt(sum(Flandshoff.^2,2));
figure, plot3D(XYZ,'k.'), view(3)
quiver3(XYZ(in,1),XYZ(in,2),XYZ(in,3),Flandshoff(in,1),Flandshoff(in,2),Flandshoff(in,3),1,'linewidth',2,'color','r')
xlabel('X'), ylabel('Y'),zlabel('Z'), title('Landshoff forces'), view(-25,90);
figure, hist(Flandshoff(in,1)), xlabel('component X - Forcefield')
%% Grid interpolation of Landshoff forces
% number of grid points along the largest dimension
resolution = ceil(50 * diff(fluidbox,[],2)'./max(diff(fluidbox,[],2)));
xw = linspace(fluidbox(1,1),fluidbox(1,2),resolution(1));
yw = linspace(fluidbox(2,1),fluidbox(2,2),resolution(2));
zw = linspace(fluidbox(3,1),fluidbox(3,2),resolution(3));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZ = buildVerletList({XYZgrid XYZ},hLandshoff); % special grid syntax
% Interpolate Landshoff forces, extract components for plotting
W = kernelSPH(hLandshoff,'lucy',3); % kernel for interpolation (not gradkernel!)
FXYZgrid = interp3SPHVerlet(XYZ,Flandshoff,XYZgrid,VXYZ,W,configLandshoff.vol);
FXYZgridx = reshape(FXYZgrid(:,1),size(Xw));
FXYZgridy = reshape(FXYZgrid(:,2),size(Yw));
FXYZgridz = reshape(FXYZgrid(:,3),size(Zw));
% Interpolate the tensor xy
WXYZgrid = interp3SPHVerlet(XYZ,Wlandshoff(:,2),XYZgrid,VXYZ,W,configLandshoff.vol);
WXYZgrid = reshape(WXYZgrid,size(Xw));
%% === PLot WLandshoff - Landshoff forces only
figure, hold on
hs = slice(Xw,Yw,Zw,WXYZgrid, ...
[xw(1)+2*hLandshoff boxcenter(1)],...
[yw(2)+2*hLandshoff boxcenter(2) yw(end)-2*hLandshoff],...
[fluidbox(3,1)+2*hLandshoff boxcenter(3)]);
set(hs,'edgecolor','none','facealpha',0.9)
axis equal, view(3),
hc = colorbar('AxisLocation','in','fontsize',14); hc.Label.String = 'Landshoff along X';
xlabel('X'), ylabel('Y'), zlabel('Z')
title('Landshoff forces along X','fontsize',20)
step = [3 5 5];
quiver3( ...
Xw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Yw(1:step(1):end,1:step(2):end,1:step(3):end), ...
Zw(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridx(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridy(1:step(1):end,1:step(2):end,1:step(3):end), ...
FXYZgridz(1:step(1):end,1:step(2):end,1:step(3):end) ...
,1,'color','k','LineWidth',2)
EXPLORE look for files matching a given or a list of formats
SYNTAX
filelist = explore(format, [path],[depth],['abbreviate' or 'fullabbreviate'])
INPUTS
format = string ou cells of string (see isformat for details, very powerfull)
format are not case sensitive (required on WIN machines)
Main metacharacters based on the old syntax of isformat
? = any character
# = any digit
#* = any number (list of digits)
~ = not a digit
@ = not a word character ~([a-z_A-Z0-9])
* = any list of characters
==> any combinations and occurences are possible
path = any valid path (local or remote) or current directory
several paths can be used as {'path1' 'path2' ....}
maxdepth = maximum of examined levels (par d�faut = 10)
FLAG abbreviate can be
'abbreviate' generate a simplified structure of filenames
'fullabbreviate' generate only a cell string of filenames
CONVERSION
Conversion between formats is possible with:
filelist = explore(filelist) which is equivalent to filelist = explore(filelist,'fullabbreviate')
filelist = explore(filelist,'abbreviate');
function list = explore(format,pathstr,depth,abbreviate,recursionprefix)
% EXPLORE look for files matching a given or a list of formats
% SYNTAX
% filelist = explore(format, [path],[depth],['abbreviate' or 'fullabbreviate'])
% INPUTS
% format = string ou cells of string (see isformat for details, very powerfull)
% format are not case sensitive (required on WIN machines)
% Main metacharacters based on the old syntax of isformat
% ? = any character
% # = any digit
% #* = any number (list of digits)
% ~ = not a digit
% @ = not a word character ~([a-z_A-Z0-9])
% * = any list of characters
% ==> any combinations and occurences are possible
% path = any valid path (local or remote) or current directory
% several paths can be used as {'path1' 'path2' ....}
% maxdepth = maximum of examined levels (par d�faut = 10)
% FLAG abbreviate can be
% 'abbreviate' generate a simplified structure of filenames
% 'fullabbreviate' generate only a cell string of filenames
% CONVERSION
% Conversion between formats is possible with:
% filelist = explore(filelist) which is equivalent to filelist = explore(filelist,'fullabbreviate')
% filelist = explore(filelist,'abbreviate');
% TCPIP v. 1.0 - 26/02/01 - INRA\Olivier Vitrac rev. 2023-04-27
% Revision history
% 25/02/04 abbreviate format
% 01/03/04 subpath
% 14/05/04 fix bug when an empty list occures with abbreviate format
% 25/01/05 fix case profondeur <= 1
% 10/02/05 fix fieldnames ~pathstr -> ~path
% 06/03/05 add fields bytes and date
% 04/12/05 update to new formats (see isformat), add 'full abbreviate'
% 20/01/08 add post conversions
% 24/01/08 improve compatibility with Matlab 7.5
% 04/01/11 fix versn in FILEPARTS for Matlab later than 7.11 (not supported any more)
% 04/01/11 add datenum
% 21/07/17 fix subpath (change sizeofroot+2:end to sizeofroot+1:end)
% 30/11/17 remove the use of cd as it fails with onedrive
% 29/12/17 add recursion when pathstr is a cell array of string
% 20/07/18 add recursionprefix to include lastdir in subpath
% 22/07/18 initialize recursionprefix to '' instead of false
% 2023-04-27 accepts a comparison with a pattern without using literals '*','+'
% default
profondeur_default = 19;
found = false;
oldmatlab = verLessThan('matlab','7.11');
% arg check
if nargin<2, pathstr = []; end
if isstruct(format), list = abbreviateform(format,pathstr,oldmatlab); return, end
if nargin<3, depth = []; end
if nargin<4, abbreviate = 'none'; end
if nargin<5, recursionprefix = ''; end
if isempty(pathstr), pathstr = pwd; end
if isempty(depth), depth = profondeur_default; end
if isempty(recursionprefix), recursionprefix = ''; end
recursionflagprefix = ~isempty(recursionprefix);
% recursion if multiple paths
if iscell(pathstr)
list = explore(format,pathstr{1},depth,abbreviate,lastdir(pathstr{1}));
for i=2:length(pathstr)
list = [list, explore(format,pathstr{i},depth,abbreviate,lastdir(pathstr{i}))];
end
return
end
sizeofroot = length(pathstr);
currentfilesep = filesep;
% exploration
if depth <=1
rawlist = dir(fullfile(pathstr,format));
j = 0;
list = repmat(struct('path','','file','','subpath','','name','','ext','','ver','','date','','datenum',[],'bytes',[]),0,0);
for i = 1:length(rawlist)
if ~rawlist(i).isdir
j = j + 1;
if oldmatlab
[p,n,e,v] = fileparts(rawlist(i).name); %#ok<*FPART,ASGLU>
date_num = datenum(rawlist(i).date);
else
[p,n,e] = fileparts(rawlist(i).name); %#ok
v = NaN;
date_num = rawlist(i).datenum;
end
list(j) = struct( 'path', pathstr, ...
'file', rawlist(i).name, ...
'subpath','', ...
'name',n, ...
'ext',e, ...
'ver',v,...
'date',rawlist(i).date,...
'datenum',date_num,...
'bytes',rawlist(i).bytes);
found = true;
end
end
list = abbreviateform(list,abbreviate,oldmatlab);
else
%origine = pwd; % fix 30/11/2017
mycd = pathstr; %cd(pathstr) %#ok<*MCCD> % fix 30/11/2017
i_prof = 1 ;
fich = cell(depth,1);
fich{i_prof} = dir(mycd);
i_fich = zeros(depth,1);
i_fich(i_prof) = 1;
i_list = 1;
list = [];
while (i_prof <= depth) && (i_prof > 0)
if i_fich(i_prof) > length(fich{i_prof})
mycd = rootdir(mycd); %cd('..') % fix 30/11/2017
i_prof = i_prof - 1;
else
format_match = isformat(fich{i_prof}(i_fich(i_prof)).name,format);
if ischar(format) && isempty(format_match)
if strcmp(fich{i_prof}(i_fich(i_prof)).name,format) % enable the comparison without literals
format_match = 1;
end
end
if fich{i_prof}(i_fich(i_prof)).isdir && fich{i_prof}(i_fich(i_prof)).name(1) ~= '.'
mycd = fullfile(mycd,fich{i_prof}(i_fich(i_prof)).name); %cd(fich{i_prof}(i_fich(i_prof)).name) % fix 30/11/2017
i_prof = i_prof + 1;
i_fich(i_prof) = 0;
fich{i_prof} = dir(mycd); % fix 30/11/2017
elseif any(format_match) && fich{i_prof}(i_fich(i_prof)).bytes > 0
found = true;
list(i_list).format_match = format_match; %#ok<*AGROW>
list(i_list).info.path = mycd; % fix 30/11/2017
list(i_list).info.file = fich{i_prof}(i_fich(i_prof)).name;
if length(list(i_list).info.path)>sizeofroot+1
list(i_list).info.subpath = list(i_list).info.path(sizeofroot+1:end); % before 21/07/2017 : (sizeofroot+1:end)
else
list(i_list).info.subpath = currentfilesep;
end
if recursionflagprefix % added 20/07/2018
list(i_list).info.subpath = fullfile(recursionprefix,list(i_list).info.subpath);
end
list(i_list).info.date = fich{i_prof}(i_fich(i_prof)).date;
if oldmatlab
list(i_list).info.datenum = datenum(list(i_list).info.date);
else
list(i_list).info.datenum = fich{i_prof}(i_fich(i_prof)).datenum;
end
list(i_list).info.bytes = fich{i_prof}(i_fich(i_prof)).bytes;
i_list = i_list + 1;
end
end
if i_prof>0, i_fich(i_prof) = i_fich(i_prof) + 1; end
end
%cd(origine); % fix 30/11/2017
% abbreviate
if any(abbreviate), list = abbreviateform(list,abbreviate,oldmatlab); end
end
if ~found, list = []; end
function listout = abbreviateform(list,abbreviate,oldmatlab)
% ABBREVIATE FORM
if isempty(abbreviate), abbreviate = 'fullabbreviate'; end
switch lower(abbreviate)
case 'abbreviate'
if isempty(list) || ~isfield(list,'info')
listout=list;
else
listout = struct2cell(list);
listout = [listout{2,:,:}];
for i=1:length(listout)
if oldmatlab
[p,n,e,v] = fileparts(listout(i).file); %#ok
else
[p,n,e] = fileparts(listout(i).file); %#ok
v = NaN;
end
listout(i).name = n;
listout(i).ext = e(2:end);
listout(i).ver = v;
end
end
case 'fullabbreviate'
nlist = length(list);
listout = cell(nlist,1);
if isfield(list,'info')
for i=1:nlist, listout{i} = fullfile(list(i).info.path,list(i).info.file); end
elseif isfield(list,'file')
for i=1:nlist, listout{i} = fullfile(list(i).path,list(i).file); end
else
listout = list;
end
otherwise
listout=list;
end
% function list = explore(format,pathstr,profondeur,abbreviate)
% % EXPLORE look for files matching a given or a list of formats
% % Syntax: filelist = explore(format, [path],[depth],['abbreviate' or 'fullabbreviate'])
% % format = string ou cells of string (see isformat for details, very powerfull)
% % format are not case sensitive (required on WIN machines)
% % Main metacharacters based on the old syntax of isformat
% % ? = any character
% % # = any digit
% % #* = any number (list of digits)
% % ~ = not a digit
% % @ = not a word character ~([a-z_A-Z0-9])
% % * = any list of characters
% % ==> any combinations and occurences are possible
% % path = any valid path (local or remote) or current directory
% % maxdepth = maximum of examined levels (par d�faut = 10)
% % 'abbreviate' generate a simplified structure of filenames
% % 'fullabbreviate' generate only a cell string of filenames
% %
% % Conversion between formats is possible with:
% % filelist = explore(filelist) which is equivalent to filelist = explore(filelist,'fullabbreviate')
% % filelist = explore(filelist,'abbreviate');
%
% % TCPIP v. 1.0 - 26/02/01 - INRA\Olivier Vitrac rev. 06/05/05
%
% % Revision history
% % 25/02/04 abbreviate format
% % 01/03/04 subpath
% % 14/05/04 fix bug when an empty list occures with abbreviate format
% % 25/01/05 fix case profondeur <= 1
% % 10/02/05 fix fieldnames ~pathstr -> ~path
% % 06/03/05 add fields bytes and date
% % 04/12/05 update to new formats (see isformat), add 'full abbreviate'
% % 20/01/08 add post conversions
%
% % default
% profondeur_default = 19;
% extsep = '.';
% found = false;
%
% % arg check
% if nargin<2, pathstr = []; end
% if isstruct(format), list = abbreviateform(format,pathstr); return, end
% if nargin<3, profondeur = []; end
% if nargin<4, abbreviate = 'none'; end
% if isempty(pathstr), pathstr = cd; end
% if isempty(profondeur), profondeur = profondeur_default; end
% sizeofroot = length(pathstr);
% currentfilesep = filesep;
%
% % exploration
% if profondeur <=1
% rawlist = dir(fullfile(pathstr,format));
% j = 0;
% for i = 1:length(rawlist)
% if ~rawlist(i).isdir
% j = j + 1;
% [p,n,e,v] = fileparts(rawlist(i).name);
% list(j) = struct( 'path', pathstr, ...
% 'file', rawlist(i).name, ...
% 'subpath','', ...
% 'name',n, ...
% 'ext',e, ...
% 'ver',v,...
% 'date',rawlist(i).date,...
% 'bytes',rawlist(i).bytes);
% found = true;
% end
% end
% list = abbreviateform(list,abbreviate);
% else
% origine = cd; cd(pathstr)
% i_prof = 1 ; pos = zeros(0,1);
% fich = cell(profondeur,1);
% fich{i_prof} = dir;
% i_fich = zeros(profondeur,1);
% i_fich(i_prof) = 1;
% i_list = 1;
% list = [];
% while (i_prof <= profondeur) & (i_prof > 0)
% if i_fich(i_prof) > length(fich{i_prof})
% cd('..')
% i_prof = i_prof - 1;
% else
% format_match = isformat(fich{i_prof}(i_fich(i_prof)).name,format);
% if fich{i_prof}(i_fich(i_prof)).isdir & fich{i_prof}(i_fich(i_prof)).name(1) ~= '.'
% cd(fich{i_prof}(i_fich(i_prof)).name)
% i_prof = i_prof + 1;
% i_fich(i_prof) = 0;
% fich{i_prof} = dir;
% elseif any(format_match) & fich{i_prof}(i_fich(i_prof)).bytes > 0
% found = true;
% list(i_list).format_match = format_match;
% list(i_list).info.path = cd;
% list(i_list).info.file = fich{i_prof}(i_fich(i_prof)).name;
% if length(list(i_list).info.path)>sizeofroot+1
% list(i_list).info.subpath = list(i_list).info.path(sizeofroot+2:end);
% else
% list(i_list).info.subpath = currentfilesep;
% end
% list(i_list).info.date = fich{i_prof}(i_fich(i_prof)).date;
% list(i_list).info.bytes = fich{i_prof}(i_fich(i_prof)).bytes;
% i_list = i_list + 1;
% end
% end
% if i_prof>0, i_fich(i_prof) = i_fich(i_prof) + 1; end
% end
% cd(origine);
%
% % abbreviate
% if any(abbreviate), list = abbreviateform(list,abbreviate); end
% end
%
% if ~found, list = []; end
%
% function listout = abbreviateform(list,abbreviate)
% % ABBREVIATE FORM
% if isempty(abbreviate), abbreviate = 'fullabbreviate'; end
% switch lower(abbreviate)
% case 'abbreviate'
% if isempty(list) || ~isfield(list,'info')
% listout=list;
% else
% listout = struct2cell(list);
% listout = [listout{2,:,:}];
% for i=1:length(listout)
% [p,n,e,v] = fileparts(listout(i).file);
% listout(i).name = n;
% listout(i).ext = e(2:end);
% listout(i).ver = v;
% end
% end
% case 'fullabbreviate'
% nlist = length(list);
% listout = cell(nlist,1);
% if isfield(list,'info')
% for i=1:nlist, listout{i} = fullfile(list(i).info.path,list(i).info.file); end
% elseif isfield(list,'file')
% for i=1:nlist, listout{i} = fullfile(list(i).path,list(i).file); end
% else
% listout = list;
% end
% otherwise
% listout=list;
% end
FILEINFO returns a structure that contains the main information of a given file
Syntax: info = fileinfo(filename,[path])
Options: [info,str] = fileinfo(filename,[path,dispon,stringonly,noerror])
INPUTS
filename can include a full path or not
path is used if filename does not include it (the current path is used otherwise)
dispon (flag, default=true) prints on screen if true
stringonly (flag, default=false) returns a formated single string instead of a structure if true
noerror (flag, default=false) no error message if true, corresponding files appear with sizes 0
OUTPUTS
info = structure matching dir/ls properties (string if stringonly is true)
str = formated string
See also
explore, isformat, isformat_multiple, ls, dir
function [rout,strout] = fileinfo(filename,ppath,dispon,stringonly,noerror)
%FILEINFO returns a structure that contains the main information of a given file
% Syntax: info = fileinfo(filename,[path])
% Options: [info,str] = fileinfo(filename,[path,dispon,stringonly,noerror])
%
% INPUTS
% filename can include a full path or not
% path is used if filename does not include it (the current path is used otherwise)
% dispon (flag, default=true) prints on screen if true
% stringonly (flag, default=false) returns a formated single string instead of a structure if true
% noerror (flag, default=false) no error message if true, corresponding files appear with sizes 0
%
% OUTPUTS
% info = structure matching dir/ls properties (string if stringonly is true)
% str = formated string
%
% See also: explore, isformat, isformat_multiple, ls, dir
% MS-MATLAB 1.0 - 20/04/04 - INRA\Olivier Vitrac - rev. 03/06/19
% revision history
% 19/08/04: display if no ouput, added str
% 11/09/07 fix filename as cell array
% 12/01/11 fix versn in FILEPARTS for Matlab later than 7.11 (not supported any more)
% 30/12/12 cosmetic modifications
% 09/02/16 improved help, add noerror
% 03/06/19 fix date format on French computers
% arg check
if nargin<2, ppath = ''; end
if nargin<3, dispon = []; end
if nargin<4, stringonly = []; end
if nargin<5, noerror = []; end
if ~nargout || isempty(dispon), dispon = true; end
if isempty(stringonly), stringonly = false; end
if isempty(noerror), noerror = false; end
oldmatlab = verLessThan('matlab','7.11');
issomethingmissing = false;
if iscell(filename)
m = length(filename);
str = cell(m,1);
[r,str{1}] = fileinfo(filename{1},ppath,dispon);
for i=2:m, [r(i),str{i}] = fileinfo(filename{i},ppath,dispon); end
dispon = false;
else
% other checks
if oldmatlab
[pathstr,name,ext,versn] = fileparts(filename);
else
[pathstr,name,ext] = fileparts(filename);
versn = [];
end
if isempty(pathstr)
if isempty(ppath)
pathstr = cd;
else
pathstr = ppath;
end
end
if ~exist(pathstr,'dir')
if noerror
issomethingmissing = true;
else
error('the directory ''%s'' does not exist',pathstr)
end
end
fullfilename = fullfile(pathstr,[name ext versn]);
if ~exist(fullfilename,'file')
if noerror
issomethingmissing = true;
else
error('the file ''%s'' does not exist in ''%s''',[name ext versn],pathstr)
end
end
if issomethingmissing % faked info
f = struct('date',datestr(now),'bytes',0);
else % true info
f = dir(fullfilename);
if isfield(f,'datenum'), f.date = datestr(f.datenum); end
end
r =struct( 'filename', [name ext versn], ...
'name', name, ...
'ext', ext, ...
'ver', versn, ...
'date', f.date, ...
'bytes', f.bytes, ...
'path', pathstr...
);
if f.bytes>1024*1024
str = sprintf('\t%s%s\t\t%s\t\t%0.1f MBytes\t\t%s',name,ext,f.date,f.bytes/(1024*1024),pathstr);
elseif f.bytes>1024
str = sprintf('\t%s%s\t\t%s\t\t%0.1f kBytes\t\t%s',name,ext,f.date,f.bytes/1024,pathstr);
else
str = sprintf('\t%s%s\t\t%s\t\t%d Bytes\t\t%s',name,ext,f.date,f.bytes,pathstr);
end
end
% output
if nargout>0, rout = r; end
if stringonly, rout = str; return, end
if nargout>1, strout = str; end
if dispon, disp(str), end
FILLSTREAMLINE2 fills streamline with distibuted and non-overlapping objects (beads)
USAGE: traj = fillstreamline2(s,Xgrid,Ygrid,Vxgrid,Vygrid [,dmax,l0,verbosity])
[traj,errout] = fillstreamline2(...)
Inputs:
s: n x 2 array coding for one single 2D streamline
1 x m cell array (output of streamline)
Xgrid: 2D (axb) array coding for X coordinates (output of meshgrid)
Ygrid: 2D (axb) array coding for Y coordinates (output of meshgrid)
Vxgrid: 2D (axb) array coding for x-component of velocity
Vygrid: 2D (axb) array coding for y-component of velocity
rbead: bead radius (default value = 1)
l0: initial position of first bead (curvilinear) (default value = 0)
verbose: flag to control verbosity (default = true)
Outputs:
traj: structure with fields (nb = number of objects/beads distributed along the streamline)
function [traj,errout] = fillstreamline2(s,Xgrid,Ygrid,Vxgrid,Vygrid,rbead,l0,verbose)
%FILLSTREAMLINE2 fills streamline with distibuted and non-overlapping objects (beads)
%
% USAGE: traj = fillstreamline2(s,Xgrid,Ygrid,Vxgrid,Vygrid [,dmax,l0,verbosity])
% [traj,errout] = fillstreamline2(...)
%
% Inputs:
% s: n x 2 array coding for one single 2D streamline
% 1 x m cell array (output of streamline)
% Xgrid: 2D (axb) array coding for X coordinates (output of meshgrid)
% Ygrid: 2D (axb) array coding for Y coordinates (output of meshgrid)
% Vxgrid: 2D (axb) array coding for x-component of velocity
% Vygrid: 2D (axb) array coding for y-component of velocity
% rbead: bead radius (default value = 1)
% l0: initial position of first bead (curvilinear) (default value = 0)
% verbose: flag to control verbosity (default = true)
%
% Outputs:
% traj: structure with fields (nb = number of objects/beads distributed along the streamline)
% streamline: n×2 original streamline
% curvilinear: n×1 curvilinear coordinate along the streamline
% pseudotime: n×1 pseudotime to travel along the streamline
% curv_velocity: n×1 velocity along the curvilinear coordinate
% cart_velocity: n×2 cartesian velocity
% curv_distribution: nb×1 distribution of beads along the curvilinear coordinate
% cart_distribution: nb×2 corresponding cartesian coordinates
% t_distribution: nb×1 corresponding time
% curv_v_distribution: nb×1 corresponding velocity along the streamline
% cart_v_distribution: nb×2 corresponding cartesian velocity
%
% errout: error messages
% MS 3.0 | 2024-03-13 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-31
% Revision history
% 2024-03-17 fix help
% 2024-03-27 force nbmax to be at least one
% 2024-03-31 add verbose, errout, fix isempty(s)
%% default values
rbead_default = 1;
l0_default = 0;
verbose_default = true;
%% check arguments
if nargin<5, error('USAGE: traj = fillstreamline2(s,Xgrid,Ygrid,Vxgrid,Vygrid [,rbead,l0])'), end
if nargin<6, rbead = []; end
if nargin<7, l0 = []; end
if nargin<8, verbose = []; end
Xgridsiz = size(Xgrid);
if ~isequal(size(Ygrid),Xgridsiz), error('Ygrid should be of the same size (%d x %d) as Xgrid',Xgridsiz(1),Xgridsiz(2)), end
if ~isequal(size(Vxgrid),Xgridsiz), error('Vxgrid should be of the same size (%d x %d) as Xgrid',Xgridsiz(1),Xgridsiz(2)), end
if ~isequal(size(Vygrid),Xgridsiz), error('Vygrid should be of the same size (%d x %d) as Xgrid',Xgridsiz(1),Xgridsiz(2)), end
if isempty(rbead), rbead = rbead_default; end
if isempty(l0), l0 = l0_default; end
if isempty(verbose), verbose = verbose_default; end
errmsg = {};
if isempty(s), error('no streamline to fill (uniform velocity field?)'), end
%% pseudo recursion
if iscell(s)
ns = length(s);
if ns<2 % only one streamline (no recursion)
s = s{1};
else % more than one streamline
listerrmsg = [];
screen = ''; t0_ = clock;
for i=1:ns
[tmp,errmsg] = fillstreamline2(s{i},Xgrid,Ygrid,Vxgrid,Vygrid,rbead,l0,false);
dt = etime(clock,t0_); %#ok
if dt>0.5
done = 100 * i/ns;
screen = dispb(screen,'FILLSTREAMLINE2 [%d/%d] fill 2D streamline with %d beads (resolution: %d segments) | done %0.3g %% | elapsed %0.4g s | remaining %0.4g s',...
i,ns,length(tmp.t_distribution),size(tmp.streamline,1),done,dt,(100/done-1)*dt);
end
if i==1, traj = repmat(tmp,ns,1); else, traj(i) = tmp; end
for j = 1:length(errmsg)
if ~ismember(errmsg{j},listerrmsg)
screen = ''; dispf('%s\n\t(future occurrences of the same message will not be repeated)',errmsg{j})
listerrmsg{end+1} = errmsg{j}; %#ok
end
end
end % next i
return
end % ns>1
end
%% check argument (continued)
[n0,d] = size(s);
if d~=2, error('The streamline should be n*2 array'), end
valid = all(~isnan(s),2);
s = s(valid,:);
n = size(s,1);
if n'FILTSTREAMLINE2: %d points on %d were removed from the streamline',n0-n,n0); end
if n==0, error('the streamline is empty'), end
%% curvilinear coordinate
dl = sqrt(sum(diff(s,1,1).^2,2));
l = cumsum([0;dl],1);
nbmax = max(1,l(end)/(2*rbead)); % total number of beads
%dmean = nbmax/l(end); % mean-density
%% calculate the Tangent
T = zeros(n,d);
% first point (forward difference)
T(1,:) = s(2,:) - s(1,:);
% middle points (central differences)
T(2:end-1,:) = s(3:end,:) - s(1:end-2,:);
% end point (backward difference)
T(end,:) = s(end,:) - s(end-1,:);
%normalize the tangent
T = T./sqrt(sum(T.^2,2));
%% interpolate the velocity along the streamline
Vxy = [
interp2(Xgrid,Ygrid,Vxgrid,s(:,1),s(:,2)),...
interp2(Xgrid,Ygrid,Vygrid,s(:,1),s(:,2))
];
v = dot(Vxy,T,2);
%% reverse the streamline if the flow is in the other direction
if mean(v,1,'omitmissing')<0
errmsg{end+1} = 'FILTSTREAMLINE2: streamline upsidedown detected';
if verbose, dispf(errmsg{end}), end
traj = fillstreamline2(flipud(s),Xgrid,Ygrid,Vxgrid,Vygrid,rbead,l0,verbose);
return
end
%% fix bad v value
ibadv = find(isnan(v));
if ~isempty(ibadv)
errmsg{end+1} = 'FILTSTREAMLINE2: bad velocity detected (fixed with extrapolation)';
if verbose, dispf(errmsg{end}), end
igoodv = setdiff((1:n)',ibadv);
v(ibadv) = interp1(igoodv,v(igoodv),ibadv,'linear','extrap');
end
%% traveling time (backward: (li+1-li)/Vi) and total time
dt = diff(l)./v(1:end-1);
t = cumsum([0;dt]);
%% condition of steady-state: rate = minimum rate (e.g., # cars/beads per time unit)
% The rate is the same between all li and li+1 and it's determined by the lowest rate.
ratemin = min(1./dt); %condition of steady state:
%% condition of uniform density along each segment
density = ratemin*dt./dl; % # cars/beads per length
dnb = density .* dl; % # beads (not scaled)
dnb = [0;dnb/sum(dnb)] * nbmax; % variation of the number of beads between segment
nb = cumsum(dnb);
nb = nb-interp1(l,nb,l0)+1; % first bead at l0
nb(nb<0) = 0;
%% evaluation positions
ok = nb>=1;
nbok = nb(ok);
lok = l(ok);
if any(nbok==0), error('error at least two positions are identical in the streamline'), end
pos = interp1(nbok,lok,(1:nbmax)');
pos = pos(~isnan(pos));
%% build the trajectory (fake trajectory)
traj = struct( ...
'streamline',s,...
'curvilinear',l,...
'pseudotime',t,...
'curv_velocity',v,...
'cart_velocity',Vxy,...
'curv_distribution',pos,...
'cart_distribution',interp1(l,s,pos),...
't_distribution',interp1(l,t,pos),...
'curv_v_distribution',interp1(l,v,pos), ...
'cart_v_distribution',interp1(l,Vxy,pos) ...
);
if nargout>1
errout = errmsg;
end
FITCIRCLEFROMPOINTS fit a circle from a set of points and calculate the missing information such as the radius, centroid, and angles
Syntax: circleFit = fitCircleFromPoints(XY)
XY is a Nx2 matrix where N is the number of points
circleFit is a structure with fields
center: [xcenter y center]
radius: radius
minAngle: minimum angle
maxAngle: maximum angle
angles: corresponding angles
XYgenerator: returns the position XY from an angles
angleGenerator: returns the angles from positions XY
function circleFit = fitCircleFromPoints(XY)
%FITCIRCLEFROMPOINTS fit a circle from a set of points and calculate the missing information such as the radius, centroid, and angles
%
% Syntax: circleFit = fitCircleFromPoints(XY)
% XY is a Nx2 matrix where N is the number of points
% circleFit is a structure with fields
% center: [xcenter y center]
% radius: radius
% minAngle: minimum angle
% maxAngle: maximum angle
% angles: corresponding angles
% XYgenerator: returns the position XY from an angles
% angleGenerator: returns the angles from positions XY
% MS 3.0 | 2024-03-23 | INRAE\han.chen@inrae.fr, INRAE\Olivier.vitrac@agroparistech.fr | rev.
% Revision history
% Initial estimate of the center using the centroid of the points
initialCenter = mean(XY, 1);
% Define the objective function for the least squares problem
objectiveFunction = @(params) sum(((XY(:,1) - params(1)).^2 + (XY(:,2) - params(2)).^2 - params(3)^2).^2);
% Initial guess for [center_x, center_y, radius]
initialGuess = [initialCenter, sqrt(var(XY(:,1)) + var(XY(:,2)))];
% Optimize using fminsearch or another optimization function
optimizedParams = fminsearch(objectiveFunction, initialGuess);
% Extract the optimized center and radius
center = optimizedParams(1:2);
radius = optimizedParams(3);
% Calculate shifted points and angles
XY_shifted = XY - center;
angles = atan2(XY_shifted(:,2), XY_shifted(:,1));
% Calculate the full angle range of the given points
minAngle = min(angles);
maxAngle = max(angles);
% Return results in a structure
circleFit.center = center;
circleFit.radius = radius;
circleFit.minAngle = minAngle;
circleFit.maxAngle = maxAngle;
circleFit.angles = angles;
circleFit.XYgenerator = @(a) [cos(a(:)) sin(a(:))]*radius + center;
circleFit.angleGenerator = @(xy) atan2(xy(:,2)-center(2), xy(:,1)-center(1));
FORCEHERTZ calculates Hertz contact forces for all atoms assuming that the Verlist list V has been partitioned (coord X)
Syntax: F = forceHertz(X,[,V,config,verbose])
[F,W,n] = forceHertz()
Inputs
X: nX x 3 coordinates of atoms
V: nX x 1 cell array (output of partitionVerletList)
a partition is required
V{i} should include all atoms which are not of the same type of i
config: 2x1 structure array with fields
R: radius
E: elasticity modulus
rho: density
m: mass of the bead
Example
config = struct('R',{1 1},'E',{1e6 1e6})
Example
Output (single output provided):
F: nX x 3 vector of Hertz forces
with F{i} = mi x 3 forces
Example
Outputs (two outputs provided):
F: nX x 1 Hertz force magnitudes
W: nX x 9 virial stress tensor (use reshape(W(i),3,3) to recover the matrix)
n: nX x 3 normal vectors
function [F,Wout,nout] = forceHertz(X,V,config,verbose)
%FORCEHERTZ calculates Hertz contact forces for all atoms assuming that the Verlist list V has been partitioned (coord X)
%
% Syntax: F = forceHertz(X,[,V,config,verbose])
% [F,W,n] = forceHertz()
%
% Inputs
% X: nX x 3 coordinates of atoms
% V: nX x 1 cell array (output of partitionVerletList)
% a partition is required
% V{i} should include all atoms which are not of the same type of i
% config: 2x1 structure array with fields
% R: radius
% E: elasticity modulus
% rho: density
% m: mass of the bead
%
% example: config = struct('R',{1 1},'E',{1e6 1e6})
%
% Output (single output provided):
% F: nX x 3 vector of Hertz forces
% with F{i} = mi x 3 forces
%
% Outputs (two outputs provided):
% F: nX x 1 Hertz force magnitudes
% W: nX x 9 virial stress tensor (use reshape(W(i),3,3) to recover the matrix)
% n: nX x 3 normal vectors
% MS 3.0 | 2023-04-02 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2023-09-13
% Revision history
% 2023-04-02 rename forceHertz into forceHertzAB, to avoid the confusion with the new forceHertz
% 2023-04-03 WJ. update the force Hertz equation to that as set in the SMD source code of LAMMPS:
% lammps-2022-10-04/src/MACHDYN/pair_smd_hertz.cpp ln. 169
% 2023-04-03 WJ. add an additional delta calculation step
% 2023-09-07 add W (virial stress)
% 2023-09-09 local virial stress fixed to match https://doi.org/10.1016/j.cplett.2019.07.008
% 2023-09-13 update to match new rules and fixes from forceLandshoff
%% Check arguments
if nargin<1, X = []; end
if nargin<2, V = []; end
if isempty(X), error('one table is at least required with ''x'',''y'',''z'',''type'' columns'), end
if istable(X)
typ = X.type;
if isempty(V)
V = partitionVerletList(buildVerletList(X),typ);
end
vol = X.c_vol;
X = table2array(X(:,{'x','y','z'}));
else
vol = [];
end
classX = class(X);
if nargin<3, config = []; end
if nargin<4, verbose = []; end
if isnumeric(V) && ~isnan(V) && length(V)==1
error('a partioned Verletlist is required');
end
if ~isstruct(config), error('config must be a structure'), end
if ~isfield(config,'R'), error('the field R is missing'), end
if ~isfield(config,'E'), error('the field E is missing'), end
if length(config)==1, config = repmat(config,[2,1]); end
if length(config)~=2, error('config must be a 2x1 structure array'), end
[nX,dX] = size(X);
if length(V)~=nX, error('V must be a %d cell array created with buildVerletList()',nX), end
verbosedefault = nX > 1e7;
if isempty(verbose), verbose = verbosedefault; end
askvirialstress = (nargout==2);
if askvirialstress
%if ~isfield(config,'rho'), error('the field rho is missing'), end
%if ~isfield(config,'m'), error('the field m is missing'), end
%if ~isfield(config,'h'), error('the field h is missing'), end
%volmin = mean([config.m],'omitnan')/mean([config.rho],'omitnan');
%hmean = mean([config.h],'omitnan');
%vol = 4/3 * pi * hmean^3;
%if vol'the value of h (%0.4g) leads to a volume smaller than atoms (%0.4g)' ,hmean,volmin), end
if isempty(vol)
if ~isfield(config,'vol'), error('the field vol is missing'), end
vol = mean([config.vol],'omitnan');
end
if numel(vol)==1, vol = vol * ones(nX,1,classX); end
end
%% verbosity
if verbose
t0_ = clock; %#ok
t1_ = t0_;
screen = '';
if askvirialstress
dispf('Calculate Hertz contact forces + virial stress between [%d x %d] ATOMS...',nX,dX)
else
dispf('Calculate Hertz contact forces between [%d x %d] ATOMS...',nX,dX)
end
end
%% Evaluate forces
% parameters
E = sqrt(config(1).E*config(2).E); % Bertholet formula
rcut = config(1).R + config(2).R;
% allocate
F = zeros(nX,1,classX);
n = zeros(nX,dX,classX);
% for virial stress calculation;
if askvirialstress
W = zeros(nX,dX*dX,classX);
else
W = [];
end
stresstensor = zeros(dX,dX,classX);
% for all atoms
for i=1:nX
j = V{i}; % neighbors of i
if any(j) % if any neighbor of other type
rij = X(i,:) - X(j,:); % position vector j->i
rij2 = dot(rij,rij,2); % dot(rij,rij,2)
rij_d = sqrt(rij2); % norm
rij_n = rij ./ rij_d; % normalized vector
iscontact = rij_d < rcut;
stresstensor(:) = 0;
if any(iscontact) % if they are contact
%Fij = E * sqrt((rcut-rij_d(iscontact))*config(1).R*config(2).R/rcut) .* rij_n(iscontact,:);
% formula as set in the SMD source code of LAMMPS: lammps-2022-10-04/src/MACHDYN/pair_smd_hertz.cpp ln. 169
delta = rcut-rij_d(iscontact);
r_geom = config(1).R*config(2).R/rcut;
bulkmodulus = E/(3*(1-2*0.25));
Fij = 1.066666667 * bulkmodulus * delta .* sqrt(delta * r_geom).* rij_n(iscontact,:); % units N
Fbalance = sum(Fij,1);
F(i) = norm(Fbalance);
n(i,:) = Fbalance/F(i);
if askvirialstress
stresstensor(:) = 0;
for ineigh = 1:length(find(iscontact))
% -rij' * Fij is the outerproduct (source: doi:10.1016/j.ijsolstr.2008.03.016)
stresstensor = stresstensor -rij(ineigh,:)' * Fij(ineigh,:);
end
W(i,:) = stresstensor(:)'/ (2*vol(i)); % origin of 2 ?? in this case (since j-->i not considered);
end
else
n(i,:) = sum(rij_n,1);
end
end % if any neighbor of other type
% verbosity
t_ = clock; %#ok
if verbose && (mod(i,1000)==0 || (etime(t_,t1_)>0.5)) %#ok<*DETIM>
t1_=t_; dt_ = etime(t_,t0_); done_ = i/nX;
screen = dispb(screen,'[atom %d:%d] FORCE Hertz Contacts | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,nX,dt_,100*done_,(1/done_-1)*dt_);
end
end
%% outputs
if nargout>1, Wout = W; end
if nargout>2
nout = n;
else
F = F .* n;
end
%% verbosity
if verbose
if askvirialstress
dispb('\t ... done with virial stress in %0.3g s for %d ATOMS',etime(clock,t0_),nX); %#ok
else
dispb('\t ... done in %0.3g s for %d ATOMS',etime(clock,t0_),nX); %#ok
end
end
FORCEHERTZAB calculates Hertz contact forces between atoms A (coord XA) and B (coord B)
Syntax: FAB = forceHertzAB(XA,XB,config)
[FAB,nAB] = forceHertzAB(...)
Inputs:
XA: nA x 3 coordinates of atoms A
XB: nB x 3 coordinates of atoms B
config: 2x1 structure array with fields
R: radius
E: elasticity modulus
Example
config = struct('R',{1 1},'E',{1e6 1e6})
Example
Outputs (single output provided):
FAB: (nA*nB) x 3 Hertz forces
Example
Outputs (two outputs provided):
FAB: (nA*nB) x 1 vector of force magnitudes
nAB: (nA*nB) x 3 coordinates of unitary direction vectors AB
Example
TODO list
The code is vectorized and may generate an Overflow/OutOfMemory error
an iterative version with automatic switch must be implemented
Example
Example
See also
function [FAB,nAB] = forceHertzAB(XA,XB,config,verbose)
%FORCEHERTZAB calculates Hertz contact forces between atoms A (coord XA) and B (coord B)
%
% Syntax: FAB = forceHertzAB(XA,XB,config)
% [FAB,nAB] = forceHertzAB(...)
%
% Inputs:
% XA: nA x 3 coordinates of atoms A
% XB: nB x 3 coordinates of atoms B
% config: 2x1 structure array with fields
% R: radius
% E: elasticity modulus
% example: config = struct('R',{1 1},'E',{1e6 1e6})
%
% Outputs (single output provided):
% FAB: (nA*nB) x 3 Hertz forces
%
% Outputs (two outputs provided):
% FAB: (nA*nB) x 1 vector of force magnitudes
% nAB: (nA*nB) x 3 coordinates of unitary direction vectors AB
%
% TODO list
% The code is vectorized and may generate an Overflow/OutOfMemory error
% an iterative version with automatic switch must be implemented
%
%
% See also: forceHertz, forceLandshoff
% MS 3.0 | 2023-03-25 | INRAE\Olivier.vitrac@agroparistech.fr | rev.
% Revision history
% 2023-04-02 rename forceHertz into forceHertzAB, to avoid the confusion with the new forceHertz
% 2023-04-03 WJ. update the force Hertz equation to that as set in the SMD source code of LAMMPS:
% lammps-2022-10-04/src/MACHDYN/pair_smd_hertz.cpp ln. 169
% 2023-04-03 WJ. add an additional delta calculation step
% 2023-04-03 WJ. wrap particles along the x direction
%% Check arguments
if nargin<3, error('3 inputs are required: [FAB,nAB] = forceHertz(XA,XB,config)'), end
if nargin<4, verbose = []; end
if ~isstruct(config), error('config must be a structure'), end
if ~isfield(config,'R'), error('the field R is missing'), end
if ~isfield(config,'E'), error('the field E is missing'), end
if length(config)==1, config = repmat(config,[2,1]); end
if length(config)~=2, error('config must be a 2x1 structure array'), end
[nA,dA] = size(XA);
[nB,dB] = size(XB);
if dA ~= dB, error('the number of dimensions of XA (%d) and XB (%d) are not compatible',dA,dB), end
if dA~=3 || dB~=3, warning('the number of dimensions (%d) are not standard',dA), end
verbosedefault = nA * nB>1e7;
if isempty(verbose), verbose = verbosedefault; end
%% Evaluate all AB vectors (full vectorized code)
% all B-A combinations are calculated
% note that the classes are preserved (single is recommended)
if verbose
t0 = clock; %#ok
dispf('Calculate Hertz contact forces between [%d x %d] and [%d x %d] atoms...',nA,dA,nB,dB)
end
AB = reshape( bsxfun(@minus,... the operation B-A is done within the builtin bsxfun
reshape(XB,1,nB,dB),... B values are placed column-wise
reshape(XA,nA,1,dA)),... A values are placed row-wise
nA*nB,3); % note that bsxfun will expand implicitely dimensions 1 and 2
% wrap coordinates at the edges of the 2 mm box
iswrapcontactpositive = AB(:,1)>0.002-(config(1).R + config(2).R);
iswrapcontactnegative = AB(:,1)<-0.002+(config(1).R + config(2).R);
ABwrappositive = AB(iswrapcontactpositive,:);
ABwrappositive(:,1) = ABwrappositive(:,1) - 0.002;
ABwrapnegative = AB(iswrapcontactnegative,:);
ABwrapnegative(:,1) = ABwrapnegative(:,1) + 0.002;
AB = vertcat(AB, ABwrappositive);
AB = vertcat(AB, ABwrapnegative);
rAB = sqrt(sum(AB.^2,2)); % distances AB
AB = AB./rAB; % normalized vectors
% n.b. data for the simulation box needed for a more generic wrap implementation
%% Evaluated forces
E = sqrt(config(1).E*config(2).E); % Bertholet formula
rcut = config(1).R + config(2).R; % cut distance
FAB = zeros(length(AB),1,class(AB)); % preallocate
iscontact = rAB% true if contact, false otherwise
% formula as set in the slide of Billy
% FAB(iscontact) = E * sqrt((rcut-rAB(iscontact))*config(1).R*config(2).R/rcut);
% formula as set in the SMD source code of LAMMPS: lammps-2022-10-04/src/MACHDYN/pair_smd_hertz.cpp ln. 169
delta = rcut-rAB(iscontact);
r_geom = config(1).R*config(2).R/rcut;
bulkmodulus = E/(3*(1-2*0.25));
FAB(iscontact) = 1.066666667 * bulkmodulus * delta .* sqrt(delta * r_geom); % units N
%% Output management
if nargout>1
nAB = AB;
else
FAB = FAB .* AB;
end
if verbose
dispf('\t ... done in %0.3g s',etime(clock,t0)) %#ok
end
FORCELANDSHOFF calculates Landshoff forces between fluid atoms (coord X, Verlist list V)
Syntax: F = forceLandshoff(X,vX [,V,config,verbose])
[F,W,n] = forceLandshoff()
Inputs
X: nX x 3 coordinates of atoms
vX: nX x 3 velocity components
V: nX x 1 cell array (output of buildVerletList)
with V{i} = 1 x mi index of neighbors (mi = number of neighbors within the cutoff distance)
config: structure with fields
gradkernel: gradient kernel (default value = kernelSPH(h,'lucyder',3))
it anonymous function depending only the radial distance r @(r)...., not on h
h: smoothing length
c0: speed of the sound
q1: q1 coefficient
rho: density (default value based on X.c_rho_smd if X is a table, 1000 otherwise)
mass: mass of the bead (default value based on X.mass if X is a table, 1 otherwise)
vol: volume of the bead (default value based on X.c_vol if X is a table, 1 otherwise)
Output (single output provided):
F: nX x 3 vector of Landshoff forces
with F{i} = mi x 3 forces
Outputs (two outputs provided):
F: nX x 1 Landshoff force magnitudes
W: nX x 9 virial stress tensor (use reshape(W(i),3,3) to recover the matrix)
n: nX x 3 normal vectors
function [F,Wout,nout] = forceLandshoff(X,vX,V,config,verbose)
%FORCELANDSHOFF calculates Landshoff forces between fluid atoms (coord X, Verlist list V)
%
% Syntax: F = forceLandshoff(X,vX [,V,config,verbose])
% [F,W,n] = forceLandshoff()
%
% Inputs
% X: nX x 3 coordinates of atoms
% vX: nX x 3 velocity components
% V: nX x 1 cell array (output of buildVerletList)
% with V{i} = 1 x mi index of neighbors (mi = number of neighbors within the cutoff distance)
% config: structure with fields
% gradkernel: gradient kernel (default value = kernelSPH(h,'lucyder',3))
% it anonymous function depending only the radial distance r @(r)...., not on h
% h: smoothing length
% c0: speed of the sound
% q1: q1 coefficient
% rho: density (default value based on X.c_rho_smd if X is a table, 1000 otherwise)
% mass: mass of the bead (default value based on X.mass if X is a table, 1 otherwise)
% vol: volume of the bead (default value based on X.c_vol if X is a table, 1 otherwise)
%
% Output (single output provided):
% F: nX x 3 vector of Landshoff forces
% with F{i} = mi x 3 forces
%
% Outputs (two outputs provided):
% F: nX x 1 Landshoff force magnitudes
% W: nX x 9 virial stress tensor (use reshape(W(i),3,3) to recover the matrix)
% n: nX x 3 normal vectors
% MS 3.0 | 2023-03-31 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2024-03-30
% Revision history
% 2023-04-01 sum forces instead of individual ones,accept X as a table
% 2023-04-02 fix summation bug
% 2023-09-04 add m, fix config fields check
% 2023-09-07 add W (virial stress)
% 2023-09-09 local virial stress fixed to match https://doi.org/10.1016/j.cplett.2019.07.008
% 2023-09-09 code acceleration
% 2023-09-11 prevent NaN if the norm is 0
% 2023-09-12 fix m^2 (ma*mb) instead of mb
% 2023-09-13 upgrade rho, mass, vol from X as a table, adhere to scheme from SPH_virial_stress in Billy's thesis
% 2024-03-30 small control comments by OV
%% Default
h_default = 60e-6; % rough guess (to be adjusted with common value)
config_default = struct( ...real dynamic viscosity: q1 * h * c0 * rho / 8 (2D) or 10 (3D) see Eq. 8.8 Monaghan, J. J. (2005). Smoothed particle hydrodynamics. Reports on Progress in Physics, 68(8), 1703–1759. doi:10.1088/0034-4885/68/8/r01
'gradkernel', kernelSPH(h_default,'lucyder',3),...% kernel gradient (note that h is bound with the kernel)
'h', h_default,...smoothing length
'c0',10,... speed of the sound
'q1',1,... constant
'rho', 1000, ... fluid density
'mass', 1,... bead weight
'vol', 1, ... bead volume (uniquely for virial stress)
'repulsiononly', false ... if true, only Landshoff forces when dot(rij,vij)<0
);
askvirialstress = (nargout==2);
%% Check arguments
if nargin<1, X = []; end
if nargin<2, vX = []; end
if isempty(X), error('one table is at least required with ''x'',''y'',''z'',''vx'',''vy'',vz'' columns'), end
if istable(X)
if isempty(vX), vX = X{:,{'vx','vy','vz'}}; end
config_default.mass = X.mass;
config_default.vol = X.c_vol;
config_default.rho = X.c_rho_smd;
X = X{:,{'x','y','z'}};
end
classX = class(X);
if isempty(vX), error('2 inputs are required: [F,n] = forceHertz(X,vX)'), end
if nargin<3, V = []; end
if nargin<4, config = []; end
if nargin<5, verbose = []; end
if isempty(V), V = buildVerletList(X); end
if isnumeric(V) && ~isnan(V) && length(V)==1, V = buildVerletList(frame,V); end
if isempty(config), config = config_default; end
for f = fieldnames(config_default)'
if ~isfield(config,f{1}) || isempty(config.(f{1}))
config.(f{1}) = config_default.(f{1});
end
end
[nX,dX] = size(X);
if (size(vX,1)~=nX) || (size(vX,2)~=dX), error('vX should be a %d x %d array',nX,dX), end
for f = {'mass','rho','vol'}
if numel(config.(f{1}))==1, config.(f{1})= config.(f{1})* ones(nX,1,classX); end
if ~isa(config.(f{1}),classX), config.(f{1})= cast(config.(f{1}),classX); end
end
if length(V)~=nX, error('V must be a %d cell array created with buildVerletList()',nX), end
verbosedefault = nX > 1e2;
if isempty(verbose), verbose = verbosedefault; end
%% verbosity
if verbose
t0_ = clock; %#ok
t1_ = t0_;
screen = '';
if askvirialstress
dispf('Calculate Landshoff forces + virial stress between [%d x %d] ATOMS...',nX,dX)
else
dispf('Calculate Landshoff forces between [%d x %d] ATOMS...',nX,dX)
end
end
%% Evaluate forces (and stresses)
% the code is highly vectorized and use only one for loop on primary atoms
% In the case of shock tube problems, it is usual to turn the viscosity
% on for approaching particles and turn it off for receding particles.
% In this way, the viscosity is used for shocks and not rarefactions.
% after Monaghan (2005), p 1741
% Source: https://cg.informatik.uni-freiburg.de/intern/seminar/animation%20-%20SPH%20survey%20-%202005.pdf
% allocate
F = zeros(nX,1,classX);
n = zeros(nX,dX,classX);
% for virial stress calculation
% volmin = config.m/config.rho; % depreciated
% vol = 4/3 * pi * config.h^3; % depreciated
if askvirialstress
W = zeros(nX,dX*dX,classX);
%if vol'the value of h (%0.4g) leads to a volume smaller than atoms (%0.4g)' ,config.h,volmin), end
else
W = [];
end
stresstensor = zeros(dX,dX,classX);
% for all atoms
% reference formula https://docs.lammps.org/PDF/SPH_LAMMPS_userguide.pdf (page 7, Eqs 18, 19)
q1c0h = config.q1 * config.c0 * config.h; % q1 * c0 * h
epsh = 0.01*config.h^2; % tolreance
dmin = 0.1 * config.h; % minimim distance for Landshoff
for i=1:nX
j = V{i}; % neighbors of i
mi = config.mass(i);
rhoi = config.rho(i);
rij = X(i,:) - X(j,:); % position vector j->i
rij2 = dot(rij,rij,2); % dot(rij,rij,2)
rij_d = sqrt(rij2); % norm
rij_n = rij ./ rij_d; % normalized vector
vij = vX(i,:) - vX(j,:); % relative velocity of i respectively to j
rvij = dot( rij, vij, 2); % projected velocity
ok = rij_d>dmin; % added on 2023-09-13
if config.repulsiononly, ok = ok & (rvij<0); end
if any(ok)
% Reference Formulation - p1740 of Rep. Prog. Phys. 68 (2005) 1703–1759 (attention acceleration, not force)
% http://dx.doi.org/10.1088/0034-4885/68/8/R01
% before 2023-09-09
%muij = config.h * rvij(ok) ./ ( rij2(ok) + 0.01*config.h^2 );
%nuij = (1/config.rho) * (-config.q1 * config.c0 * muij);
%Fij = - config.m^2 * nuij .* config.gradkernel(rij_d(ok)) .* rij_n(ok,:);
% after 2023-09-09
mj = config.mass(j(ok));
rhoj = config.rho(j(ok));
rhoij = 0.5 * (rhoi+rhoj);
muij = rvij(ok) ./ ( rij2(ok) + epsh );
Fij = q1c0h * mi * mj./rhoij .* muij .* config.gradkernel(rij_d(ok)) .* rij_n(ok,:);
Fbalance = sum(Fij,1);
F(i) = norm(Fbalance);
if F(i)>0, n(i,:) = Fbalance/F(i); end
if askvirialstress
stresstensor(:) = 0;
for ineigh = 1:length(find(ok))
% -rij' * Fij is the outerproduct (source: doi:10.1016/j.ijsolstr.2008.03.016)
stresstensor = stresstensor -rij(ineigh,:)' * Fij(ineigh,:);
end
W(i,:) = stresstensor(:)' / (2*config.vol(i)); % origin of 2 ?? in this case (since j-->i not considered)
end
else
n(i,:) = sum(rij_n,1);
end % if any(ok)
% verbosity
t_ = clock; %#ok
if verbose && (mod(i,1000)==0 || (etime(t_,t1_)>0.5)) %#ok<*DETIM>
t1_=t_; dt_ = etime(t_,t0_); done_ = i/nX;
screen = dispb(screen,'[atom %d:%d] FORCE Landshoff | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,nX,dt_,100*done_,(1/done_-1)*dt_);
end
end % next i
%% outputs
if nargout>1, Wout = W; end
if nargout>2
nout = n;
else
F = F .* n;
end
%% verbosity
if verbose
if askvirialstress
dispb(screen,'\t ... done with virial stress in %0.3g s for %d atoms',etime(clock,t0_),nX); %#ok
else
dispf(screen,'\t ... done in %0.3g s for %d ATOMS',etime(clock,t0_),nX); %#ok
end
end
HOURGLASS calculates the total hourglss correction force according to Eq. 17-24 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
Syntax:
out = hourglassSPH(X0,X,defgradSPHout) <--- syntax 1 (preferred)
out = hourglassSPH(X0,X,F [,V, config, silent]) <--- syntax 2
Inputs: (syntax 1)
X0 : kxd coordinates of the kernel centers of reference configuration
X : kxd coordinates of the kernel centers of deformed configuration
defgradSPHout : ouput (structure) of defgradSPH
Inputs: (syntax 2)
X0 : kxd coordinates of the kernel centers of reference configuration
X : kxd coordinates of the kernel centers of deformed configuration
F : k x d^2 deformation gradient
f : k x d pairwise forces
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
forcesilent: flag to force silence mode (default = false)
config: structure with fields coding for Lamé parameters
lambda (default = 30 000 Pa)
mu (default = 3000 Pa)
Output: out a structure with fields:
F : k x d^2 deformation gradient
C : k x d^2 Cauchy-Green deformation tensor
E : k x d^2 Green-Lagrange strain tensor
S : k x d^2 Second Piola-Kirchoff stress tensor replacing Cauchy stress (Elastic stress = config.lambda*trace(E) + 2*config.mu*E)
P : k x d^2 First Piola-Kirchoff stress
f : k x d pairwise forces
G : k * d von Mises stress
description : tensor description
k,d,V,correctedgradW,config are also included
engine : 'hourglassSPH'
Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
See also
defgradSPH, shapeSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
See also
function out = hourglassSPH(X0,X,W,defgradout,V,config,forcesilent)
%HOURGLASS calculates the total hourglss correction force according to Eq. 17-24 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
%
% Syntax:
% out = hourglassSPH(X0,X,defgradSPHout) <--- syntax 1 (preferred)
% out = hourglassSPH(X0,X,F [,V, config, silent]) <--- syntax 2
%
% Inputs: (syntax 1)
% X0 : kxd coordinates of the kernel centers of reference configuration
% X : kxd coordinates of the kernel centers of deformed configuration
% defgradSPHout : ouput (structure) of defgradSPH
%
% Inputs: (syntax 2)
% X0 : kxd coordinates of the kernel centers of reference configuration
% X : kxd coordinates of the kernel centers of deformed configuration
% F : k x d^2 deformation gradient
% f : k x d pairwise forces
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
% forcesilent: flag to force silence mode (default = false)
% config: structure with fields coding for Lamé parameters
% lambda (default = 30 000 Pa)
% mu (default = 3000 Pa)
%
% Output: out a structure with fields:
% F : k x d^2 deformation gradient
% C : k x d^2 Cauchy-Green deformation tensor
% E : k x d^2 Green-Lagrange strain tensor
% S : k x d^2 Second Piola-Kirchoff stress tensor replacing Cauchy stress (Elastic stress = config.lambda*trace(E) + 2*config.mu*E)
% P : k x d^2 First Piola-Kirchoff stress
% f : k x d pairwise forces
% G : k * d von Mises stress
% description : tensor description
% k,d,V,correctedgradW,config are also included
% engine : 'hourglassSPH'
%
% Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
% ~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
%
%
% See also: defgradSPH, shapeSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
%
% 2023-10-31 | INRAE\Han CHEN | rev. 2023-11-15
%{
% Example:
r = 0.5;
X0 = packSPH(5,r);
X0(sqrt(sum((X0-mean(X0,1)).^2,2))>4*r,:) = [];
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure, hold on
for i = 1:size(X0, 1), surf(xs*r + X0(i,1), ys*r + X0(i,2), zs*r + X0(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none'); end
lighting gouraud, camlight left, shading interp, axis equal, view(3)
% deformation (vertical compression + shearing)
Xc = mean(X0,1); Xmin = min(X0,[],1); Xmax = max(X0,[],1)
% compression along z, with support at zmin, compression rate = 20%
X = X0; X(:,3) = 0.8*(X(:,3)-Xmin(1,3)) + Xmin(1,3);
% shearing 20% along y
X(:,2) = X(:,2) + 0.2 * (Xmax(1,2)-Xmin(1,2)) * (X(:,3)-Xmin(1,3))/(Xmax(1,3)-Xmin(1,3));
% plot
figure, hold on
for i = 1:size(X, 1), surf(xs*r + X(i,1), ys*r + X(i,2), zs*r + X(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none'); end
lighting gouraud, camlight left, shading interp, axis equal, view(3)
% displacement
u = X-X0;
% shape matrix
gradW = kernelSPH(2*r,'lucyder',3);
shapeout = shapeSPH(X0,gradW)
defgradout = defgradSPH(u,shapeout)
% visualization
figure, hold on
scatter3(X(:,1),X(:,2),X(:,3),40,defgradout.G)
f = defgradout.f; fn=sqrt(sum(f.^2,2)); f = f./fn;
f90 = prctile(fn,90); fn(fn>f90) = f90; f = f .* fn;
quiver3(X(:,1),X(:,2),X(:,3),f(:,1),f(:,2),f(:,3))
view(3)
%hourglass force
W = kernelSPH(2*r,'lucy',3);
fhgout = hourglassSPH(X0,X,W,defgradout);
figure, hold on
scatter3(X(:,1),X(:,2),X(:,3),40)
fhg= fhgout.fHG; fn=sqrt(sum(fhg.^2,2)); fhg = fhg./fn;
f90 = prctile(fn,90); fn(fn>f90) = f90; fhg = fhg .* fn;
quiver3(X(:,1),X(:,2),X(:,3),fhg(:,1),fhg(:,2),fhg(:,3))
view(3)
%}
%Revision history
% 2023-11-15 alpha version
% Default Lamé parameters
config_default = struct(...
'lambda',3e4, ...first Lamé parameter
'mu',3e3, ... shear modulus (second Lamé parameter)
'E',9e3, ... young's modulus
'alpha',1 ... a dimensionless coefficient that controls the amplitude of hourglass correction
);
%% arg check
if nargin<4, error('4 arguments are required at least'), end
[k,d] = size(X0); if k==0, error('please supply some displacements centers'), end
if isstruct(defgradout) && strcmpi(defgradout.engine,'defragSPH') % -- syntax 1: we reuse the arguments of shapeSPH
args = defgradout;
if (args.d~=d) || (args.k~=k), error('%dx%d u is not compatible with previous %dx%d centers',k,d,args.k,args.d), end
F = args.F;
f = args.f;
V = args.V;
config = args.config;
for fig = fieldnames(config_default)'
if ~isfield(config,fig{1}) || isempty(config.(fig{1}))
config.(fig{1}) = config_default.(fig{1});
end
end
forcesilent = args.forcesilent;
else % -- syntax 2
if nargin<5, V = []; end
if nargin<6, config = []; end
if nargin<7, forcesilent = []; end
if d>3, error('3 dimensions maximum'), end
kv = length(V);
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
if isempty(forcesilent), forcesilent = false; end
if isempty(config), config = config_default; end
for fig = fieldnames(config_default)'
if ~isfield(config,fig{1}) || isempty(config.(fig{1}))
config.(fig{1}) = config_default.(fig{1});
end
end
end
%% initialization
verbosity = (k>1e3) & ~forcesilent;
largek = k>200;
t0_ = clock; t1_=t0_; screen='';
% vec2tensor: indices to convert a vector to a 2D tensor (faster than many reshapes)
vec2tensor_reshape = reshape(1:d^2,d,d);
vec2tensor = @(x) x(vec2tensor_reshape);
%% Output total hourglass correction force fHG
fHG = zeros(k,d,class(f));
Xij = zeros(k,k,d);
xij = zeros(k,k,d);
Xij_d = zeros(k,k);
xij_d = zeros(k,k);
deltaij = zeros(k,k);
if verbosity, dispf('SHAPESPH calculate L correction matrix for all %d kernels (K) in %d dimensions...',k,d), end
% loop over all j for summation
for j=1:k
% verbosity
if verbosity
if largek
t_ = clock; %#ok<*CLOCK>
if mod(j,10)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_;
dt_ = etime(t_,t0_); done_ = j/k;
screen = dispb(screen,'[K%d:%d] SHAPESPH | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
j,k,dt_,100*done_,(1/done_-1)*dt_);
end
else
dispf('... SHAPESPH respectively to kernel %d of %d',j,k);
end
end
for i=1:k
if i~=j
Fi = vec2tensor(F(i,:));
Xij(i,j,:) = X0(j,:) - X0(i,:);
Xij_d(i,j) = sqrt(dot(Xij(i,j,:),Xij(i,j,:)));
xij(i,j,:) = X(j,:) - X(i,:);
xij_d(i,j) = sqrt(dot(xij(i,j,:),xij(i,j,:)));
xij_i = Fi*(X0(j,:) - X0(i,:))';
deltaij(i,j) = (xij_i' - (X(j,:) - X(i,:))) * xij_i / xij_d(i,j);
end
end
end
for i = 1:k
for j = 1:k
if j~=i
coef = - 0.5*config.alpha*V(i)*V(j)*W(Xij(i,j));
fHG(i,:) = fHG(i,:) + squeeze(coef*config.E*(deltaij(i,j)+deltaij(j,i))*xij(i,j,:)/(Xij_d(i,j)^2*xij_d(i,j)))';
end
end
end
%% verbosity
if verbosity
dispb(screen,'DEFRAGSPH %d L matrix calculated in %0.4g s',k,etime(clock,t0_));
end
% collect all outputs
description = struct(...
'fHG','hourglass correction force'...
);
out = struct( ...
'k',k,'d',d,'V',V,'fHG',fHG,...
'description',description,'engine','hourglassSPH','forcesilent',forcesilent);
INTERP2SPH interpolates y at Xq,Yq using the 2D kernel W centered on centers
Syntax:
Vq = interp2SPH(X,y,Xq,Yq [,W,V])
Inputs:
centers : kx2 coordinates of the kernel centers
y : kxny values at X (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculatoin
Xq : array or matrix coordinates along X
Yq : array or matrix coordinates along Y
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
forcesilent: flag to force silence mode (default = false)
Output:
Vq : same size as Xq, with an additional dimension if y was an array
See also
interp3SPH, kernelSPH, packSPH
See also
function Vq = interp2SPH(centers,y,Xq,Yq,W,V,forcesilent)
% INTERP2SPH interpolates y at Xq,Yq using the 2D kernel W centered on centers
%
% Syntax:
% Vq = interp2SPH(X,y,Xq,Yq [,W,V])
%
% Inputs:
% centers : kx2 coordinates of the kernel centers
% y : kxny values at X (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculatoin
% Xq : array or matrix coordinates along X
% Yq : array or matrix coordinates along Y
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
% forcesilent: flag to force silence mode (default = false)
%
% Output:
% Vq : same size as Xq, with an additional dimension if y was an array
%
% See also: interp3SPH, kernelSPH, packSPH
%
% 2023-02-20 | INRAE\Olivier Vitrac | rev. 2024-03-18
%Revision history
% 2023-10-30 remove Zq from the input variables
% 2024-03-18 fixes for production
% arg check
if nargin<1, centers = []; end
if nargin<2, y = []; end
if nargin<3, Xq = []; end
if nargin<4, Yq = []; end
if nargin<5, W = []; end
if nargin<6, V = []; end
if nargin<7, forcesilent = []; end
[k,d] = size(centers);
[ky,ny] = size(y);
kv = length(V);
if k==0, error('please supply some centers'), end
if d~=2, error('2 dimensions (columns) are required'), end
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if ~isequal(size(Xq),size(Yq)), error('Xq,Yq and Zq do not have compatible sizes'), end
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
if isempty(forcesilent), forcesilent=false; end
fmtsize = @(array) sprintf([repmat('%d x ', 1, ndims(array)-1), '%d'], size(array));
% main
sumW = cell(1,ny);
verbosity = numel(Xq)>1e4;
largek = k>200;
t0_ = clock; t1_=t0_; screen=''; %#ok
if verbosity, dispf('INTPER2SPH is summing %s grid values over %d kernels (K)...',fmtsize(Xq),k), end
for i=1:k
% initialization if needed
if i==1
for iy=1:ny
sumW{iy} = zeros(size(Xq),class(Xq));
end
end
% verbosity
if verbosity
if largek
t_ = clock;
if mod(i,500)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_;
dt_ = etime(t_,t0_); done_ = i/k;
screen = dispb(screen,'[K%d:%d] INTERP2SPH | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,k,dt_,100*done_,(1/done_-1)*dt_);
end
else
dispf('... interpolate respectively to kernel %d of %d',i,k);
end
end
% interpolation
R = sqrt( (Xq-centers(i,1)).^2 + (Yq-centers(i,2)).^2 );
for iy = 1:ny
sumW{iy} = sumW{iy} + y(i,iy) * V(i) * W(R);
end
end
% output
if ny==1
Vq = sumW{1};
else
Vq = cat(ndims(Xq)+1,sumW{:});
end
% verbosity
if verbosity
dispb(screen,'done in . INTERP2SPH interpolated %s grid points with %d kernels in %0.4g s', ...
etime(clock,t0_),fmtsize(Xq),k);
end
INTERP2SPHVerlet interpolates y at XYgrid using the GridVerletList and the 2D kernel W centered on XY
Vgrid = interp2SPHVerlet(XY,y,XYgrid,GridVerletList [,W,V])
Inputs:
XY : kx2 coordinates of the kernel centers
y : kxny values at XY (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculatoin
XYgrid : gx2 grid coordinates
GridVerletList : VerletList of the grid
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
Output:
Vgrid : gxny array
See also
buildVerletList, interp3SPH, interp3SPHVerlet, interp2SPH, kernelSPH, packSPH, virialStress
function Vq = interp2SPHVerlet(XY,y,XYgrid,GridVerletList,W,V)
% INTERP2SPHVerlet interpolates y at XYgrid using the GridVerletList and the 2D kernel W centered on XY
%
% Vgrid = interp2SPHVerlet(XY,y,XYgrid,GridVerletList [,W,V])
%
% Inputs:
% XY : kx2 coordinates of the kernel centers
% y : kxny values at XY (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculatoin
% XYgrid : gx2 grid coordinates
% GridVerletList : VerletList of the grid
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
%
% Output:
% Vgrid : gxny array
%
%
% See also: buildVerletList, interp3SPH, interp3SPHVerlet, interp2SPH, kernelSPH, packSPH, virialStress
% 2023-10-30 | INRAE\Han CHEN | rev.
% Revision history
% 2023-10-30 alpha version
% arg check
if nargin<1, XY = []; end
if nargin<2, y = []; end
if nargin<3, XYgrid = []; end
if nargin<4, GridVerletList= []; end
if nargin<5, W = []; end
if nargin<6, V = []; end
[k,d] = size(XY);
if d~=2, error('2 dimensions (columns) are required for XY'), end
[ky,ny] = size(y);
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if k==0, error('please supply some centers XY'), end
[kg,dg] = size(XYgrid);
if dg~=2, error('2 dimensions (columns) are required for XYgrid'), end
if ~iscell(GridVerletList) || length(GridVerletList)~=kg
error('the supplied VerletList (%d atoms) does not match the number of grid points (%d)',length(GridVerletList),kg)
end
kv = length(V);
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
fmtsize = @(array) sprintf([repmat('%d x ', 1, ndims(array)-1), '%d'], size(array));
% main
t0_ = clock; t1_=t0_; screen=''; %#ok
Vq = NaN(kg,ny,class(y));
sizVerlet = cellfun(@length,GridVerletList);
dispf('INTERP2SPHVERLET interpolates %s grid points with a Verlet list including from %d to %d neighbors...',...
fmtsize(XYgrid),min(sizVerlet),max(sizVerlet))
for i=1:kg
t_ = clock; %#ok
if mod(i,200)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_; dt_ = etime(t_,t0_); done_ = i/kg;
screen = dispb(screen,'[GridPoint %d:%d] INTERP2SPHVerlet | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,kg,dt_,100*done_,(1/done_-1)*dt_);
end
if ~isempty(GridVerletList{i})
Vq(i,:) = interp2SPH( ...
XY(GridVerletList{i},:),...
y(GridVerletList{i},:),...
XYgrid(i,1),...
XYgrid(i,2),...
W,...
V(GridVerletList{i}),...volume of the kernel
true ...silent
);
end
end
dispb(screen,'...done in %0.4g s. INTERP2SPHVerlet completed the interpolation of %d points with %d kernels', ...
etime(clock,t0_),kg,k); %#ok
INTERP3SPH interpolates y at Xq,Yq,Zq using the 3D kernel W centered on centers
Syntax:
Vq = interp3SPH(X,y,Xq,Yq,Zq [,W,v])
Inputs:
centers : kx3 coordinates of the kernel centers
y : kxny values at X (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculation
Xq : array or matrix coordinates along X
Yq : array or matrix coordinates along Y
Zq : array or matrix coordinates along Z
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
v : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
forcesilent: flag to force silence mode (default = false)
Output:
Vq : same size as Xq, with an additional dimension if y was an array
See also
interp3SPHVerlet, interp2SPH, kernelSPH, packSPH, virialStress
See also
Example, :, interpolate, the, field, x+2*y-3*z, {
function Vq = interp3SPH(centers,y,Xq,Yq,Zq,W,V,forcesilent)
% INTERP3SPH interpolates y at Xq,Yq,Zq using the 3D kernel W centered on centers
%
% Syntax:
% Vq = interp3SPH(X,y,Xq,Yq,Zq [,W,v])
%
% Inputs:
% centers : kx3 coordinates of the kernel centers
% y : kxny values at X (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculation
% Xq : array or matrix coordinates along X
% Yq : array or matrix coordinates along Y
% Zq : array or matrix coordinates along Z
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% v : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
% forcesilent: flag to force silence mode (default = false)
%
% Output:
% Vq : same size as Xq, with an additional dimension if y was an array
%
% See also: interp3SPHVerlet, interp2SPH, kernelSPH, packSPH, virialStress
%
% Example : interpolate the field x+2*y-3*z
%{
r = 0.5;
h = 2*r;
XYZ = packSPH(5,r);
W = kernelSPH(h,'lucy',3);
y = XYZ*[1;2;-3]; % arbitrary field to be interpolated x+2*y-3*z
nresolution = 50;
xg = linspace(min(XYZ(:,1))-h,max(XYZ(:,1))+h,nresolution);
yg = linspace(min(XYZ(:,2))-h,max(XYZ(:,2))+h,nresolution);
zg = linspace(min(XYZ(:,3))-h,max(XYZ(:,3))+h,nresolution);
[Xg,Yg,Zg] = meshgrid(xg,yg,zg);
Vg = interp3SPH(XYZ,y,Xg,Yg,Zg,W);
figure, hs= slice(Xg,Yg,Zg,Vg,1:3,1:3,[]); set(hs,'edgecolor','none','facealpha',0.5), axis equal
% comparison with standard scattered interpolation
F = scatteredInterpolant(XYZ(:,1),XYZ(:,2),XYZ(:,3),y);
Vg = F(Xg,Yg,Zg);
figure, hs= slice(Xg,Yg,Zg,Vg,1:3,1:3,[]); set(hs,'edgecolor','none','facealpha',0.5), axis equal
%}
%
% Example : calculate the density between the central bead and its closest neighbor
%{
r = 0.5;
h = 2*r;
XYZ = packSPH(5,r);
W = kernelSPH(h,'lucy',3);
[~,icentral] = min(sum((XYZ-mean(XYZ)).^2,2));
dcentral = sqrt(sum((XYZ-XYZ(icentral,:)).^2,2));
icontact = find( (dcentral>=2*r-0.0001) & (dcentral<=2*r+0.0001) );
[~,closest] = min(dcentral(icontact));
icontact = icontact(closest);
reducedcurvilinear = linspace(-2.5,2.5,100)';
curvilinear = reducedcurvilinear*norm(XYZ(icontact,:)-XYZ(icentral,:));
XYZg = XYZ(icentral,:) + reducedcurvilinear*(XYZ(icontact,:)-XYZ(icentral,:));
Vg = interp3SPH(XYZ,[],XYZg(:,1),XYZg(:,2),XYZg(:,3),W);
figure, plot(curvilinear,Vg), xlabel('distance to the central bead'), ylabel('density')
%}
% 2023-02-20 | INRAE\Olivier Vitrac | rev. 2023-10-26
% Revision history
% 2023-05-16 - improve verbosity
% 2023-05-17 - add forcesilent
% 2023-05-18 - improve verbosity (fmtsize)
% 2023-10-26 - improve help
% arg check
if nargin<1, centers = []; end
if nargin<2, y = []; end
if nargin<3, Xq = []; end
if nargin<4, Yq = []; end
if nargin<5, Zq = []; end
if nargin<6, W = []; end
if nargin<7, V = []; end
if nargin<8, forcesilent = []; end
[k,d] = size(centers);
[ky,ny] = size(y);
kv = length(V);
if k==0, error('please supply some centers'), end
if d~=3, error('3 dimensions (columns) are required'), end
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if ~isequal(size(Xq),size(Yq)) || ~isequal(size(Yq),size(Zq)), error('Xq,Yq and Zq do not have compatible sizes'), end
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
if isempty(forcesilent), forcesilent=false; end
fmtsize = @(array) sprintf([repmat('%d x ', 1, ndims(array)-1), '%d'], size(array));
% main
sumW = cell(1,ny);
verbosity = (numel(Xq)>1e4) && ~forcesilent;
largek = k>200;
t0_ = clock; t1_=t0_; screen=''; %#ok
if verbosity, dispf('INTPER3SPH is summing %s grid values over %d kernels (K)...',fmtsize(Xq),k), end
for i=1:k
% initialization if needed
if i==1
for iy=1:ny
sumW{iy} = zeros(size(Xq),class(Xq));
end
end
% verbosity
if verbosity
if largek
t_ = clock;
if mod(i,500)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_;
dt_ = etime(t_,t0_); done_ = i/k;
screen = dispb(screen,'[K%d:%d] INTERP3SPH | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,k,dt_,100*done_,(1/done_-1)*dt_);
end
else
dispf('... interpolate respectively to kernel %d of %d',i,k);
end
end
% interpolation
R = sqrt( (Xq-centers(i,1)).^2 + (Yq-centers(i,2)).^2 + (Zq-centers(i,3)).^2 );
for iy = 1:ny
sumW{iy} = sumW{iy} + y(i,iy) * V(i) * W(R);
end
end
% output
if ny==1
Vq = sumW{1};
else
Vq = cat(ndims(Xq)+1,sumW{:});
end
% verbosity
if verbosity
dispb(screen,'done in . INTERP3SPH interpolated %s grid points with %d kernels in %0.4g s', ...
etime(clock,t0_),fmtsize(Xq),k);
end
INTERP3SPHVerlet interpolates y at XYZgrid using the GridVerletList and the 3D kernel W centered on XYZ
Vgrid = interp3SPHVerlet(XYZ,y,XYZgrid,GridVerletList [,W,V])
Inputs:
XYZ : kx3 coordinates of the kernel centers
y : kxny values at XYZ (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculatoin
XYZgrid : gx3 grid coordinates
GridVerletList : VerletList of the grid
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
Output:
Vgrid : gxny array
See also
buildVerletList, interp3SPH, interp2SPH, kernelSPH, packSPH, virialStress
function Vq = interp3SPHVerlet(XYZ,y,XYZgrid,GridVerletList,W,V)
% INTERP3SPHVerlet interpolates y at XYZgrid using the GridVerletList and the 3D kernel W centered on XYZ
%
% Vgrid = interp3SPHVerlet(XYZ,y,XYZgrid,GridVerletList [,W,V])
%
% Inputs:
% XYZ : kx3 coordinates of the kernel centers
% y : kxny values at XYZ (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculatoin
% XYZgrid : gx3 grid coordinates
% GridVerletList : VerletList of the grid
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
%
% Output:
% Vgrid : gxny array
%
%
% See also: buildVerletList, interp3SPH, interp2SPH, kernelSPH, packSPH, virialStress
% 2023-05-16 | INRAE\Olivier Vitrac | rev. 2023-05-17
% Revision history
% 2023-05-16 alpha version
% 2023-05-17 RC
% 2023-05-18 improve verbosity
% arg check
if nargin<1, XYZ = []; end
if nargin<2, y = []; end
if nargin<3, XYZgrid = []; end
if nargin<4, GridVerletList= []; end
if nargin<5, W = []; end
if nargin<6, V = []; end
[k,d] = size(XYZ);
if d~=3, error('3 dimensions (columns) are required for XYZ'), end
[ky,ny] = size(y);
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if k==0, error('please supply some centers XYZ'), end
[kg,dg] = size(XYZgrid);
if dg~=3, error('3 dimensions (columns) are required for XYZgrid'), end
if ~iscell(GridVerletList) || length(GridVerletList)~=kg
error('the supplied VerletList (%d atoms) does not match the number of grid points (%d)',length(GridVerletList),kg)
end
kv = length(V);
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
fmtsize = @(array) sprintf([repmat('%d x ', 1, ndims(array)-1), '%d'], size(array));
% main
t0_ = clock; t1_=t0_; screen=''; %#ok
Vq = NaN(kg,ny,class(y));
sizVerlet = cellfun(@length,GridVerletList);
dispf('INTERP3SPHVERLET interpolates %s grid points with a Verlet list including from %d to %d neighbors...',...
fmtsize(XYZgrid),min(sizVerlet),max(sizVerlet))
for i=1:kg
t_ = clock; %#ok
if mod(i,200)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_; dt_ = etime(t_,t0_); done_ = i/kg;
screen = dispb(screen,'[GridPoint %d:%d] INTERP3SPHVerlet | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
i,kg,dt_,100*done_,(1/done_-1)*dt_);
end
if ~isempty(GridVerletList{i})
Vq(i,:) = interp3SPH( ...
XYZ(GridVerletList{i},:),...
y(GridVerletList{i},:),...
XYZgrid(i,1),...
XYZgrid(i,2),...
XYZgrid(i,3),...
W,...
V(GridVerletList{i}),...volume of the kernel
true ...silent
);
end
end
dispb(screen,'...done in %0.4g s. INTERP3SPHVerlet completed the interpolation of %d points with %d kernels', ...
etime(clock,t0_),kg,k); %#ok
INTERP3CAYCHY Computes the local stress tensor at each point in a 3D grid.
stress = interp3cauchy(Xw, Yw, Zw, FXw, FYw, FZw)
This function calculates the local stress tensor at each grid point
in a non-uniform 3D grid using the given force components at each point.
The calculated stress tensor reflects the mechanical state of the grid.
Inputs:
- Xw, Yw, Zw: 3D matrices containing the x, y, and z coordinates of the grid points.
- FXw, FYw, FZw: 3D matrices containing the x, y, and z components of the force at each grid point.
Output:
- stress: 4D array where the first three dimensions correspond to the size
of the input grid (minus one in each dimension to avoid index overflow),
and the fourth dimension contains the 9 components of the
local stress tensor for each grid cell (flattened 3x3 tensor).
Example
[Xw,Yw,Zw] = meshgrid(1:0.1:2, 1:0.2:3, 1:0.3:2);
FXw = rand(size(Xw));
FYw = rand(size(Yw));
FZw = rand(size(Zw));
stress = interp3virial(Xw, Yw, Zw, FXw, FYw, FZw);
Example
Example
=== Interpretation and comparison with Cauchy tensor definition ===
Example
In continuum mechanics, the Cauchy stress tensor, often represented by the symbol
\( \sigma \), is a second-order tensor describing the stress state at a given point
in a material. The Cauchy stress tensor is defined as follows:
Example
\[
\sigma_{ij} = \lim_{{\Delta A \to 0}} \frac{\Delta F_j}{\Delta A_i}
\]
Example
where \( \Delta F_j \) is the force in the \( j \) direction acting on a differential
area \( \Delta A_i \) oriented along the \( i \) direction. The indices \( i \) and
\( j \) run from 1 to 3, referring to the spatial dimensions \( x, y, z \).
Example
In the code, the variable `local_tensor` serves as the Cauchy stress tensor
for a particular grid cell. For this tensor, the components are computed as:
Example
\[
\text{local\_tensor}(\alpha, \beta) = \frac{F_{\text{avg}}(\alpha) \times
n_{\beta}(\beta)}{A(\beta)}
\]
Example
Here, \( F_{\text{avg}}(\alpha) \) is the average force in the \( \alpha \)
direction acting on the cell, \( n_{\beta}(\beta) \) is the normal vector to
the face of the cell in the \( \beta \) direction, and \( A(\beta) \) is the area
of the face in the \( \beta \) direction.
Example
### Comparison of Indices
In the Matlab implementation, the components of `local_tensor(alpha, beta)`
essentially represent \( \sigma_{\alpha \beta} \) if \( \alpha, \beta \) refer to the
\( x, y, z \) directions. In other words, \( \alpha = i \) and \( \beta = j \).
Example
It's important to note that Matlab uses 1-based indexing, so the components
\( \sigma_{11}, \sigma_{12}, \sigma_{13}, \ldots, \sigma_{33} \) in continuum
mechanics are stored as `local_tensor(1,1), local_tensor(2,1),
local_tensor(3,1), ..., local_tensor(3,3)` in Matlab.
Example
So, in conclusion, `local_tensor` in the Matlab code is essentially the Cauchy stress
tensor \( \sigma \), and its components are consistent with the definitions used
in continuum mechanics, albeit with 1-based indexing with the following rule:
first dimension = normal direction, second = considered force component.
function stress = interp3cauchy(Xw, Yw, Zw, FXw, FYw, FZw)
%INTERP3CAYCHY Computes the local stress tensor at each point in a 3D grid.
%
% stress = interp3cauchy(Xw, Yw, Zw, FXw, FYw, FZw)
%
% This function calculates the local stress tensor at each grid point
% in a non-uniform 3D grid using the given force components at each point.
% The calculated stress tensor reflects the mechanical state of the grid.
%
% Inputs:
% - Xw, Yw, Zw: 3D matrices containing the x, y, and z coordinates of the grid points.
% - FXw, FYw, FZw: 3D matrices containing the x, y, and z components of the force at each grid point.
%
% Output:
% - stress: 4D array where the first three dimensions correspond to the size
% of the input grid (minus one in each dimension to avoid index overflow),
% and the fourth dimension contains the 9 components of the
% local stress tensor for each grid cell (flattened 3x3 tensor).
%
% Example:
% [Xw,Yw,Zw] = meshgrid(1:0.1:2, 1:0.2:3, 1:0.3:2);
% FXw = rand(size(Xw));
% FYw = rand(size(Yw));
% FZw = rand(size(Zw));
% stress = interp3virial(Xw, Yw, Zw, FXw, FYw, FZw);
%
%
% === Interpretation and comparison with Cauchy tensor definition ===
%
% In continuum mechanics, the Cauchy stress tensor, often represented by the symbol
% \( \sigma \), is a second-order tensor describing the stress state at a given point
% in a material. The Cauchy stress tensor is defined as follows:
%
% \[
% \sigma_{ij} = \lim_{{\Delta A \to 0}} \frac{\Delta F_j}{\Delta A_i}
% \]
%
% where \( \Delta F_j \) is the force in the \( j \) direction acting on a differential
% area \( \Delta A_i \) oriented along the \( i \) direction. The indices \( i \) and
% \( j \) run from 1 to 3, referring to the spatial dimensions \( x, y, z \).
%
% In the code, the variable `local_tensor` serves as the Cauchy stress tensor
% for a particular grid cell. For this tensor, the components are computed as:
%
% \[
% \text{local\_tensor}(\alpha, \beta) = \frac{F_{\text{avg}}(\alpha) \times
% n_{\beta}(\beta)}{A(\beta)}
% \]
%
% Here, \( F_{\text{avg}}(\alpha) \) is the average force in the \( \alpha \)
% direction acting on the cell, \( n_{\beta}(\beta) \) is the normal vector to
% the face of the cell in the \( \beta \) direction, and \( A(\beta) \) is the area
% of the face in the \( \beta \) direction.
%
% ### Comparison of Indices
% In the Matlab implementation, the components of `local_tensor(alpha, beta)`
% essentially represent \( \sigma_{\alpha \beta} \) if \( \alpha, \beta \) refer to the
% \( x, y, z \) directions. In other words, \( \alpha = i \) and \( \beta = j \).
%
% It's important to note that Matlab uses 1-based indexing, so the components
% \( \sigma_{11}, \sigma_{12}, \sigma_{13}, \ldots, \sigma_{33} \) in continuum
% mechanics are stored as `local_tensor(1,1), local_tensor(2,1),
% local_tensor(3,1), ..., local_tensor(3,3)` in Matlab.
%
% So, in conclusion, `local_tensor` in the Matlab code is essentially the Cauchy stress
% tensor \( \sigma \), and its components are consistent with the definitions used
% in continuum mechanics, albeit with 1-based indexing with the following rule:
% first dimension = normal direction, second = considered force component.
% MS 3.0 | 2023-09-09 | INRAE\Olivier.vitrac@agroparistech.fr | rev.
% Revision history
% Check argument compatibility
if nargin~=6, error('Six arguments are required: stress = interp3virial(Xw, Yw, Zw, FXw, FYw, FZw)'), end
if any(size(Xw) ~= size(Yw)) || any(size(Yw) ~= size(Zw)) || ...
any(size(FXw) ~= size(FYw)) || any(size(FYw) ~= size(FZw)) || ...
any(size(Xw) ~= size(FXw))
error('Dimension mismatch among the input matrices.');
end
% Pre-check to ensure Xw, Yw, Zw are meshgrid-generated
tolerance = 1e-10; % Tolerance for checking uniform increments
if ~all(std(diff(Xw(:,1,1), 1, 1), 0, 2,'omitnan') < tolerance) || ...
~all(std(diff(Yw(1,:,1), 1, 2), 0, 1, 'omitnan') < tolerance) || ...
~all(std(diff(Zw(1,1,:), 1, 3), 0, 3, 'omitnan') < tolerance)
error('Input matrices Xw, Yw, and Zw must be generated using meshgrid');
end
% Initialize 4D array to store local stress tensor for each cell
% Dimensions: size(Xw) x 9 (flattened 3x3 tensor)
stress = NaN([size(Xw), 9],class(FXw));
% Loop through all grid points except the last in each dimension
% to avoid index overflow
[ny,nx,nz] = size(Xw); % remember than Xw,Yw and Zw are generated by meshgrid (variation directions: 1=y, 2=x, 3=z)
nxyz = ny * nx * nz; % total number of elements
verbose = nxyz > 1000;
if verbose
dispf('Calculate the local Cauchy stress from a [%d x %d x %d] grid...',ny,nx,nz)
t0_ = clock; %#ok
t1_ = t0_;
screen = '';
end
iter = 0;
for iy = 1:ny
for ix = 1:nx
for iz = 1:nz
iter = iter + 1;
% Initialize local tensor
local_tensor = zeros(3, 3);
% Edge cases for dx, dy, dz
dx = Xw(iy, min(ix+1,nx), iz) - Xw(iy, ix, iz);
dy = Yw(min(iy+1,ny), ix, iz) - Yw(iy, ix, iz);
dz = Zw(iy, ix, min(iz+1,nz)) - Zw(iy, ix, iz);
% Calculate area of each face of this cell
A = [dy * dz, dx * dz, dx * dy]; % Face areas
% Calculate tensor components for each face considering only the four vertices of the face
for beta = 1:3 % -> direction beta=1 (x-face), beta=2 (y-face), beta=3 (z-face)
%vert_idx = []; vert_idy = []; vert_idz = [];
% Define the indices for the 4 vertices constituting each face
if beta == 1 && iy < ny && iz < nz
vert_idx = repmat(ix, 1, 4);
vert_idy = [iy, iy, iy+1, iy+1];
vert_idz = [iz, iz+1, iz, iz+1];
elseif beta == 2 && ix < nx && iz < nz
vert_idy = repmat(iy, 1, 4);
vert_idx = [ix, ix+1, ix, ix+1];
vert_idz = [iz, iz, iz+1, iz+1];
elseif beta == 3 && ix < nx && iy < ny
vert_idz = repmat(iz, 1, 4);
vert_idx = [ix, ix+1, ix, ix+1];
vert_idy = [iy, iy, iy+1, iy+1];
else
continue; % Skip, as it's the edge of the grid
end
% Translate to 1D indices for force matrices
ind = sub2ind([ny, nx, nz], vert_idy, vert_idx, vert_idz);
for alpha = 1:3 % -> force component
% Compute average force on vertices
if alpha == 1
F_alpha_avg = mean(FXw(ind), 'omitnan');
elseif alpha == 2
F_alpha_avg = mean(FYw(ind), 'omitnan');
elseif alpha == 3
F_alpha_avg = mean(FZw(ind), 'omitnan');
end
if isnan(F_alpha_avg), continue; end
% Update the stress tensor component
local_tensor(beta, alpha) = F_alpha_avg / A(beta);
end % next alpha
end % next beta
% Store this tensor
stress(iy, ix, iz, :) = local_tensor(:);
% verbosity
t_ = clock; %#ok
if verbose && (mod(iter,200)==0 || (etime(t_,t1_)>0.5)) %#ok<*DETIM>
t1_=t_; dt_ = etime(t_,t0_); done_ = iter/nxyz;
screen = dispb(screen,'[GridPoint %d:%d] INTERP3 Cauchy Stress | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
iter,nxyz,dt_,100*done_,(1/done_-1)*dt_);
end
end % next iz (variation direction = 1) -- see meshgrid
end % next ix (variation direction = 2) -- see meshgrid
end % next iy (variation direction = 3) -- see meshgrid
if verbose
dispb(screen,'\t ... done in %0.3g s from %d GRIDPOINTS',etime(clock,t0_),nxyz); %#ok
end
KERNELSPH return a SPH kernel as an anonymous function in 3D or 2D (kernels are zero for r>h)
Syntax:
W = kernelSPH(h,type,d)
Inputs:
h : cutoff (all kernels have support between 0 and h)
type : kenel name (default = Lucy)
d : dimension (3 or 2)
Output:
W : kernel function @(r)
Example
W = kernelSPH(1,'lucy',3)
Example
Example
List of implemented Kernels (aliases can be defined)
Suffix der is added to first-order derivative kernels
Example
Example
lucy and lucyder: used for the Morris calculation in the SPH source code of LAMMPS:
lammps-2022-10-04/src/SPH/pair_sph_taitwater_morris.cpp ln. 138
poly6, polyder: used for ULSPH density calculation in SMD source code of LAMMPS:
lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
cubicspline, cubic: used for ULSPH artificial pressure calculation in SMD source code of LAMMPS:
cubicder lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
USER-SMD/smd_kernels.h
spikykernel,spiky used for ULSPH and TLSPH force calculation in SMD source code of LAMMPS:
spikyder lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
lammps-2022-10-04/src/MACHDYN/pair_smd_tlsph.cpp
gaussian, gaussiankernel
gaussiander
function W = kernelSPH(h,type,d)
% KERNELSPH return a SPH kernel as an anonymous function in 3D or 2D (kernels are zero for r>h)
%
% Syntax:
% W = kernelSPH(h,type,d)
% Inputs:
% h : cutoff (all kernels have support between 0 and h)
% type : kenel name (default = Lucy)
% d : dimension (3 or 2)
% Output:
% W : kernel function @(r)
% Example:
% W = kernelSPH(1,'lucy',3)
%
%
% List of implemented Kernels (aliases can be defined)
% Suffix der is added to first-order derivative kernels
%
%
% lucy and lucyder: used for the Morris calculation in the SPH source code of LAMMPS:
% lammps-2022-10-04/src/SPH/pair_sph_taitwater_morris.cpp ln. 138
% poly6, polyder: used for ULSPH density calculation in SMD source code of LAMMPS:
% lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
% cubicspline, cubic: used for ULSPH artificial pressure calculation in SMD source code of LAMMPS:
% cubicder lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
% USER-SMD/smd_kernels.h
% spikykernel,spiky used for ULSPH and TLSPH force calculation in SMD source code of LAMMPS:
% spikyder lammps-2022-10-04/src/MACHDYN/pair_smd_ulsph.cpp
% lammps-2022-10-04/src/MACHDYN/pair_smd_tlsph.cpp
% gaussian, gaussiankernel
% gaussiander
% See also: interp3SPH, interp3SPHVerlet, interp2SPH, interp2SPHVerlet, packSPH
% 2023-02-20 | INRAE\Olivier Vitrac | rev. 2023-10-29
% Revision history
% 2023-04-03 WJ. addition of the `poly6kernel', the `cubicsplinekernel' and the `spikykernel' as set in the SMD source code of LAMMPS:
% lammps-2022-10-04/src/MACHDYN/smdkernel.cpp
% 2023-10-29 major revision, updated and new kernels, help improvement, all kernels are zero beyond h
% arg check
if nargin<1, h = []; end
if nargin<2, type = ''; end
if nargin<3, d = []; end
if isempty(h), error('Supply a value for h'), end
if isempty(type), type = 'lucy'; end
if ~ischar(type), error('type must be a char array'), end
if isempty(d), d = 3; end
if (d<2) || (d>3), error('d must be equal to 1, 2 or 3'), end
% main
switch lower(type)
case 'lucy'
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case 'lucyder'
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case {'poly6kernel','poly6'}
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0)
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case {'cubicsplinekernel','cubicspline','cubic'}
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
q = 2*r/h;
W(r) = piecewise(q<1,(1/s)*(2/3-q^2+0.5*q^3),(q>=1) & (q<2),(1/s)*((2-q)^3)/6,q>=2,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(Rclear')
assume((R>h/2) & (R%}
W = @(r) (r=h/2) & (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
q = 2*r/h;
W(r) = piecewise(q<1,(1/s)*(2/3-q^2+0.5*q^3),(q>=1) & (q<2),(1/s)*((2-q)^3)/6,q>=2,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(Rclear')
assume((R>h/2) & (R%}
W = @(r) (r=h/2) & (rend
case {'cubicsplineder','cubicder'}
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
q = 2*r/h;
W(r) = piecewise(q<1,(1/s)*(2/3-q^2+0.5*q^3),(q>=1) & (q<2),(1/s)*((2-q)^3)/6,q>=2,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(Rclear')
assume((R>h/2) & (R%}
W = @(r) (r=h/2) & (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
q = 2*r/h;
W(r) = piecewise(q<1,(1/s)*(2/3-q^2+0.5*q^3),(q>=1) & (q<2),(1/s)*((2-q)^3)/6,q>=2,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(Rclear')
assume((R>h/2) & (R%}
W = @(r) (r=h/2) & (rend
case {'spikykernel' 'spiky'}
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
hr = h - r
n = h^6
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
hr = h - r
n = h^5
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case {'spikykernelder','spikyder'}
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
hr = h - r
n = h^6
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
hr = h - r
n = h^5
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case 'gaussian'
% M. Liu, & G. Liu, Smoothed particle hydrodynamics (SPH): an overview and recent developments, “Archives of computational methods in engineering”, 17.1 (2010), pp. 25-76.
if d==3
%{
syms R h s W(r) pi
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r<=h,(1/s)*(exp(-(3*r/h)^2) - exp(-(3)^2)),r>h,0); % kernel definition
s3D = simplify(solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s)); % scaling factor in 3D
% -pi*((2*h^3*exp(-9))/9 + (2276509072173613*h^3)/13835058055282163712 - (h^3*pi^(1/2)*erf(3))/27)
s3D = h^3*pi^(3/2)/27;
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R<=h)
matlabFunction((subs(W3(R),R,r)))
%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r<=h,(1/s)*(exp(-(3*r/h)^2) - exp(-(3)^2)),r>h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
% (h^2*pi*exp(-9)*(18426255492059989099*exp(9) - 18446744073709551616))/166020696663385964544
s2D = pi*h^2/9; % exp(-9) is dropped
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R<=h)
matlabFunction((subs(W2(R),R,r)))
%}
W = @(r) (rend
case 'gaussiander'
% M. Liu, & G. Liu, Smoothed particle hydrodynamics (SPH): an overview and recent developments, “Archives of computational methods in engineering”, 17.1 (2010), pp. 25-76.
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r<=h,(1/s)*(exp(-(3*r/h)^2) - exp(-(3)^2)),r>h,0); % kernel definition
s3D = simplify(solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s)); % scaling factor in 3D
% -pi*((2*h^3*exp(-9))/9 + (2276509072173613*h^3)/13835058055282163712 - (h^3*pi^(1/2)*erf(3))/27)
s3D = h^3*pi^(3/2)/27;
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R<=h)
matlabFunction((subs(diff(W3(R),R,1),R,r)))
%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r<=h,(1/s)*(exp(-(3*r/h)^2) - exp(-(3)^2)),r>h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
% (h^2*pi*exp(-9)*(18426255492059989099*exp(9) - 18446744073709551616))/166020696663385964544
s2D = pi*h^2/9; % exp(-9) is dropped
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R<=h)
matlabFunction((subs(diff(W2(R),R,1),R,r)))
%}
W = @(r) (rend
case {'kernelwendlandquintic' 'wendlandquintickernel' 'wendlandquintic'}
error('the kernel ''%s'' is not implemented yet (pending)',type)
otherwise
error('the kernel ''%s'' is not implemented',type)
end
% --- old code --
% function W = kernelSPH(h,type,d)
% % KERNELSPH return a SPH kernel
% %
% % Syntax:
% % W = kernelSPH(h,type,d)
% % Inputs:
% % h : cutoff
% % type : kenel name (default = Lucy)
% % d : dimension
% % Output:
% % W : kernel function @(r)
% %
% % Example:
% % W = kernelSPH(1,'lucy',3)
% %
% %
% % See also: interp3SPH, interp2SPH, packSPH
%
%
% % 2023-02-20 | INRAE\Olivier Vitrac | rev.
%
% % arg check
% if nargin<1, h = []; end
% if nargin<2, type = ''; end
% if nargin<3, d = []; end
% if isempty(h), error('Supply a value for h'), end
% if isempty(type), type = 'lucy'; end
% if ~ischar(type), error('type must be a char array'), end
% if isempty(d), d = 3; end
% if (d<2) || (d>3), error('d must be equal to 1, 2 or 3'), end
%
% % main
% switch lower(type)
% case 'lucy'
% if d==3
% %{
% syms R h s W(r)
% assume(h,{'real','positive'})
% assume(r,{'real','positive'})
% W(r) = piecewise(r=h,0); % kernel definition
% s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
% W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
% assume(R
% matlabFunction((subs(W3(R),R,r)))
% %}
% W = @(r) (r
% elseif d==2
% %{
% syms R h s W(r)
% assume(h,{'real','positive'})
% assume(r,{'real','positive'})
% W(r) = piecewise(r=h,0); % kernel definition
% s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
% W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
% assume(R
% matlabFunction((subs(W2(R),R,r)))
% %}
% W = @(r) (r
% end
% case 'lucyder'
% if d==3
% %{
% syms R h s W(r)
% assume(h,{'real','positive'})
% assume(r,{'real','positive'})
% W(r) = piecewise(r=h,0); % kernel definition
% s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
% W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
% assume(R
% matlabFunction((subs(diff(W3(R),R,1),R,r)))
% %}
% W = @(r) (r
% elseif d==2
% %{
% syms R h s W(r)
% assume(h,{'real','positive'})
% assume(r,{'real','positive'})
% W(r) = piecewise(r=h,0); % kernel definition
% s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
% W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
% assume(R
% matlabFunction((subs(diff(W2(R),R,1),R,r)))
% %}
% W = @(r) (r
% end
% otherwise
% error('the kernel ''%s'' is not implemented',type)
% end
LAMDUMPREAD2 read on the fly LAMMPS dump files (recognized formats: TEXT='*', TEXT GZIPPED='*.gz', BINARIES='*.bin')
LAMDUMPRREAD2 is a fork of LAMDUMPREAD (work in progress to implement standards similar to those those used in OVITO)
LAMMPS FORMATS
TEXT: editable, readable format (default format in LAMMPS)
loading time: +++++ : file size ++++++++
TEXT GZIPPED: as above but compressed (file size reduction 60 %, LAMMPS requires to be compiled with -DGZIP flag)
loading time: ++++++++++++++++ : file size ++++
BINARY: proprietary format (leads to the largest files but the fastest to open)
loading time: ++ : file size +++++++++++
SYNTAX AND OUTPUTS
------------------
X = lamdumpread(dumpfilename)
X.KEYWORD{i} = RECORD (by default)
X.KEYWORD(:,:,i) = RECORD when collect is used (or when LAMDUMPSAVE is used);
KEYWORD = keyword (e.g. TIMESTEP NUMBEROFATOMS BOXBOUNDS ATOMS ... )
RECORD = array
OPTIONS FOR ALL FORMATS
-----------------------
X = lamdumpread(dumpfilename [,action, molecules, itimes, iatoms])
action: keyword among 'collect','count','robot','split', 'default', 'search'
Actions controlling the way the file filename is read
if 'collect' is used, array data are stored into a 3 dims-arrays instead of a cell array
if 'count' is used, the size of each dataset is determined and not loaded
For binary files, 'collect' is always applied.
Actions controlling batches
if 'robot' or 'prefetch' is used, all files are read and a prefetch is created
if 'split' is used, all files are split into small prefetch files
if 'usesplit' is used, split files have higher precedence than prefetch files
if 'forceprefetch' is used, it force the genration of prefetch files even if split files exist)
-- notes --
lampdumpread() generate all prefetch files (high memory footprint)
lamdumpread2('dump.*','split') force split even if prefetch files have been generated
lamdumpread('mydump','usesplit',[],[0 500]) load specifically frames 0 and 500 from split files
Actions giving details on dump files and their prefetches
if 'default' is used (same behavior with "#prefetch" as filename), X is a structure with fields:
prefetch.file giving the prefetch file or anonymous function doing so for any filename fn
prefetch.folder idem for the prefetch folder
prefetch.frame is anonymous function @(filname,iframe) or @(itime) giving the filename of each split frame
if 'search' is used the dump file is looked for inside subfolders with the following precedence : splits, prefetech and
original files
molecules: cell array to build molecules according to X.ATOMS
molecules{i} list all atoms belonging to the ith molecule
X.ATOMS is replaced by X.MOLECULES
where X.MOLECULES{molecule index}(internal atom index,coordinate index,time step index)
OPTIONS FOR TEXT AND GZIPPED FORMATS
------------------------------------
X = lamdumpread(dumpfilename,[action],[molecules],[itimes],[iatoms])
itimes: requested timestep index (if empty, all timesteps are loadted)
iatoms: requested atom index; when molecules is used, index of molecules instead
SIMPLE EXAMPLE: based on in.LJ
------------------------------
X=lamdumpread('dump.atom')
X =
TIMESTEP: [0 100]
NUMBEROFATOMS: [32000 32000]
BOXBOUNDS: {[3x2 single] [3x2 single]}
ATOMS: {[32000x5 single] [32000x5 single]}
>> simple 3D plot with: plot3(X.ATOMS{2}(:,3),X.ATOMS{2}(:,4),X.ATOMS{2}(:,5),'ro')
ADVANCED EXAMPLES
-----------------
1) Extract the coordinates of 2 molecules consisting in atoms 1:1000 and 3001:4000 respectively
for the timestep indices 1 and 11
X=lamdumpread('dump.atom.gz','collect',{1:1000 3001:4000},[1 11])
X =
TIMESTEP: [0 1000]
NUMBEROFATOMS: [32000 32000]
BOXBOUNDS: [3x2x2 single]
ATOMS: [32000 5 11] << this field is only informative
MOLECULES: {2x1 cell} << Molecular data are stored here ({[1000x3x2 single];[1000x3x2 single]})
2) Extract the coordinates of a group of atoms [1:1000 3001:4000] for all timesteps
X=lamdumpread('dump.atom.gz','collect',{},[],[1:1000 3001:4000])
X =
TIMESTEP: [0 100 200 300 400 500 600 700 800 900 1000]
NUMBEROFATOMS: [32000 32000 32000 32000 32000 32000 32000 32000 32000 32000 32000]
BOXBOUNDS: [3x2x11 single]
ATOMS: [2000x5x11 single]
See also
LAMDUMPSAVE, LAMMPSCHAIN, LAMPLOT, GZIPR
See also
See also
-------------------------------------------------, EXAMPLES, FROM, THE, PHD, THESIS, OF, WILLIAM, JENKINSON, -------------------------------------------------, {
function X=lamdumpread2(filename,action,molecules,itimes,iatoms)
%LAMDUMPREAD2 read on the fly LAMMPS dump files (recognized formats: TEXT='*', TEXT GZIPPED='*.gz', BINARIES='*.bin')
% LAMDUMPRREAD2 is a fork of LAMDUMPREAD (work in progress to implement standards similar to those those used in OVITO)
%
% LAMMPS FORMATS
% TEXT: editable, readable format (default format in LAMMPS)
% loading time: +++++ : file size ++++++++
% TEXT GZIPPED: as above but compressed (file size reduction 60 %, LAMMPS requires to be compiled with -DGZIP flag)
% loading time: ++++++++++++++++ : file size ++++
% BINARY: proprietary format (leads to the largest files but the fastest to open)
% loading time: ++ : file size +++++++++++
% SYNTAX AND OUTPUTS
% ------------------
% X = lamdumpread(dumpfilename)
% X.KEYWORD{i} = RECORD (by default)
% X.KEYWORD(:,:,i) = RECORD when collect is used (or when LAMDUMPSAVE is used);
% KEYWORD = keyword (e.g. TIMESTEP NUMBEROFATOMS BOXBOUNDS ATOMS ... )
% RECORD = array
% OPTIONS FOR ALL FORMATS
% -----------------------
% X = lamdumpread(dumpfilename [,action, molecules, itimes, iatoms])
%
% action: keyword among 'collect','count','robot','split', 'default', 'search'
%
% Actions controlling the way the file filename is read
% if 'collect' is used, array data are stored into a 3 dims-arrays instead of a cell array
% if 'count' is used, the size of each dataset is determined and not loaded
% For binary files, 'collect' is always applied.
% Actions controlling batches
% if 'robot' or 'prefetch' is used, all files are read and a prefetch is created
% if 'split' is used, all files are split into small prefetch files
% if 'usesplit' is used, split files have higher precedence than prefetch files
% if 'forceprefetch' is used, it force the genration of prefetch files even if split files exist)
% -- notes --
% lampdumpread() generate all prefetch files (high memory footprint)
% lamdumpread2('dump.*','split') force split even if prefetch files have been generated
% lamdumpread('mydump','usesplit',[],[0 500]) load specifically frames 0 and 500 from split files
% Actions giving details on dump files and their prefetches
% if 'default' is used (same behavior with "#prefetch" as filename), X is a structure with fields:
% prefetch.file giving the prefetch file or anonymous function doing so for any filename fn
% prefetch.folder idem for the prefetch folder
% prefetch.frame is anonymous function @(filname,iframe) or @(itime) giving the filename of each split frame
% if 'search' is used the dump file is looked for inside subfolders with the following precedence : splits, prefetech and
% original files
%
% molecules: cell array to build molecules according to X.ATOMS
% molecules{i} list all atoms belonging to the ith molecule
% X.ATOMS is replaced by X.MOLECULES
% where X.MOLECULES{molecule index}(internal atom index,coordinate index,time step index)
%
% OPTIONS FOR TEXT AND GZIPPED FORMATS
% ------------------------------------
% X = lamdumpread(dumpfilename,[action],[molecules],[itimes],[iatoms])
% itimes: requested timestep index (if empty, all timesteps are loadted)
% iatoms: requested atom index; when molecules is used, index of molecules instead
%
% SIMPLE EXAMPLE: based on in.LJ
% ------------------------------
% X=lamdumpread('dump.atom')
% X =
% TIMESTEP: [0 100]
% NUMBEROFATOMS: [32000 32000]
% BOXBOUNDS: {[3x2 single] [3x2 single]}
% ATOMS: {[32000x5 single] [32000x5 single]}
%
% >> simple 3D plot with: plot3(X.ATOMS{2}(:,3),X.ATOMS{2}(:,4),X.ATOMS{2}(:,5),'ro')
%
% ADVANCED EXAMPLES
% -----------------
% 1) Extract the coordinates of 2 molecules consisting in atoms 1:1000 and 3001:4000 respectively
% for the timestep indices 1 and 11
% X=lamdumpread('dump.atom.gz','collect',{1:1000 3001:4000},[1 11])
% X =
% TIMESTEP: [0 1000]
% NUMBEROFATOMS: [32000 32000]
% BOXBOUNDS: [3x2x2 single]
% ATOMS: [32000 5 11] << this field is only informative
% MOLECULES: {2x1 cell} << Molecular data are stored here ({[1000x3x2 single];[1000x3x2 single]})
%
% 2) Extract the coordinates of a group of atoms [1:1000 3001:4000] for all timesteps
% X=lamdumpread('dump.atom.gz','collect',{},[],[1:1000 3001:4000])
% X =
% TIMESTEP: [0 100 200 300 400 500 600 700 800 900 1000]
% NUMBEROFATOMS: [32000 32000 32000 32000 32000 32000 32000 32000 32000 32000 32000]
% BOXBOUNDS: [3x2x11 single]
% ATOMS: [2000x5x11 single]
%
% See also: LAMDUMPSAVE, LAMMPSCHAIN, LAMPLOT, GZIPR
%
%
% -------------------------------------------------
% EXAMPLES FROM THE PHD THESIS OF WILLIAM JENKINSON
% -------------------------------------------------
%{
cd ~/billy/matlab/sandbox/
% == simple 2D simulation (standard nomenclature) ==
X = lamdumpread2(fullfile('misc_dumpfiles','dump.wall.2d'))
% == complex 3D simulation ==
X = lamdumpread2(fullfile('misc_dumpfiles','dump.backextrusion_v3b')) % nothing inside
TIMESTEP = X.TIMESTEP;
A = X.ATOMS(X.ATOMS.type==1,:); % liquid
B = X.ATOMS(X.ATOMS.type==2,:); % walls
listidB = B.id(B.TIMESTEP==TIMESTEP(1));
zB = B.z; idB = B.id; % to reduce memory impact when arrayfun is used
dzB = arrayfun(@(id) sqrt(mean(diff(zB(idB==id)).^2)),listidB); % mean displacement (long calculations)
B0 = B(ismember(B.id,listidB(dzB==0)),:); % static cylinder
B1 = B(ismember(B.id,listidB(dzB>0)),:); % moving cylinder
plotback = @(it) [
plot3(A.x (A.TIMESTEP ==TIMESTEP(it)), A.y( A.TIMESTEP==TIMESTEP(it)),A.z( A.TIMESTEP==TIMESTEP(it)),'bo','markerfacecolor','b','markersize',5)
plot3(B0.x(B0.TIMESTEP==TIMESTEP(it)),B0.y(B0.TIMESTEP==TIMESTEP(it)),B0.z(B0.TIMESTEP==TIMESTEP(it)),'r.')
plot3(B1.x(B1.TIMESTEP==TIMESTEP(it)),B1.y(B1.TIMESTEP==TIMESTEP(it)),B1.z(B1.TIMESTEP==TIMESTEP(it)),'g.')
];
figure, view(3), for it=1:length(TIMESTEP), cla, hold on, plotback(it); title(sprintf('t= %4g',TIMESTEP(it))), drawnow, end
% == complex 3D simulation ==
X = lamdumpread2('/home/olivi/billy/lammps/sandbox/dump.backextrusion_v3a');
TIMESTEP = unique(X.ATOMS_grp01.TIMESTEP);
A = X.ATOMS_grp01(X.ATOMS_grp01.type==1,:); % liquid
B = X.ATOMS_grp01(X.ATOMS_grp01.type==2,:); % walls
listidB = B.id(B.TIMESTEP==TIMESTEP(1));
zB = B.z; idB = B.id; % to reduce memory impact when arrayfun is used
dzB = arrayfun(@(id) sqrt(mean(diff(zB(idB==id)).^2)),listidB); % mean displacement (long calculations)
B0 = B(ismember(B.id,listidB(dzB==0)),:); % static cylinder
B1 = B(ismember(B.id,listidB(dzB>0)),:); % moving cylinder
plotback = @(it) [
plot3(A.x(A.TIMESTEP ==TIMESTEP(it)), A.y( A.TIMESTEP==TIMESTEP(it)),A.z( A.TIMESTEP==TIMESTEP(it)),'bo','markerfacecolor','b','markersize',5)
plot3(B0.x(B0.TIMESTEP==TIMESTEP(it)),B0.y(B0.TIMESTEP==TIMESTEP(it)),B0.z(B0.TIMESTEP==TIMESTEP(it)),'r.')
plot3(B1.x(B1.TIMESTEP==TIMESTEP(it)),B1.y(B1.TIMESTEP==TIMESTEP(it)),B1.z(B1.TIMESTEP==TIMESTEP(it)),'g.')
];
figure, view(3), cameratoolbar('Show'), for it=1:length(TIMESTEP), cla, hold on, plotback(it); title(sprintf('t= %4g',TIMESTEP(it))), drawnow, end
% profile extraction
% bottom position of B0
[~,ibottom] = min(B1.z(B1.TIMESTEP==TIMESTEP(1)));
zbottom = B1.z(B1.id==B1.id(ibottom));
figure, plot(TIMESTEP,zbottom), ylabel('z'), xlabel('t')
% center and radius of B0
xmean = mean(B0.x(B0.TIMESTEP==TIMESTEP(1)));
ymean = mean(B0.y(B0.TIMESTEP==TIMESTEP(1)));
rB0 = max( sqrt( (B0.x(B0.TIMESTEP==TIMESTEP(1)) - xmean).^2 + (B0.y(B0.TIMESTEP==TIMESTEP(1)) - ymean).^2 ) );
% sample positions
nr = 200; r = linspace(0,rB0,nr+1);
rA = sqrt( (A.x - xmean).^2 + (A.y - ymean).^2 );
nt = length(TIMESTEP);
vz = zeros(nt,nr);
for it=1:nt
okt = (A.TIMESTEP==TIMESTEP(it)) & (A.z>zbottom(it));
if any(okt)
vzt = A.vz(okt);
rAt = rA(okt);
for ir = 1:nr
ind = (rAt>=r(ir)) & (rAtend
end
end
itplot = find(any(abs(vz)>0,1),1,'first'):nt; nitplot = length(itplot);
figure, colororder(jet(nitplot)); plot(r(1:end-1),vz(itplot,:),'-','linewidth',2), xlabel('r'), ylabel('vz')
%}
% MS 2.1 - 22/01/08 - INRA\Olivier Vitrac rev. 2023-08-22
% Revision history
% 05/03/08 add action
% 05/03/08 convert ATOMS field into MOLECULES
% 09/03/08 add an efficient engine for binary files
% 12/03/08 add support for GZ files using gzipr and popenr
% 13/03/08 fix error index associated to matrices with a sintle timestep, impose single precision
% 14/03/08 add itimes, iatoms, generalized counter
% 15/03/08 fix numberofdims (improve rules for row and column vectors)
% 17/04/09 trim data
% 06/10/09 add excludedexpr
% 2021/02/11 implement R2020b standards
% 2021/02/15 format ATOMS as Ovito 3.x
% 2021/02/15a updated version to split automatically broken simulations (with non constant number of atoms)
% 2021/03/03 generalized ITEM: %[] ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_[]
% 2021/03/04 the rule to separate keywords (keys) and their descriptions (variable names) has been customized
% initially it was thought that keys were uppercase and descriptions lowercase.
% It is not the case, some variables include letters in capitals (e.g. c_S_s[1])
% In the future, the implemented rule may suffer some flaws as not all possible variable names have not been explored.
% 2022/11/26 remove \d ([0-9]) from keywords
% 2023-03-23 add robot, prefetch management, remove [] from ITEM
% 2023-04-02 sort files by size before applying the robot (smaller files first)
% 2023-04-04 fix lampdumpread2 or files inclding one single frame
% 2023-04-11 split all dumpfiles larger than 8GB into individual frames
% 2023-04-14 set actions 'prefetch','split','usesplit'
% 2023-04-25 add forceprefetch
% 2023-04-27 add TIMESTEPS, TIMESTEPfirst, TIMESTEPlast when 'usesplit' is used
% 2023-08-22 fixes for workshop on post treatment
% KNOWN LIMITATIONS
% 'collect' works only on standard MD DUMP files, not on SMD/SMD dump files... for now
% Patterns (see textscan for details and the example provided)
% a=textscan('ITEM: NUMBER OF ATOMS',['ITEM: %[ ' 'A':'Z' ']']); a{1}
variablepattern = ['ITEM: %[] ' 'A':'Z' 'a':'z' '0':'9' '_[]'];
numsep = ' '; % column separator A = X.ATOMS(X.ATOMS.type==1,:);
datatype = 'single'; % single precision (replace by 'double' to force double precision)
numpattern = '%f32'; % idem (replace by '%f' to force double precision')
maxfilesize = 8e9; % 8 GB (size before splitting)
%prefetchthreshold = 1e7; % bytes
robotdepth = 100; % maxdepth search
% excludedexpr = '\sid type xu yu zu\s$'; % regular expression % removed 02/14/2021
SPLITPREFIX = 'TIMESTEP_'; % added 2023/04/11
makeprefetch = @(fn) fullfile(rootdir(fn),['PREFETCH_' lastdir(fn) '.mat']); % added 2023/03/23
makesplitdir = @(fn) fullfile(rootdir(fn),['PREFETCH_' lastdir(fn)]); % added 2023/03/23
makesplit = @(fn,itime) fullfile(makesplitdir(fn),sprintf('%s%09d.mat',SPLITPREFIX,itime)); % added 2023/04/11
% Arg check
robot = false;
if nargin<1, robot = true; end
if nargin<2, action = ''; end
if nargin<3, molecules = {}; end
if nargin<4, itimes = []; end
if nargin<5, iatoms = []; end
collecton = strcmpi(action,'collect');
DEBUGON = true; % set DEBUGON to true for debuggind, false otherwise
% automatic robot (added 2023-03-23)
if robot, filename = 'dump.*'; end
if any(filename=='*'), robot = true; end
if strcmpi(filename,'#prefetch'), action='default'; filename=''; end
[datafolder,fn,en] = fileparts(filename);
dumpfile = [fn en];
% action on several files
switch lower(action)
case 'prefetch'
robot = true;
action = '';
case 'forceprefetch'
robot = true;
action = 'robotprefetch';
case 'split'
robot = true;
action = 'robotsplit';
case 'default'
% return the default prefetch files
if isempty(filename)
X = struct('prefetch',struct('file',makeprefetch,'folder',makesplitdir), ...
'frame',makesplit);
elseif ischar(filename)
X = struct( ...
'source',filename,...
'prefetch',struct('file',makeprefetch(filename),'folder',makesplitdir(filename)), ...
'frame',@(itime) makesplit(filename,itime));
return
else
error('filename should be a char or empty when action=''default''')
end
case 'search'
found = false; completed = false;
searchmode = {'splitfolder','prefetch','original'};
isearchmode = 0;
while ~found && ~completed
isearchmode = isearchmode+1;
current_searchmode = searchmode{isearchmode};
framefolder = '';
% search
datafolder_bak = datafolder;
switch current_searchmode
case 'original'
% We look for the original dump files
foundmatchingdumps = explore(dumpfile,datafolder,[],'abbreviate');
found = ~isempty(foundmatchingdumps);
if found
if length(foundmatchingdumps)>1
warning('%d copies of the same dump file has been found in ''%s'', the first is used',length(dumpfiles),datafolder)
foundmatchingdumps = foundmatchingdumps(1);
end
datafolder = foundmatchingdumps.path;
framefolder = fullfile(datafolder,makesplitdir(dumpfile));
end
case 'prefetch'
% We look for the prefetch dump files
foundmatchingdumps = explore(lastdir(makeprefetch(dumpfile)),datafolder,[],'abbreviate');
found = ~isempty(foundmatchingdumps);
if found
if length(foundmatchingdumps)>1
warning('%d copies of the same dump file has been found in ''%s'', the first is used',length(dumpfiles),datafolder)
foundmatchingdumps = foundmatchingdumps(1);
end
datafolder = foundmatchingdumps.path;
framefolder = fullfile(datafolder,makesplitdir(dumpfile));
end
case 'splitfolder'
framefilepattern = makesplit(fullfile(datafolder,dumpfile),0);
if ~exist(framefilepattern,'file')
dispf('Look for ''%s''... (be patient)',dumpfile)
frameid = lastdir(framefilepattern);
parentframeid = lastdir(rootdir(framefilepattern));
foundmatchingdumps = explore(frameid,datafolder,[],'abbreviate');
found = ~isempty(foundmatchingdumps);
if found
foundmatchingdumps = foundmatchingdumps(strcmp(cellfun(@lastdir,{foundmatchingdumps.subpath}','UniformOutput',false),parentframeid));
nfoundmatchingdumps = length(foundmatchingdumps);
if nfoundmatchingdumps==1
datafolder = rootdir(foundmatchingdumps.path);
dispf('...found in ''%s''\n',datafolder)
elseif nfoundmatchingdumps>1
warning('%d dump files are matching, only the first is used',nfoundmatchingdumps)
datafolder = rootdir(foundmatchingdumps(1).path);
end
end
end
framefolder = makesplitdir(fullfile(datafolder,dumpfile));
framefile = makesplit(fullfile(datafolder,dumpfile),0);
otherwise
error('unrecognized searchmode ''%s''',searcurrent_searchmodechmode)
end % end switch
if found
firstframeok = false;
dispf('The dumpfile ''%s'' has been found with the method ''%s''',dumpfile,current_searchmode)
dispf('\tin the folder: %s',datafolder);
if ~strcmp(datafolder,datafolder_bak)
dispf('the original search started in %s',datafolder_bak)
end
if exist(framefolder,'dir')
dispf('The frame (split) folder is: %s',framefolder)
end
if exist(framefile,'file')
dispf('The first frame (split) is located in: %s',framefile)
firstframeok = true;
end
end
completed = strcmp(current_searchmode,searchmode{end});
end % end while
if ~found,
error('the dumpfile ''%s'' does not exist in ''%s''',dumpfile,datafolder_bak)
else
if firstframeok % we return the first frame (default behavior)
X = lamdumpread2(fullfile(datafolder,dumpfile),'usesplit');
end
X = datafolder;
return
end
end % swicth action
% Pseudo-recursion on many files
if robot
if isempty(datafolder), robotpath = pwd; else, robotpath = datafolder; end
robotfile = dumpfile;
dumpfiles = explore(robotfile,robotpath,robotdepth,'abbreviate');
dumpfiles = dumpfiles(cellfun(@isempty,regexp({dumpfiles.ext},'gz$','once')));
[~,ind] = sort([dumpfiles.bytes],'ascend'); dumpfiles = dumpfiles(ind);
nfiles = length(dumpfiles);
for ifile = 1:nfiles
currentdumpfile = fullfile(dumpfiles(ifile).path,dumpfiles(ifile).file);
if ~exist(makeprefetch(currentdumpfile),'file') || strcmpi(action,'robotsplit')
dispf('LAMPDUMPREAD AUTO %d of %d',ifile,nfiles)
if DEBUGON
lamdumpread2(currentdumpfile,action)
else
try
lamdumpread2(currentdumpfile,action)
catch ME
warning(ME.message)
dispf('ERROR to read the file ''%s'' in ''%s''',dumpfiles(ifile).file,dumpfiles(ifile).path)
end
end
else
dispf('%d:%d %s',ifile,nfiles,currentdumpfile)
fileinfo(makeprefetch(currentdumpfile))
end
end
return
end
% display help
if strcmp(filename,''), error('syntax: X=lamdumpread(dumpfilename,[collect],[molecules],[itimes],[iatoms])'), end
% Manage forcesplit and usesplit
forcesplit = strcmpi(action,'robotsplit');
forceprefetch = strcmpi(action,'robotprefetch');
usesplit = strcmpi(action,'usesplit');
% prefetch (added 2023-03-23)
prefetchfile = makeprefetch(filename);
hasprefetchfile = exist(prefetchfile,'file');
hasprefetchfolder = exist(makesplitdir(filename),'dir');
if (hasprefetchfile && ~forcesplit && ~usesplit) || (hasprefetchfile && ~hasprefetchfolder) % fixed on 2023-08-21 (hasprefetchfile added)
dispf('Load prefetch file (instead of ''%s'')...',filename), fileinfo(prefetchfile), t0=clock;
load(prefetchfile)
dispf('...loaded in %0.3g s',etime(clock,t0)) %#ok<*CLOCK,*DETIM>
return
elseif (hasprefetchfolder && ~forcesplit && ~forceprefetch) || usesplit % prefetch dir (added 2023-03-11)
if isempty(itimes)
tmplist = explore([SPLITPREFIX '*.mat'],makesplitdir(filename),0,'abbreviate');
itimes = str2double(strrep({tmplist.name},SPLITPREFIX,''))';
ncol = 10;
nrow = ceil(length(itimes) / ncol);
itimes_cell = cell(nrow, ncol);
for i = 1:length(itimes)
[row, col] = ind2sub([nrow, ncol], i);
itimes_cell{row, col} = sprintf('%9d', itimes(i));
end
dispf('The prefetch is split in several files.\n%d TIMESTEPS are availble:',length(itimes))
for col = 1:ncol, fprintf(' Column %02d\t', col); end, fprintf('\n');
for row = 1:nrow, for col = 1:ncol, fprintf('%s\t', itimes_cell{row, col}); end, fprintf('\n'); end
dispf('Choose the time step you are interested in.\nOnly the first one is returned for now.')
itimes_all = itimes;
itimes = itimes(1);
else
itimes_all = [];
end
t0 = clock;
for it = 1:numel(itimes)
tmpfile = makesplit(filename,itimes(it));
if ~exist(tmpfile,'file'), error('the time step %d does not exist (%s)',itimes(it),filename); end
dispf('Use the prefetch (split: TIMESTEP %d) folder (instead of ''%s'')...',itimes(it), filename), fileinfo(tmpfile)
tmp = load(tmpfile);
if ~isfield(tmp,'X'), error('corrupted frame'), end
if isfield(tmp.X,'description')
ttmp = tmp.X.TIMESTEP*ones(tmp.X.NUMBER,1,class(tmp.X.ATOMS));
vtmp = unique(strsplit(tmp.X.description.ATOMS,' '),'stable');
if isempty(iatoms)
tmp.X.ATOMS = array2table([ttmp tmp.X.ATOMS],'VariableNames',[{'TIMESTEP'} vtmp]);
else
[~,~,jatoms] = intersect(iatoms,tmp.X.ATOMS(:,ismember(vtmp,'id')),'stable');
tmp.X.ATOMS = array2table([ttmp(jatoms) tmp.X.ATOMS(jatoms,:)],'VariableNames',[{'TIMESTEP'} vtmp]);
end
oktable = true;
else
oktable = false;
end
if it==1
X = tmp.X;
else
if oktable
X.TIMESTEP(end+1) = tmp.X.TIMESTEP;
if isfield(tmp.X,'TIME'), X.TIME(end+1) = tmp.X.TIME; end
X.ATOMS = [X.ATOMS;tmp.X.ATOMS];
else
X(end+1) = tmp.X; %#ok<*AGROW>
end
end
end
dispf('...loaded in %0.3g s',etime(clock,t0))
X.TIMESTEPS = itimes_all';
X.TIMESTEPfirst = min(itimes_all);
X.TIMESTEPlast = max(itimes_all);
return
end
% return an error if the original file does not exist
if ~exist(filename,'file'), error('the file ''%s'' does not exist',filename), end
% Check extensions: .bin (binary mode) and .gz (text mode but gzipped)
[~,~,de] = fileparts(filename);
if strcmpi(de,'.bin'), fprintf('\n>> Switch to binary <<\n\n'), X = dumpbinary(filename,1,molecules); return, end
isgz = strcmpi(de,'.gz'); if isgz, fprintf('\n>> Switch to GZIPPED text mode <<\n\n'), end
%d = dir(filename);
counton = strcmpi(action,'count'); % || (d.bytes>prefetchthreshold);
nmol = length(molecules);
% detect large file
if ~isgz
nfo = dir(filename);
islargefile = (nfo.bytes>maxfilesize) || forcesplit;
else
islargefile = false;
end
if islargefile && ~exist(makesplitdir(filename),'dir'), mkdir(makesplitdir(filename)); end
ipartlargefile = 0;
% PREALLOCATE when collect is active
if collecton
fprintf('Count timesteps for all fields...\nPreallocation to preserve memory usage...\nThe file will be read twice.\n')
fprintf('DATA format: ''%s'' [%s]\n',datatype,numpattern)
clock0 = clock;
if isempty(itimes) || isempty(iatoms)
X = lamdumpread(filename,'count',molecules,itimes,iatoms); % whole procedure
else % the structure is assumed to be known (bad prediction will generate an error)
fprintf('Assume a general DUMP format (faster)\nBad parameters for iatoms and itimes will result in an error.\n\n')
ntimes = length(itimes); natoms = length(iatoms);
X = struct('TIMESTEP',[1 1 ntimes],...
'NUMBEROFATOMS',[1 1 ntimes],...
'BOXBOUNDS',[3 2 ntimes],...
'ATOMS',[natoms 5 11]);
end
if isempty(itimes), fprintf('\nFOUND records: [size]\n\n'), else fprintf('\nFOUND records: [size]\n between timesteps [%d %d]\n',min(itimes),max(itimes)), end
disp(X)
restart=true; iserrorgenerated = false;
key=fieldnames(X)'; siz = X.(key{1});
if isempty(itimes), ntimes = siz(3); else ntimes=length(itimes); end
if isempty(iatoms), natoms = siz(1); else natoms = length(iatoms); end
while restart
try
for key=fieldnames(X)'
siz = X.(key{1});
if isempty(itimes), ntimes = siz(3); else ntimes=length(itimes); end
if nmol && strcmp(key{1},'ATOMS') % molecules assembling
if isempty(iatoms), listofmol = 1:nmol; else listofmol = iatoms; end
if (max(listofmol)>nmol) || (min(listofmol)<1), iserrorgenerated=true; error('invalid molecules index'), end
X.MOLECULES = cell(length(listofmol),1);
for imol=listofmol, X.MOLECULES{imol} = zeros(length(molecules{imol}),3,ntimes,datatype); end
else
if strcmp(key{1},'ATOMS')
if isempty(iatoms), natoms = siz(1); else natoms = length(iatoms); end
X.(key{1}) = zeros(squeezedims([natoms siz(2) ntimes]),datatype);
else % other fields
X.(key{1}) = zeros(squeezedims([siz(1:end-1) ntimes]),datatype);
end
end
end
restart = false;
catch ME
if iserrorgenerated, rethrow(ME), end
fprintf('Insifficient memory for %d timesteps ranged between [%d %d] and %d atoms ranged between [%d %d]\n',ntimes,min(itimes),max(itimes),natoms,min(iatoms),max(iatoms))
disp('a 1 for 2 decimation rule wil be tented')
if length(itimes)==1 || isempty(itimes), rethrow(ME), end
itimes = unique(round(linspace(min(itimes),max(itimes),round(ntimes/2)))); % decimation
end % try/catch
end % wend
fprintf('''%s'' preallocated in %0.4g s\n\n',filename,etime(clock,clock0))
counton = false;
else % no preallocation (efficient but less usefull for interpretation)
X = [];
end
% fast scan file based on BLOCKS and PATTERNS (low memory usage)
disp('LAMMPS DUMP file...'), fileinfo(filename)
start = clock;
if isgz, fid = gzipr(filename); else fid = fopen(filename,'r','n','US-ASCII'); end
foundrecord = true;
eof = false;
[RAWKEYLIST,KEYLIST,DESRCLIST] = deal({}); KEYOCC = []; %raw counters (replace pos.('key') when it is not defined)
CURRENTOCC = -Inf; % number of occurence
if isempty(itimes), itimesmax=Inf; else itimesmax = max(itimes); end
screen=''; isfirstframe = true; nodisplay = false; tsplit = clock;
while foundrecord && ~eof
% look for the keyword ITEM (stop when it does not match, i.e. numerical data
if isgz, [key,eof,row] = textscanp(fid,variablepattern,'delimiter','\n');
else key = textscan(fid,variablepattern,'delimiter','\n'); end
if ~isempty(key) && ~isempty(key{1})
if isfirstframe && any(strcmp(key{1},{'TIME','TIMESTEP'})), keyforfirstframe = key{1}; isfirstframe = false; end
if islargefile && strcmp(key{1},keyforfirstframe) && ~isempty(X) && isfield(X,keyforfirstframe)
X.description = cell2struct(DESRCLIST',KEYLIST');
X.nfo = nfo; ipartlargefile = ipartlargefile +1;
save(makesplit(filename,X.TIMESTEP),'X')
[~,nfotxt] = fileinfo(makesplit(filename,X.TIMESTEP),'',false);
screen = dispb(screen,'[%d:%d] split in %0.3g s ==> %s',ipartlargefile,X.TIMESTEP,etime(clock,tsplit),nfotxt);
X = []; nodisplay = true; tsplit = clock;
end
%key{1}{1} = regexprep(key{1}{1},excludedexpr,''); % remove undesirable set of characters (for new version of LAMMPS may 2009)
% counters based on raw names
if ~ismember(key{1}{1},RAWKEYLIST)
RAWKEYLIST{end+1} = key{1}{1}; %#ok
KEYLIST{end+1} = regexprep(key{1}{1},{'[a-z_]','\d','\s*$','\s+','_[A-Z]?\[\d+\]','_','[\[\]]'},{'','','','_','',' ',''}); %#ok % only A-Z characters are kept
DESRCLIST{end+1} = regexprep(key{1}{1},{'^[A-Z\s]*([a-z])','^\s*','\s*$',KEYLIST{end}},{'$1','','',''}); %#ok % '[A-Z]', the remaining characters are kept for description
KEYLIST{end} = regexprep(KEYLIST{end},{'\s+','_[A-Z]*'},{'_',''}); % remove residual _XX names (e.g., _VM)
KEYOCC(end+1)=0; %#ok
end % create the counter if it does not exist
ikey = find(ismember(RAWKEYLIST,key{1}{1})); %key index
KEYOCC(ikey)=KEYOCC(ikey)+1; %#ok % increment the counter (not order dependent)
% keyname standardization
%key = strrep(key{1}{1},' ','_'); % remove all spaces
key = KEYLIST{ikey};
if ~isgz % read the next line
row = '';
while isempty(row) && ~feof(fid) % while the current line is not valid
currentpos = ftell(fid); % current position in the file
row = fgetl(fid); % read a single line/row to identify the pattern
end
end
n = length(find(strtrim(row)==numsep)); % number of data per row
if ~isempty(row) % records were found
% Read values
if ~counton && ~nodisplay, display(1.001,'[%d] %s reading...',KEYOCC(ikey),KEYLIST{ikey}); end
clock1 = clock;
if isgz
[val,eof] = textscanp(fid,[repmat([numpattern numsep],1,n) numpattern],'delimiter','\n','CollectOutput', 1);
else
fseek(fid,currentpos,'bof'); % go back to the last position
val = textscan(fid,[repmat([numpattern numsep],1,n) numpattern],'delimiter','\n','CollectOutput', 1); % read the values
end
% Timestep counter
if CURRENTOCCif counton, screen=dispb(screen,'Preallocate TIMESTEP index: %d ',CURRENTOCC); end, end
if CURRENTOCC>itimesmax, eof = true; end % force end of file
% Check validity of the current time step, sort ATOMS values, verbosity
% Note that a robust/clean counter is used when a PREALLOCATION is performed, if not a raw counter (field order dependent) is used
if ~counton
if isempty(itimes), validtime = true; storeposition = KEYOCC(ikey);
else
validtime = ismember(KEYOCC(ikey),itimes);
if validtime, storeposition = find(itimes==KEYOCC(ikey)); storeposition=storeposition(1); end
end
if ~nodisplay, if numel(val{1})<2, display(1.002,'= %0.6g',val{1}), else, display(1.002,'= %s%s array',sprintf('%dx',size(val{1})),char(8)),end, end
if strcmp(key,'ATOMS') && validtime, if ~nodisplay, display(1.003,'>>sorting'), end, [~,ia] = sort(val{1}(:,1)); val{1} = val{1}(ia,:); end
if ~nodisplay, display(1.004,'... end in %0.3g s\n',etime(clock,clock1)); end
end
% Assign values
if counton % count values (if requested)
if isfield(X,key), X.(key)(end)=X.(key)(end)+1; else X.(key)=[size(val{1}) 1]; end
elseif collecton % collect
if strcmp(key,'ATOMS') && nmol % replace ATOMS by MOLECULES
key = 'MOLECULES';
if validtime
if isempty(iatoms), listofmol = 1:nmol; else, listofmol = iatoms; end
for imol = listofmol, X.(key){imol}(:,:,storeposition)=val{1}(molecules{imol},3:5); end
end
elseif validtime
if numberofel(X.(key),val{1})==numel(val{1})
switch numberofdims(val{1})
case 0, X.(key)(storeposition) = val{1};
case 1, X.(key)(:,storeposition) = val{1}(:);
case 2, X.(key)(:,:,storeposition)= val{1};
end
end
end
elseif validtime
if ~isfield(X,key), X.(key) = val{1}; % create a new keyword
elseif ~iscell(X.(key)) % convert to a cell array to store results from several records
if numel(val{1})==1, X.(key)(end+1) = val{1};
else X.(key) = {X.(key) val{1}}; %#ok<*SEPEX>
end
else % append records
if numel(val{1})==1, X.(key)(end+1)=val{1};
else X.(key){end+1}=val{1};
end
end
end % count on
else % likely corrupted file
foundrecord = false;
end
else
foundrecord = false;
end
end
if isgz, try textscanp(fid); catch ME, fprintf('*-*- ERROR -*-*'), rethrow(ME), end, gzipr(fid); else fclose(fid); end
fprintf('... end in %0.3g s\n',etime(clock,start))
if isempty(X), disp('It seems not to be a valid file'), return, end
% last frame for large file
if islargefile && ~isempty(X)
X.description = cell2struct(DESRCLIST',KEYLIST');
X.nfo = nfo;
save(makesplit(filename,X.TIMESTEP),'X')
nfotxt = fileinfo(makesplit(filename,X.TIMESTEP),'',false);
dispb(screen,'[last=%d] %d split files completed in %0.3g s\n in\t%s\n',X.TIMESTEP,ipartlargefile+1,etime(clock,start),nfotxt.path);
return
end
%% rearrangement of outputs to match description, when possible
% added INRAE\Olivier Vitrac, 2021-02-15 (RC)
X.description=cell2struct(DESRCLIST',KEYLIST');
if length(X.TIMESTEP)>1 % more than one frame
iseries = find(~cellfun(@isempty,DESRCLIST));
if isempty(iseries), return; end
nseries = length(iseries);
for j=1:nseries
tmp = unique(strsplit(DESRCLIST{iseries(j)},' '),'stable');
siz = size(X.(KEYLIST{iseries(j)}){1});
if (siz(2)==length(tmp)) && strcmpi(KEYLIST{iseries(j)},'ATOMS') % === ATOMS ===
nt = length(X.(KEYLIST{iseries(j)}));
na = zeros(nt,1);
for it=1:nt, na(it) = size(X.(KEYLIST{iseries(j)}){it},1); end
grps = unique(na,'stable'); ngrps = length(grps);
for igrp = 1:ngrps
jgrp = find(na==grps(igrp)); njgrp = length(jgrp);
tmp2 = reshape(permute(cat(length(siz)+1,X.(KEYLIST{iseries(j)}){jgrp}),[1 3 2]),[grps(igrp)*njgrp siz(2)]);
t = ones(grps(igrp),1,'single')*X.TIMESTEP(jgrp); % time steps
tmp2 = [t(:) tmp2]; %#ok
if ngrps==1, suffix=''; else, suffix=sprintf('_grp%02d',igrp); end
X.([KEYLIST{iseries(j)} suffix]) = array2table(tmp2,'VariableNames',[{'TIMESTEP'} tmp]);
end
if ngrps>1, X = rmfield(X,'ATOMS'); end
elseif prod(siz)>max(siz) && strcmpi(KEYLIST{iseries(j)},'BOX') % array %% === BOX_BOUNDS ===
try
X.(KEYLIST{iseries(j)}) = cat(length(siz)+1,X.(KEYLIST{iseries(j)}){:});
siz = size(X.(KEYLIST{iseries(j)}));
if ndims(X.(KEYLIST{iseries(j)}))==3
X.(KEYLIST{iseries(j)}) = reshape(permute(X.(KEYLIST{iseries(j)}),[3 2 1]),[siz(3) siz(1)*siz(2)]);
end
catch
fprintf('\nWARNING: unable to collect all data for ''%s''\n\t The likely cause is incompatible size.\n',KEYLIST{iseries(j)});
end
end
end % next j (series)
elseif isfield(X,'ATOMS')
end % if nframes > 1
% save prefetch - added 2023-03-23
dispf('save prefetch file (for ''%s'')...',filename), t0=clock;
save(prefetchfile,'X','filename')
if ~exist(prefetchfile,'file')
switch73 = true;
else
details = dir(prefetchfile);
switch73 = details.bytes<1000;
end
if switch73
dispf('--> too large file for default save format, switch to v7.3 (HDF5) format')
save(prefetchfile,'X','filename','-v7.3');
end
fileinfo(prefetchfile)
dispf('...saved in %0.3g s',etime(clock,t0))
end % ENDFUNCTION
%% =====================================================
% PRIVATE FUNCTIONS
% ====================================================
% Display function with id manager
function display(id,varargin)
% manage display at prescribed intervals with an id manager
% id = 2.001 means event=2, message instance = 1;
persistent TDISP
if isempty(TDISP), TDISP = struct('t',clock,'updateinterval',0.5,'idevent',NaN,'idinstance',NaN); end
t = clock; dt = etime(t,TDISP.t);
idevent = floor(id); idinstance = round(1000*(id-idevent));
if TDISP.idevent==idevent % same event
forced = (idinstance>TDISP.idinstance);% the instances are assumed linked if incremented
else
forced = false;
end
if ((dt>TDISP.updateinterval) && (idinstance==1)) || forced
TDISP.t = t;
TDISP.idevent = idevent;
TDISP.idinstance = idinstance;
fprintf(varargin{:});
end
end % ENDFUNCTION
% BASIC FUNCTIONS to work multidimensional arrays (replace SQUEEZE, NUMEL, NDIMS)
function d=squeezedims(d1)
% move singleton dimension
d = d1(d1>1);
if (length(d)==1), if d<20, d = [1 d]; else d = [d 1]; end, end %% [1 d] improve the readability of small vectors
end % ENDFUNCTION
function ne=numberofel(x,ref)
% return the expected number of elements
dref = numberofdims(ref);
ne = size(x);
ne = prod(ne(1:min(length(ne),dref)));
end % ENDFUNCTION
function d=numberofdims(x)
% returns the expected number of dimension of x (0=scalar, 1=vector, 2=matrix)
d = length(find(size(x)>1));
end % ENDFUNCTION
%% CODE FOR BINARY DUMP ===============================
% TO DO LIST: to implement: itimes and imolecules
function X=dumpbinary(filename,colsort,molecules)
%READ BINARY DUMP FILES based on tools/binary2txt.cpp (Matlab comment is given as comment)
% Field names are a priori set since this information is not available in the binary files
%'collect' is always applied in this optimized version
% Definitions
colsort_default = 1;
% arg check
if nargin<2, colsort = []; end
if nargin<3, molecules = {}; end
if isempty(colsort), colsort=colsort_default; end
if isempty(molecules), molecules = {}; end
nmol = length(molecules);
% 2 steps reading
clock0 = clock;
fp = fopen(filename,'r'); % use fopen(filename,'r','b') if any problem in future Matlab versions
for do={'prealloc' 'load'}
switch do{1}
case 'prealloc'
disp('BINARY Memory preallocation...')
X = struct('TIMESTEP',[],'NUMBEROFATOMS',[],'BOXBOUNDS',[],'ATOMS',[]);
[ncol,nproc,nbuff] = deal([]);
case 'load'
disp('BINARY reading...');
X.BOXBOUNDS = zeros(3,2,numel(X.TIMESTEP)); % 3 dim array
if nmol
X.MOLECULES = cell(nmol,1);
for imol=1:nmol, X.MOLECULES{imol}=zeros(length(molecules{imol}),3,numel(X.TIMESTEP)); end
X.ATOMS = zeros(max(sum(nbuff,2))/min(ncol),max(ncol)); % only used as a buffer
else
X.ATOMS = zeros(max(sum(nbuff,2))/min(ncol),max(ncol),numel(X.TIMESTEP)); % 3 dim array
end
end
[u,v]=deal(0); % u=low cost index, v=expensive index (to be used only with LOAD)
while ~feof(fp)
switch do{1}, case 'prealloc', v=1; case 'load', v=v+1; end
u = u+1;
tmp = fread(fp,1,'int');
if ~isempty(tmp) || ~feof(fp)
clock1 = clock;
X.TIMESTEP(u) = tmp;
X.NUMBEROFATOMS(u) = fread(fp,1,'int');
X.BOXBOUNDS(:,:,v) = fread(fp,[2 3],'double')';
ncol(u) = fread(fp,1,'int');
nproc(u) = fread(fp,1,'int');
k = 1;
iatomtime = min(v,size(X.ATOMS,3));
for i=1:nproc(u)
nbuff(u,i) = fread(fp,1,'int');
switch do{1}
case 'prealloc', fseek(fp,8*nbuff(end,i),'cof');
case 'load'
if i==1; display(10.001,'time step = %d\n',tmp), end
nlig = nbuff(u,i)/ncol(u);
X.ATOMS(k:(k+nlig-1),:,iatomtime)= fread(fp,[ncol(u) nlig],'double')';
k = k + nlig;
end
end
switch do{1}
case 'load'
display(10.002,'%s >>sorting')
[~,is] = sort(X.ATOMS(:,colsort,iatomtime));
X.ATOMS(:,:,iatomtime) = X.ATOMS(is,:,iatomtime);
if nmol, for imol=1:nmol, X.MOLECULES{imol}(:,:,v)=X.ATOMS(molecules{imol},3:5); end, end
display(10.003,'... end in %0.3g s\n',etime(clock,clock1))
end
end
end
fseek(fp,0,'bof');
end
fclose(fp);
fprintf('''%s'' loaded in %0.3g s\n',filename,etime(clock,clock0))
end %ENDFUNCTION ===============================
% INITIAL PROTOTYPE BASED ON BINARY2TXT
% filename = 'dump.atom.bin';
% fp = fopen(filename,'r');
% while ~feof(fp)
% ntimestep = fread(fp,1,'int');
% natoms = fread(fp,1,'int');
% bounds = fread(fp,[2 3],'double');
% size_one = fread(fp,1,'int');
% nchunk = fread(fp,1,'int');
% for i=1:nchunk
% n = fread(fp,1,'int');
% buf = fread(fp,[size_one n/size_one],'double');
% end
% end
% fclose(fp);
LASTDIR extrait le nom du dernier r�pertoire du chemin (et la racine correspondante)
ex. rep = last_dir(chemin)
options : [rep,root] = last_dir(chemin)
function [rep,root] = lastdir(chemin)
% LASTDIR extrait le nom du dernier r�pertoire du chemin (et la racine correspondante)
% ex. rep = last_dir(chemin)
% options : [rep,root] = last_dir(chemin)
% Woodox 1.0 - 27/02/01 - Olivier Vitrac - rev. 02/02/08
% Revision history
% 24/01/08 optimization, filesep instead of '\'
% 02/02/08 Unix compatibility
% 07/02/2019 fix lastdir(filesep)
% arg check
if nargin<1, chemin = ''; end
if isempty(chemin), chemin = pwd; end
if ~ischar('the argument must be a string'), end
if isunix
if (length(chemin)<1), warning('''%s'' does not seem to be a valid path',chemin), rep = ''; end
else
if (length(chemin)==1) && (chemin==filesep), warning('''%s'' does not seem to be a valid path',chemin), rep = ''; end
end
chemin = remove_slash(chemin);
ind = find(chemin == filesep);
if any(ind) && ind(end)end)+1:end);
if nargout>1, root = remove_slash(chemin(1:ind(end))); end
else
rep = chemin;
if nargout>1, root = ''; end
end
function chemin = remove_slash(chemin_sl)
if isempty(chemin_sl) || (length(chemin_sl)==1 && (chemin_sl(end)==filesep) )
chemin = '';
elseif chemin_sl(end)==filesep && (isunix || (chemin_sl(end-1)~=':') )
chemin = chemin_sl(1:end-1);
else
chemin = chemin_sl;
end
NEARESTPOINT - find the nearest value in another vector
IND = NEARESTPOINT(X,Y) finds the value in Y which is the closest to
each value in X, so that abs(Xi-Yk) => abs(Xi-Yj) when k is not equal to j.
IND contains the indices of each of these points.
Example
NEARESTPOINT([1 4 12],[0 3]) -> [1 2 2]
Example
[IND,D] = ... also returns the absolute distances in D,
that is D == abs(X - Y(IND))
Example
NEARESTPOINT(X, Y, M) specifies the operation mode M:
'nearest' : default, same as above
'previous': find the points in Y that are closest, but preceeds a point in X
NEARESTPOINT([0 4 3 12],[0 3],'previous') -> [NaN 2 1 2]
'next' : find the points in Y that are closets, but follow a point in X
NEARESTPOINT([1 4 3 12],[0 3],'next') -> [2 NaN 2 NaN]
Example
If there is no previous or next point in Y for a point X(i), IND(i)
will be NaN.
Example
X and Y may be unsorted.
Example
This function is quite fast, and especially suited for large arrays with
time data. For instance, X and Y may be the times of two separate events,
like simple and complex spike data of a neurophysiological study.
Example
Example
EXAMPLE
Example
disp('TEST for nearestpoint, please wait ... ') ;
M = 13 ;
tim = NaN(M,3) ;
tim(8:M,1) = 2.^[8:M].' ;
figure('Name','NearestPointTest','doublebuffer','on') ;
h = plot(tim(:,1),tim(:,2),'bo-',tim(:,1),tim(:,3),'rs-') ;
xlabel('N') ;
ylabel('Time (seconds)') ;
title('Test for Nearestpoint function ... please wait ...') ;
set(gca,'xlim',[0 max(tim(:,1))+10]) ;
for j=8:M,
N = 2.^j ;
A = rand(N,1) ; B = rand(N,1) ;
tic ;
D1 = zeros(N,1) ;
I1 = zeros(N,1) ;
for i=1:N,
[D1(i), I1(i)] = min(abs(A(i)-B)) ;
end
tim(j,2) = toc ;
pause(0.1) ;
tic ;
[I2,D2] = nearestpoint(A,B) ;
tim(j,3) = toc ;
% isequal(I1,I2)
set(h(1),'Ydata',tim(:,2)) ;
set(h(2),'Ydata',tim(:,3)) ;
drawnow ;
end
title('Test for Nearestpoint function') ;
legend('Traditional for-loop','Nearestpoint',2) ;
function [IND, D] = nearestpoint(x,y,m)
% NEARESTPOINT - find the nearest value in another vector
%
% IND = NEARESTPOINT(X,Y) finds the value in Y which is the closest to
% each value in X, so that abs(Xi-Yk) => abs(Xi-Yj) when k is not equal to j.
% IND contains the indices of each of these points.
% Example:
% NEARESTPOINT([1 4 12],[0 3]) -> [1 2 2]
%
% [IND,D] = ... also returns the absolute distances in D,
% that is D == abs(X - Y(IND))
%
% NEARESTPOINT(X, Y, M) specifies the operation mode M:
% 'nearest' : default, same as above
% 'previous': find the points in Y that are closest, but preceeds a point in X
% NEARESTPOINT([0 4 3 12],[0 3],'previous') -> [NaN 2 1 2]
% 'next' : find the points in Y that are closets, but follow a point in X
% NEARESTPOINT([1 4 3 12],[0 3],'next') -> [2 NaN 2 NaN]
%
% If there is no previous or next point in Y for a point X(i), IND(i)
% will be NaN.
%
% X and Y may be unsorted.
%
% This function is quite fast, and especially suited for large arrays with
% time data. For instance, X and Y may be the times of two separate events,
% like simple and complex spike data of a neurophysiological study.
%
%
% EXAMPLE
%
% disp('TEST for nearestpoint, please wait ... ') ;
% M = 13 ;
% tim = NaN(M,3) ;
% tim(8:M,1) = 2.^[8:M].' ;
% figure('Name','NearestPointTest','doublebuffer','on') ;
% h = plot(tim(:,1),tim(:,2),'bo-',tim(:,1),tim(:,3),'rs-') ;
% xlabel('N') ;
% ylabel('Time (seconds)') ;
% title('Test for Nearestpoint function ... please wait ...') ;
% set(gca,'xlim',[0 max(tim(:,1))+10]) ;
% for j=8:M,
% N = 2.^j ;
% A = rand(N,1) ; B = rand(N,1) ;
% tic ;
% D1 = zeros(N,1) ;
% I1 = zeros(N,1) ;
% for i=1:N,
% [D1(i), I1(i)] = min(abs(A(i)-B)) ;
% end
% tim(j,2) = toc ;
% pause(0.1) ;
% tic ;
% [I2,D2] = nearestpoint(A,B) ;
% tim(j,3) = toc ;
% % isequal(I1,I2)
% set(h(1),'Ydata',tim(:,2)) ;
% set(h(2),'Ydata',tim(:,3)) ;
% drawnow ;
% end
% title('Test for Nearestpoint function') ;
% legend('Traditional for-loop','Nearestpoint',2) ;
% Created : august 2004
% Author : Jos van der Geest
% Email : matlab@jasen.nl
% Modifications :
% aug 25, 2004 - corrected to work with unsorted input values
% nov 02, 2005 -
% apr 28, 2006 - fixed problem with previous points
% Revision INRA\Olivier Vitrac - 25/08/11
% argcheck
if nargin<2, error('2 arguments are required'); end
if nargin==2,
m = 'nearest' ;
else
if ~ischar(m),
error('Mode argument should be a string (either ''nearest'', ''previous'', or ''next'')') ;
end
end
if ~isa(x,'double') || ~isa(y,'double'),
error('X and Y should be double matrices') ;
end
% sort the input vectors
sz = size(x) ;
[x, xi] = sort(x(:)) ;
[~, xi] = sort(xi) ; % for rearranging the output back to X
nx = numel(x) ;
cx = zeros(nx,1) ;
qx = isnan(x) ; % for replacing NaNs with NaNs later on
[y,yi] = sort(y(:)) ;
ny = length(y) ;
cy = ones(ny,1) ;
xy = [x ; y] ;
[~, xyi] = sort(xy) ;
cxy = [cx ; cy] ;
cxy = cxy(xyi) ; % cxy(i) = 0 -> xy(i) belongs to X, = 1 -> xy(i) belongs to Y
ii = cumsum(cxy) ;
ii = ii(cxy==0).' ; % ii should be a row vector
% reduce overhead
clear cxy xy xyi ;
switch lower(m),
case {'nearest','near','absolute'}
% the indices of the nearest point
ii = [ii ; ii+1] ;
ii(ii==0) = 1 ;
ii(ii>ny) = ny ;
yy = y(ii) ;
dy = abs(repmat(x.',2,1) - yy) ;
[~, ai] = min(dy) ;
IND = ii(sub2ind(size(ii),ai,1:nx)) ;
case {'previous','prev','before'}
% the indices of the previous points
ii(ii < 1) = NaN ;
IND = ii ;
case {'next','after'}
% the indices of the next points
ii = ii + 1 ;
ii(ii>ny) = NaN ;
IND = ii ;
otherwise
error('Unknown method "%s"',m);
end
IND(qx) = NaN ; % put NaNs back in
% IND = IND(:) ; % solves a problem for x = 1-by-n and y = 1-by-1
if nargout==2,
% also return distance if requested;
D = NaN(1,nx) ;
q = ~isnan(IND) ;
D(q) = abs(x(q) - y(IND(q))) ;
D = reshape(D(xi),sz) ;
end
% reshape and sort to match input X
IND = reshape(IND(xi),sz) ;
% because Y was sorted, we have to unsort the indices
q = ~isnan(IND) ;
IND(q) = yi(IND(q)) ;
% END OF FUNCTION
PACKSPH returns the HCP, FCC, or SC packing of siz spheres of radius r
Syntax:
W = packSPH(siz,r,typ)
Inputs:
siz : [5 5 5] number of spheres along x,y,z
if siz is a scalar, the same siz is applied to all dimensions [siz siz siz]
r : bead radius
typ : 'HCP' (default, period 2), 'FCC' (period 3), or 'SC' (period 1)
'SC2' and 'SC3' implement Simple Cubic with period 2 and period 3, respectively
Output:
X : [size(1)xsize(2)xsize(3)] x 3 centers
Example
X = packSPH(5)
Example
See also
interp3SPH, interp2SPH, kernelSPH
function X = packSPH(siz, r, typ)
% PACKSPH returns the HCP, FCC, or SC packing of siz spheres of radius r
%
% Syntax:
% W = packSPH(siz,r,typ)
% Inputs:
% siz : [5 5 5] number of spheres along x,y,z
% if siz is a scalar, the same siz is applied to all dimensions [siz siz siz]
% r : bead radius
% typ : 'HCP' (default, period 2), 'FCC' (period 3), or 'SC' (period 1)
% 'SC2' and 'SC3' implement Simple Cubic with period 2 and period 3, respectively
% Output:
% X : [size(1)xsize(2)xsize(3)] x 3 centers
%
% Example:
% X = packSPH(5)
%
% See also: interp3SPH, interp2SPH, kernelSPH
% 2023-02-20 | INRAE\Olivier Vitrac | rev. 2023-09-11
% Revision History
% 2023-09-11 add SC
% Argument check
rdefault = 0.5;
typdefault = 'HCP';
if nargin < 1, siz = []; end
if nargin < 2, r = []; end
if nargin < 3, typ = ''; end
if numel(siz) == 1, siz = [1,1,1] * siz; end
if isempty(siz) || numel(siz) ~= 3, error('siz must be a 1x3 or 3x1 vector'); end
if isempty(r), r = rdefault; end
if isempty(typ), typ = typdefault; end
if ~ischar(typ), error('typ must be a char array'); end
switch upper(typ)
case 'HCP'
lattice_type = 0;
case 'FCC'
lattice_type = 1;
case 'SC'
lattice_type = 2;
case 'SC2'
lattice_type = 3;
case 'SC3'
lattice_type = 4;
otherwise
error('Valid packaging type is ''HCP'', ''FCC'', ''SC'', ''SC2'', or ''SC3''')
end
% Lattice
[i, j, k] = ndgrid(0:(siz(1) - 1), 0:(siz(2) - 1), 0:(siz(3) - 1));
[i, j, k] = deal(i(:), j(:), k(:));
switch lattice_type
case 0 % HCP
X = [
2 * i + mod(j + k, 2), ...
sqrt(3) * (j + mod(k, 2) / 3), ...
(2 * sqrt(6) / 3) * k ...
] * r;
case 1 % FCC
X = [
2 * i + mod(j + k, 2), ...
sqrt(3) * (j + mod(k, 2) / 3) + (mod(k, 3) == 2), ...
(2 * sqrt(6) / 3) * k ...
] * r;
case 2 % SC (Simple Cubic)
X = [
i, ...
j, ...
k ...
] * r * 2;
case 3 % SC2 (Simple Cubic with a period 2)
X = [
i + 0.5 * mod(j + k, 2), ...
j + 0.5 * mod(k + i, 2), ...
k + 0.5 * mod(i + j, 2) ...
] * r * 2;
case 4 % SC3 (Simple Cubic with a period 3)
X = [
i + mod(j + k, 3) / 3, ...
j + mod(k + i, 3) / 3, ...
k + mod(i + j, 3) / 3 ...
] * r * 2;
end
% ---- before 2023-09-11 ----
% % Lattice
% [i,j,k] = ndgrid(0:(siz(1)-1),0:(siz(2)-1),0:(siz(3)-1)); % HCP is period 2, FCC is period 3
% [i,j,k] = deal(i(:),j(:),k(:));
% X = [
% 2*i + mod(j+k,2) ...x
% sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
% (2*sqrt(6)/3)*k ... z
% ]*r; % https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
Simple HCP (hexagonal closed pack) lattice
generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
%% Simple HCP (hexagonal closed pack) lattice
% generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
% INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
% Define parameters
r = 0.5; % Radius of spheres
forceFCC = 0; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:2,0:2,0:2); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
for i = 1:size(centers, 1)
surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none');
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
%% Lucy kernel, note that s scales the kernel (different scaling in 2D and 3D)
syms h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
% radial position where the 3D kernel is equal to its average
r1 = solve(W3(r)==1,r,'Maxdegree',4); % all solutions (4)
% only the second root is real and positive
vpa(subs(r1,h,1))
r1 = simplify(r1(2));
% convert the kernel to a Matlab anonymous function
% function_handle with value:
% @(h,r)(1.0./h.^3.*(r./h-1.0).^3.*((r.*3.0)./h+1.0).*(-1.05e+2./1.6e+1))./pi
syms R
assume(R%% Kernel: numeric implementation
% single accuracy is used instead of double to reduce memory load
r = single(0.5);
h= single(2*r);
cutoff = @(r) single(r'-','linewidth',2), xlabel('r'), ylabel('kernel')
hold on, plot(interpleft(W(rplot),rplot,Wref),Wref,'ro','markerfacecolor',rgb('Crimson'))
line(r1expr(h)*[1;1;0],[0;1;1],'linewidth',1.5,'linestyle',':','color',rgb('deepskyblue'))
line([r h;r h],[0 0;1 1],'linewidth',1.5,'linestyle','--','color',rgb('coral'))
text(double(r),1,sprintf('\\leftarrow r_{bead}=%0.3g',r),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
text(double(h),1,sprintf('\\leftarrow h=%0.3g',h),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
% 3D field
nresolution = 200;
xw = single(linspace(min(centers(:,1))-h,max(centers(:,1))+h,nresolution));
yw = single(linspace(min(centers(:,2))-h,max(centers(:,2))+h,nresolution));
zw = single(linspace(min(centers(:,3))-h,max(centers(:,3))+h,nresolution));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
% calculate the radial distance to the center of the ith sphere
R = @(i) sqrt( (Xw-centers(i,1)).^2 + (Yw-centers(i,2)).^2 + (Zw-centers(i,3)).^2 );
sumW = zeros(size(Xw),'single');
for i=1:size(centers,1)
dispf('evaluate field respective to kernel %d',i)
sumW = sumW + W(R(i));
end
% full domain
figure, isosurface(Xw,Yw,Zw,sumW,1), axis equal
% cut domain x>1.3
figure
sumWcut = sumW;
xcut = 1.3;
sumWcut(xw>xcut,:,:) = [];
[Xwcut,Ywcut,Zwcut] = deal(Xw,Yw,Zw);
Xwcut(xw>xcut,:,:) = [];
Ywcut(xw>xcut,:,:) = [];
Zwcut(xw>xcut,:,:) = [];
p1 = patch(isosurface(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor',rgb('tomato'),'EdgeColor','none');
p2 = patch(isocaps(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor','interp','EdgeColor','none');
colormap(gray(100)), camlight left, camlight, lighting gouraud, view(-138,28), axis equal
% slice
figure, hs= slice(Xw,Yw,Zw,sumW,single(1:3),single(1:3),single([])); set(hs,'edgecolor','none','facealpha',0.5), axis equal
%% Calculate the field between two beads
r = 0.5; % Radius of spheres
forceFCC = 1; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:4,0:4,0:4); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
ncenters = size(centers,1);
for i = 1:size(centers, 1)
hs(i) = surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none','facealpha',0.2);
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
% the most central bead and found the next neighbors (coordination number = 12 with HCP)
[~,icentral] = min(sum((centers-mean(centers)).^2,2));
set(hs(icentral),'facecolor',rgb('Crimson'),'FaceAlpha',1)
dcentral = sqrt(sum((centers-centers(icentral,:)).^2,2));
icontact = find( (dcentral>=2*r-0.0001) & (dcentral<=2*r+0.0001) );
ncontact = length(icontact);
set(hs(icontact),'facecolor',rgb('ForestGreen'),'FaceAlpha',1)
%% Averaged field between the icentral (red) bead and the icontact (green) one
nd = 1000;
d = linspace(-0.1*r,2*r+0.1*r,nd)'; % support
r = 0.5;
hlist = r*linspace(1.5,4,20);
nh = length(hlist);
sumW = zeros(nd,nh,ncontact);
for ih = 1:nh
h= hlist(ih);
cutoff = @(r) single(rfor j = 1:ncontact
xyz0 = centers(icentral,:); % red bead coordinates
xyz = centers(icontact(j),:); % green bead coordinated
direction = (xyz-xyz0)/norm(xyz-xyz0);
xyzd = xyz0 + direction .* d;
R = @(i) sqrt( sum((xyzd-centers(i,:)).^2,2));
for i=1:size(centers,1)
sumW(:,ih,j) = sumW(:,ih,j) + W(R(i));
end
end
end
figure('defaultAxesColorOrder',parula(nh))
leg = arrayfun(@(x) sprintf('h/r_{bead}=%0.3g',x),hlist/r,'UniformOutput',false);
hp = plot(d/r,mean(sumW,3),'-','linewidth',3);
legend(hp,leg,'location','eastoutside','fontsize',10,'box','off')
formatax(gca,'fontsize',12)
xlabel('r/r_{bead}','fontsize',16)
ylabel('density','fontsize',16)
Simple HCP (hexagonal closed pack) lattice
generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
%% Simple HCP (hexagonal closed pack) lattice
% generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
% INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
% Define parameters
r = 0.5; % Radius of spheres
forceFCC = 0; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:2,0:2,0:2); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
for i = 1:size(centers, 1)
surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none');
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
%% Lucy kernel, note that s scales the kernel (different scaling in 2D and 3D)
syms h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
% radial position where the 3D kernel is equal to its average
r1 = solve(W3(r)==1,r,'Maxdegree',4); % all solutions (4)
% only the second root is real and positive
vpa(subs(r1,h,1))
r1 = simplify(r1(2));
% convert the kernel to a Matlab anonymous function
% function_handle with value:
% @(h,r)(1.0./h.^3.*(r./h-1.0).^3.*((r.*3.0)./h+1.0).*(-1.05e+2./1.6e+1))./pi
syms R
assume(R%% Kernel: numeric implementation
% single accuracy is used instead of double to reduce memory load
r = single(0.5);
h= single(2*r);
cutoff = @(r) single(r'spikykernelder',2);
r1expr = matlabFunction(r1);
rplot = linspace(0,1.5*h,1e3);
Wref = 0.1:0.1:floor(W(0));
figure, plot(rplot,W(rplot),'-','linewidth',2), xlabel('r'), ylabel('kernel')
hold on, plot(interpleft(W(rplot),rplot,Wref),Wref,'ro','markerfacecolor',rgb('Crimson'))
line(r1expr(h)*[1;1;0],[0;1;1],'linewidth',1.5,'linestyle',':','color',rgb('deepskyblue'))
line([r h;r h],[0 0;1 1],'linewidth',1.5,'linestyle','--','color',rgb('coral'))
text(double(r),1,sprintf('\\leftarrow r_{bead}=%0.3g',r),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
text(double(h),1,sprintf('\\leftarrow h=%0.3g',h),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
% 3D field
nresolution = 200;
xw = single(linspace(min(centers(:,1))-h,max(centers(:,1))+h,nresolution));
yw = single(linspace(min(centers(:,2))-h,max(centers(:,2))+h,nresolution));
zw = single(linspace(min(centers(:,3))-h,max(centers(:,3))+h,nresolution));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
% calculate the radial distance to the center of the ith sphere
R = @(i) sqrt( (Xw-centers(i,1)).^2 + (Yw-centers(i,2)).^2 + (Zw-centers(i,3)).^2 );
sumW = zeros(size(Xw),'single');
for i=1:size(centers,1)
dispf('evaluate field respective to kernel %d',i)
sumW = sumW + W(R(i));
end
% full domain
figure, isosurface(Xw,Yw,Zw,sumW,1), axis equal
% cut domain x>1.3
figure
sumWcut = sumW;
xcut = 1.3;
sumWcut(xw>xcut,:,:) = [];
[Xwcut,Ywcut,Zwcut] = deal(Xw,Yw,Zw);
Xwcut(xw>xcut,:,:) = [];
Ywcut(xw>xcut,:,:) = [];
Zwcut(xw>xcut,:,:) = [];
p1 = patch(isosurface(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor',rgb('tomato'),'EdgeColor','none');
p2 = patch(isocaps(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor','interp','EdgeColor','none');
colormap(gray(100)), camlight left, camlight, lighting gouraud, view(-138,28), axis equal
% slice
figure, hs= slice(Xw,Yw,Zw,sumW,single(1:3),single(1:3),single([])); set(hs,'edgecolor','none','facealpha',0.5), axis equal
%% Calculate the field between two beads
r = 0.5; % Radius of spheres
forceFCC = 1; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:4,0:4,0:4); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
ncenters = size(centers,1);
for i = 1:size(centers, 1)
hs(i) = surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none','facealpha',0.2);
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
% the most central bead and found the next neighbors (coordination number = 12 with HCP)
[~,icentral] = min(sum((centers-mean(centers)).^2,2));
set(hs(icentral),'facecolor',rgb('Crimson'),'FaceAlpha',1)
dcentral = sqrt(sum((centers-centers(icentral,:)).^2,2));
icontact = find( (dcentral>=2*r-0.0001) & (dcentral<=2*r+0.0001) );
ncontact = length(icontact);
set(hs(icontact),'facecolor',rgb('ForestGreen'),'FaceAlpha',1)
%% Averaged field between the icentral (red) bead and the icontact (green) one
nd = 1000;
d = linspace(-0.1*r,2*r+0.1*r,nd)'; % support
r = 0.5;
hlist = r*linspace(1.5,4,20);
nh = length(hlist);
sumW = zeros(nd,nh,ncontact);
for ih = 1:nh
h= hlist(ih);
cutoff = @(r) single(rfor j = 1:ncontact
xyz0 = centers(icentral,:); % red bead coordinates
xyz = centers(icontact(j),:); % green bead coordinated
direction = (xyz-xyz0)/norm(xyz-xyz0);
xyzd = xyz0 + direction .* d;
R = @(i) sqrt( sum((xyzd-centers(i,:)).^2,2));
for i=1:size(centers,1)
sumW(:,ih,j) = sumW(:,ih,j) + W(R(i));
end
end
end
figure('defaultAxesColorOrder',parula(nh))
leg = arrayfun(@(x) sprintf('h/r_{bead}=%0.3g',x),hlist/r,'UniformOutput',false);
hp = plot(d/r,mean(sumW,3),'-','linewidth',3);
legend(hp,leg,'location','eastoutside','fontsize',10,'box','off')
formatax(gca,'fontsize',12)
xlabel('r/r_{bead}','fontsize',16)
ylabel('density','fontsize',16)
particle_flux for post-treatment of Billy' dump files (3D viscosimeter)
INRAE\Olivier Vitrac - rev. 2023-03-26
INRAE\William Jenkinson
% particle_flux for post-treatment of Billy' dump files (3D viscosimeter)
% INRAE\Olivier Vitrac - rev. 2023-03-26
% INRAE\William Jenkinson
% Dependencies (not included in MS, at least not yet)
% lamdumpread2() version 2023-03-23 or later
% buildVerletList() version 2023-03-25 or later
%
% note: be sure Olivier/INRA/Codes/MS is in your Path (MS=Molecular Studio)
% Revision history
% 2023-03-23 RC, early design based on dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv100_lite
% 2023-03-24 first interaction with Billy
% the file for design was shifted to dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv10_lite
% 2023-03-25 implementation of a full Verletlist, automatic identification of fluid-solid contacts without using any
% particular topology, the template is fully operational and used to plot the number of contacts with time
% 2023-03-06 first implementation of Hertz contacts (to be validated and extended)
% better figure management (previous results can be reloaded)
% 2023-03-29 [BRANCH - WJ] script taken to template the kinetic energy vs time
% 2023-03-31 WJ - meta data and script are up-to-date for the calculation of Kinetic Energy
% 2023-03-31 [BRANCH - WJ] script taken to template the particle flux past a point
%% read datafile
% path definitions (please add your machine name by typing localname in your command window)
switch localname
case 'LP-OLIVIER2022'
local = 'C:\Users\olivi\OneDrive - agroparistech.fr\Billy\ProductionSandbox_toOV_23-03-2023';
case 'LX-Willy2021'
local = '/Data/billy/Results/Viscosimeter_SMJ_V6/ProductionSandbox_toOV_23-03-2023';
case 'YOUR MACHINE'
local = 'it is the path where the dump file is located, results are stored at the sample place';
otherwise
error('add a case with your machine name, which is ''%s''',localname)
end
datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1';
%datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1';
fulldatafile = fullfile(local,datafile); % concatenate path (local) and data filename
X = lamdumpread2(fulldatafile); % be sure the last version of lamdumpread2() is in the same folder as this script
% result file (to store results)
resultfile = fullfile(local,['RESULT_' datafile '.mat']);
%% Region-based definitions: Wall and fluid regions
% note that the interface between regions is defined from pair-distances
types = struct('wall',1,'fluid',2);
X.ATOMS.iswall = X.ATOMS.type==types.wall; % add the column iswall to the table X.ATOMS
X.ATOMS.isfluid = X.ATOMS.type==types.fluid;% add the column isfluid to the table X.ATOMS
X.ATOMS.isundef = ~X.ATOMS.iswall & ~X.ATOMS.isfluid; % add the column isundef to the table X.ATOMS
%% Control frame
% note that the data are stored in data.ATOMS, which is a table with named colums allowing hybrid indexing
nsteps = length(X.TIMESTEP);
icurrenttime = ceil(0.5*nsteps); % index of the control frame (used to set basic definitions before more advanced interpretation)
currenttime = X.TIMESTEP(icurrenttime);
rawframe = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','type','x','y','z','iswall','isfluid','isundef'});
frame = table2array(rawframe(:,{'x','y','z'})); % generate an array of coordinates
% % Build the main Verlet list (for all particles)
% % V use the natural indexing instead of rawframe.id
% cutoff = 60e-6; % empty or NaN value will force an automatic estimation of cutoff
% [V,cutoff,dmin] = buildVerletList(frame,cutoff); % cutoff can be omitted
%
% % Build the secondary Verlet lists (highly vectorized code)
% % V1 the neighbors of type 2 (fluid) for type 1 atoms (1..n1)
% % V2 the neighbors of type 1 (wall) for type 2 atoms (1..n2)
% idx1 = find(rawframe.type==1); n1 = length(idx1); % indices of the wall particles in the current frame
% idx2 = find(rawframe.type==2); n2 = length(idx2); % indices of the fluid particles in the current frame
% V1 = cellfun(@(v) v(rawframe.type(v)==2),V(idx1),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
% V2 = cellfun(@(v) v(rawframe.type(v)==1),V(idx2),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
% % corresponding distances d1,d2
% % some distances can be empty for d2
% d1 = arrayfun(@(i) pdist2(frame(idx1(i),:),frame(V1{i},:)),(1:n1)','UniformOutput',false); % evaluate the distance for each idx1(i)
% d2 = arrayfun(@(i) pdist2(frame(idx2(i),:),frame(V2{i},:)),(1:n2)','UniformOutput',false); % evaluate the distance for each idx2(i)
% d1min = cellfun(@min,d1); % minimum distance for each idx1()
% d2min = cellfun(@min,d2,'UniformOutput',false); % minimum distance for each idx2()
% [d2min{cellfun(@isempty,d2)}] = deal(NaN); % populate empty distances with NaN
% d2min = cat(1,d2min{:}); % we collect all now all distances (since the fluid is moving with respect with the wall)
%
% % Identify beads of type 1 (wall) directly in contact with fluids
% % this method is general and relies only on pair distances
% % the term "contact" is general (vincinity) not a "real" contact as set later
% iswallcontact = d1min < 1.5 * dmin; % condition for a wall particle to be considered possibly in contact with the fluid
% isfluidcontact = ~isnan(d2min); % the condition is less restrictive for the fluid, based on the cutoff distance
% V1contact = V1(iswallcontact); % "contact" Verlet list corresponding to V1
% V2contact = V2(isfluidcontact); % "contact" Verlet list corresponding to V1
% iwallcontact = idx1(iswallcontact);
% ifluidcontact = unique(cat(2,V1contact{:})); % within cutoff
% %
% % control plot
% % one color is assigned to each phase
% % symbols are filled if they are included in the contact Verlet list
% % type figure, rgb() to list all available colors
% colors = struct('wall',rgb('Crimson'),'fluid',rgb('DeepSkyBlue'),'none','None');
% figure, hold on
% plot3D(frame(rawframe.isundef,:),'ko');
% plot3D(frame(rawframe.iswall,:),'o','MarkerEdgeColor',colors.wall,'MarkerFaceColor',colors.none);
% plot3D(frame(iwallcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.wall);
% plot3D(frame(rawframe.isfluid,:),'o','MarkerEdgeColor',colors.fluid,'MarkerFaceColor',colors.none);
% plot3D(frame(ifluidcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.fluid);
% axis equal, axis tight, view(3)
%% [1:X] INTERPRETATION: counting the number of contact with time
% count the number of fluid beads in contact with wall (within 2*R)
R = 0.5*0.001/48; % radius of the particle (please, be very accurate)
dbond = 2*R; % bond = link between 2 atoms
wall_id = rawframe.id(iwallcontact); % extract the ids matching the contact condition in the reference frame
fluid_id = rawframe.id(ifluidcontact); % idem for the fluid particles within the contact Verlet list
% config setup
E = 2e4 ; % ref:2e6, Hertzdiv10:2e5, Hertzdiv100:2e4
Hertzconfig = struct('name',{'wall','fluid'},'R',R,'E',E); % entries are duplicated if not mentioned
% prepare to calculate pair distances between different beads
pairdist = @(X,Y) triu(pdist2(X,Y),0); % note that the diagonal is included here (since X and Y are different)
ncontacts = zeros(nsteps,1);
particleflux = zeros(nsteps,1);
[t_,t__] = deal(clock); %#ok<*CLOCK>
screen = '';
for icurrenttime = 2:nsteps
currenttime = X.TIMESTEP(icurrenttime);
previoustime = X.TIMESTEP(icurrenttime-1);
% --- some display, to encourage the user to be patient
if mod(icurrenttime,5)
if etime(clock,t__)>2 %#ok<*DETIM>
t__ = clock; dt = etime(t__,t_); done = 100*(icurrenttime-1)/nsteps;
screen = dispb(screen,'[%d/%d] interpretation [ done %0.3g %% | elapsed %0.3g s | remaining %0.3g s ] ...', ...
icurrenttime,nsteps,done,dt,dt*(100/done-1));
end
end % --- end of display
x1 = table2array(X.ATOMS((X.ATOMS.TIMESTEP==(previoustime)) & (X.ATOMS.type==1),{'id','x','mass'})); % raw data for the previous frame
x2 = table2array(X.ATOMS((X.ATOMS.TIMESTEP==(currenttime)) & (X.ATOMS.type==1),{'id','x','mass'})); % raw data for the current frame
passpoint = 0.0005;
for i = 1:numel(x1(:,2))
if (x1(i,2)-passpoint < 0) && (x2(i,2)-passpoint > 0)
particleflux(icurrenttime) = particleflux(icurrenttime) + 1; % sum forces along x
end
end
end
% save the data to enable a refresh of the figure without restarting this block
timesteps = X.TIMESTEP;
save(resultfile,'datafile','KEnergy','ncontacts','timesteps','Hertzconfig')
dispf('Results saved (%s):',datafile), fileinfo(resultfile)
%% PLots and figure management
% reload the data
if exist(resultfile,'file'), load(resultfile), end
% plot number of contacts vs. time
contactfigure = figure;
formatfig(contactfigure,'figname',['NumberContact' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,ncontacts,'linewidth',0.5,'Color',rgb('Crimson'))
formatax(gca,'fontsize',14)
xlabel('time (units)','fontsize',16)
ylabel('Kinetic energy of particles ()','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% plot number of Hertz projection along x vs. time
hertzfigure = figure;
formatfig(hertzfigure,'figname',['ParticleFlux' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,particleflux,'linewidth',0.5,'Color',rgb('Teal'))
formatax(gca,'fontsize',14)
xlabel('Frame (-)','fontsize',16)
ylabel('Particle flux (s^-1)','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% save images in all valid formats (including Matlab one, the data can be extracted with this format)
% filenames are identical to the dump file with the proper extension: fig, pdf, png
for myfig = [contactfigure,hertzfigure] % loop over all figures to print
figure(myfig)
saveas(gcf,fullfile(local,[get(gcf,'filename') '.fig']),'fig') % fig can be open without restarting the code
print_pdf(600,[get(gcf,'filename') '.pdf'],local,'nocheck') % PDF 600 dpi
print_png(600,[get(gcf,'filename') '.png'],local,'',0,0,0) % PNG 600 dpi
end
PARTITIONVERLETLIST partition an existing Verlet list based on type (cross-terms)
Syntax: partV = partitionVerletList(V,typ)
See also
buildVerletList, updateVerletList, selfVerletList
function partV = partitionVerletList(V,typ)
% PARTITIONVERLETLIST partition an existing Verlet list based on type (cross-terms)
%
% Syntax: partV = partitionVerletList(V,typ)
%
%
% See also: buildVerletList, updateVerletList, selfVerletList
% MS 3.0 | 2023-04-01 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2023-08-29
% CHeck arguments
if nargin<2, error('two arguments are required: partV = partitionVerletList(V,typ)'), end
if istable(typ), typ = typ.type; end
n = length(typ); % number of particless
if ~iscell(V) || length(V)~=n
error('the supplied VerletList (%d atoms) is not compatible with X (%d atoms)',length(V),n)
end
% Main
partV = cell(n,1);
for i=1:n
partV{i} = V{i}(typ(V{i})~=typ(i));
end
PLOT3D wrapper of plot3(X(:,1),X(:,2),X(:,3),'property','value')
syntax: h=plot3D(X,property,value...)
NB: works also on cell array
function ho=plot3D(X,varargin)
%PLOT3D wrapper of plot3(X(:,1),X(:,2),X(:,3),'property','value')
% syntax: h=plot3D(X,property,value...)
% NB: works also on cell array
% MS 2.1 - 30/03/08 - INRA\Olivier Vitrac rev.
% default
% arg check
if nargin<1, error('h=plot3D(X,property,value...)'), end
options = varargin;
if iscell(X)
nX = length(X);
h = [];
if ~iscell(X{1}) % new molecule
pos=1; ntot = sum(cellfun('length',X));
for i=1:nX
h =[h;plot3D(X{i},'colrange',[pos ntot],options{:})];
pos = pos+length(X{i});
end
else
if isempty(options)
col = jet(nX);
for i=1:nX, h= [h;plot3D(X{i},'-o','color',col(i,:),'markeredgecolor',col(i,:),'markerfacecolor',col(i,:),'linewidth',2,'markersize',12)]; end
else
for i=1:nX, h= [h;plot3D(X{i},options{:})]; end
end
end
if nargout, ho = h; end
return
elseif size(X,3)>1
nX = size(X,3);
col = jet(nX); h = [];
for i=1:nX, h=[h;plot3D(X(:,:,i),'-','color',col(i,:))]; end
if nargout, ho = h; end
return
end
if size(X,2)~=3 && size(X,1)==3, X=X'; end
[m,n] = size(X); if n~=3, error('X must be a mx3 array'), end
% non standard options
ikw = find(cellfun('isclass',options,'char'));
if ismember('colrange',options(ikw))
iopt = ikw(find(ismember(options(ikw),'colrange'),1,'first'));
colrange = options{iopt+1};
options = options(setdiff(1:length(options),[0 1]+iopt));
else
colrange = [];
end
ikw = find(cellfun('isclass',options,'char'));
if ismember('autocol',options(ikw))
iopt = ikw(find(ismember(options(ikw),'autocol'),1,'first'));
options = options(setdiff(1:length(options),iopt));
autocol = true;
if isempty(colrange)
collist = jet(m);
colstart = 0;
else
collist = jet(colrange(2));
colstart = colrange(1);
end
else
autocol = false;
collist = [];
end
% plots
hold on
if autocol && m>1
h = zeros(m-1,1);
for i=1:m-1
h(i) = plot3(X(i:i+1,1),X(i:i+1,2),X(i:i+1,3),options{:});
set(h(i),'color',collist(colstart+i,:),'markerfacecolor',collist(colstart+i,:),'markeredgecolor',collist(colstart+i,:))
end
else
h = plot3(X(:,1),X(:,2),X(:,3),options{:});
end
if nargout, ho = h; end
Modified version of Quiver to plots velocity vectors as arrows
with components (u,v) at the points (x,y) using the current colormap
Bertrand Dano 3-3-03
Copyright 1984-2002 The MathWorks, Inc.
QUIVERC Quiver color plot.
QUIVERC(X,Y,U,V) plots velocity vectors as arrows with components (u,v)
at the points (x,y). The matrices X,Y,U,V must all be the same size
and contain corresponding position and velocity components (X and Y
can also be vectors to specify a uniform grid). QUIVER automatically
scales the arrows to fit within the grid.
QUIVERC(U,V) plots velocity vectors at equally spaced points in
the x-y plane.
QUIVERC(U,V,S) or QUIVER(X,Y,U,V,S) automatically scales the
arrows to fit within the grid and then stretches them by S. Use
S=0 to plot the arrows without the automatic scaling.
QUIVERC(...,LINESPEC) uses the plot linestyle specified for
the velocity vectors. Any marker in LINESPEC is drawn at the base
instead of an arrow on the tip. Use a marker of '.' to specify
no marker at all. See PLOT for other possibilities.
QUIVERC(...,'filled') fills any markers specified.
H = QUIVERC(...) returns a vector of line handles.
Example
[x,y] = meshgrid(-2:.2:2,-1:.15:1);
z = x .* exp(-x.^2 - y.^2); [px,py] = gradient(z,.2,.15);
contour(x,y,z), hold on
quiverc(x,y,px,py), hold off, axis image
Example
See also FEATHER, QUIVER3, PLOT.
Clay M. Thompson 3-3-94
Copyright 1984-2002 The MathWorks, Inc.
$Revision: 5.21 $ $Date: 2002/06/05 20:05:16 $
-------------------------------------------------------------
function hh = quiverc(varargin)
% Modified version of Quiver to plots velocity vectors as arrows
% with components (u,v) at the points (x,y) using the current colormap
% Bertrand Dano 3-3-03
% Copyright 1984-2002 The MathWorks, Inc.
%QUIVERC Quiver color plot.
% QUIVERC(X,Y,U,V) plots velocity vectors as arrows with components (u,v)
% at the points (x,y). The matrices X,Y,U,V must all be the same size
% and contain corresponding position and velocity components (X and Y
% can also be vectors to specify a uniform grid). QUIVER automatically
% scales the arrows to fit within the grid.
%
% QUIVERC(U,V) plots velocity vectors at equally spaced points in
% the x-y plane.
%
% QUIVERC(U,V,S) or QUIVER(X,Y,U,V,S) automatically scales the
% arrows to fit within the grid and then stretches them by S. Use
% S=0 to plot the arrows without the automatic scaling.
%
% QUIVERC(...,LINESPEC) uses the plot linestyle specified for
% the velocity vectors. Any marker in LINESPEC is drawn at the base
% instead of an arrow on the tip. Use a marker of '.' to specify
% no marker at all. See PLOT for other possibilities.
%
% QUIVERC(...,'filled') fills any markers specified.
%
% H = QUIVERC(...) returns a vector of line handles.
%
% Example:
% [x,y] = meshgrid(-2:.2:2,-1:.15:1);
% z = x .* exp(-x.^2 - y.^2); [px,py] = gradient(z,.2,.15);
% contour(x,y,z), hold on
% quiverc(x,y,px,py), hold off, axis image
%
% See also FEATHER, QUIVER3, PLOT.
% Clay M. Thompson 3-3-94
% Copyright 1984-2002 The MathWorks, Inc.
% $Revision: 5.21 $ $Date: 2002/06/05 20:05:16 $
%-------------------------------------------------------------
set(gca, 'color', 'blue');
% Arrow head parameters
alpha = 0.33; % Size of arrow head relative to the length of the vector
beta = 0.23; % Width of the base of the arrow head relative to the length
autoscale = 1; % Autoscale if ~= 0 then scale by this.
plotarrows = 1; % Plot arrows
sym = '';
filled = 0;
ls = '-';
ms = '';
col = '';
lw=1;
nin = nargin;
% Parse the string inputs
while isstr(varargin{nin}),
vv = varargin{nin};
if ~isempty(vv) & strcmp(lower(vv(1)),'f')
filled = 1;
nin = nin-1;
else
[l,c,m,msg] = colstyle(vv);
if ~isempty(msg),
error(sprintf('Unknown option "%s".',vv));
end
if ~isempty(l), ls = l; end
if ~isempty(c), col = c; end
if ~isempty(m), ms = m; plotarrows = 0; end
if isequal(m,'.'), ms = ''; end % Don't plot '.'
nin = nin-1;
end
end
error(nargchk(2,5,nin));
% Check numeric input arguments
if nin<4, % quiver(u,v) or quiver(u,v,s)
[msg,x,y,u,v] = xyzchk(varargin{1:2});
else
[msg,x,y,u,v] = xyzchk(varargin{1:4});
end
if ~isempty(msg), error(msg); end
if nin==3 | nin==5, % quiver(u,v,s) or quiver(x,y,u,v,s)
autoscale = varargin{nin};
end
% Scalar expand u,v
if prod(size(u))==1, u = u(ones(size(x))); end
if prod(size(v))==1, v = v(ones(size(u))); end
if autoscale,
% Base autoscale value on average spacing in the x and y
% directions. Estimate number of points in each direction as
% either the size of the input arrays or the effective square
% spacing if x and y are vectors.
if min(size(x))==1, n=sqrt(prod(size(x))); m=n; else [m,n]=size(x); end
delx = diff([min(x(:)) max(x(:))])/n;
dely = diff([min(y(:)) max(y(:))])/m;
len = sqrt((u.^2 + v.^2)/(delx.^2 + dely.^2));
autoscale = autoscale*0.9 / max(len(:));
u = u*autoscale; v = v*autoscale;
end
%----------------------------------------------
% Define colormap
vr=sqrt(u.^2+v.^2);
vrn=round(vr/max(vr(:))*64);
CC=colormap;
ax = newplot;
next = lower(get(ax,'NextPlot'));
hold_state = ishold;
%----------------------------------------------
% Make velocity vectors and plot them
x = x(:).';y = y(:).';
u = u(:).';v = v(:).';
vrn=vrn(:).';
uu = [x;x+u;repmat(NaN,size(u))];
vv = [y;y+v;repmat(NaN,size(u))];
vrn1= [vrn;repmat(NaN,size(u));repmat(NaN,size(u))];
uui=uu(:); vvi=vv(:); vrn1=vrn1(:); imax=size(uui);
hold on
for i= 1:3:imax-1
ii=int8(round(vrn1(i)));
if ii==0; ii=1; end
c1= CC(ii,1); c2= CC(ii,2); c3= CC(ii,3);
plot(uui(i:i+1),vvi(i:i+1),'linewidth',lw,'color',[c1 c2 c3]);
end
%----------------------------------------------
% Make arrow heads and plot them
if plotarrows,
hu = [x+u-alpha*(u+beta*(v+eps));x+u; ...
x+u-alpha*(u-beta*(v+eps));repmat(NaN,size(u))];
hv = [y+v-alpha*(v-beta*(u+eps));y+v; ...
y+v-alpha*(v+beta*(u+eps));repmat(NaN,size(v))];
vrn2= [vrn;vrn;vrn;vrn];
uui=hu(:); vvi=hv(:); vrn2=vrn2(:); imax=size(uui);
for i= 1:imax-1
ii=int8(round(vrn2(i)));
if ii==0; ii=1; end
c1= CC(ii,1); c2= CC(ii,2); c3= CC(ii,3);
plot(uui(i:i+1),vvi(i:i+1),'linewidth',lw,'color',[c1 c2 c3]);
end
else
h2 = [];
end
%----------------------------------------------
if ~isempty(ms), % Plot marker on base
hu = x; hv = y;
hold on
h3 = plot(hu(:),hv(:),[col ms]);
if filled, set(h3,'markerfacecolor',get(h1,'color')); end
else
h3 = [];
end
if ~hold_state, hold off, view(2); set(ax,'NextPlot',next); end
if nargout>0, hh = [h1;h2;h3]; end
% set(gca, 'color', [0 0 0],'Xcolor','w','Ycolor','w');
% set(gcf, 'color', [0 0 0]);
%set(gcf, 'InvertHardCopy', 'off');
ROOTDIR extract the root of path using [last,root]=lastdir(pathstr)
syntax: root = rootdir(pathstr)
function rpath = rootdir(pathstr)
%ROOTDIR extract the root of path using [last,root]=lastdir(pathstr)
% syntax: root = rootdir(pathstr)
% MS 2.1 - 09/02/08 - INRA\Olivier - rev.
[tmp,rpath] = lastdir(pathstr);
SELFVERLETLIST include self in the VerletList (required for density)
Syntax: selfV = selfVerletList(V,typ)
See also
buildVerletList, updateVerletList, partitionVerletList, interp3SPHVerlet
function selfV = selfVerletList(V)
% SELFVERLETLIST include self in the VerletList (required for density)
%
% Syntax: selfV = selfVerletList(V,typ)
%
%
% See also: buildVerletList, updateVerletList, partitionVerletList, interp3SPHVerlet
% MS 3.0 | 2023-04-02 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2023-05-17
% Revision history
% 2023-04-02 RC
% 2023-05-16 fix arg check
% 2023-05-17 updated help
% CHeck arguments
if nargin<1, error('one arguments is required: selfV = selfVerletList(V)'), end
if ~iscell(V)
error('the supplied VerletList (%d atoms) is invalid',length(V))
end
% Main
selfV = V;
for i=1:n
selfV{i}(1:end+1) = [i;V{i}(:)]; % instead of crude concatenation, keep column or row vector
end
SHAPESPH evaluate the solid shape matrix for each center according to Eq. 15 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
Syntax:
out = shapeSPH(centers,gradW [, V, config, silent])
Inputs:
centers : kxd coordinates of the kernel centers
gradW : derivative kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
config: structure with fields coding for Lamé parameters (not used directly by shapeSPH)
lambda (default = 30 000 Pa)
mu (default = 3000 Pa)
forcesilent: flag to force silence mode (default = false)
Output: out structure with fields
L : k x d^2 shape matrix, where reshape(L(i,:),[d d]) is the dxd matrix respectively to kernel i
Linv : k x d^2 pseudo-inverse of L so that Linv * L = I
correctedgradW : k x d corrected gradient for each kernel (used by defgradSPH)
k,d,V,config : inputs
engine : 'shapeSPH'
Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
See also
defgradSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
See also
See also
See also
2023-10-31, |, INRAE\Olivier, Vitrac, |, rev., 2023-11-01
function out = shapeSPH(centers,gradW,V,config,forcesilent)
% SHAPESPH evaluate the solid shape matrix for each center according to Eq. 15 of Comput. Methods Appl. Mech. Engrg. 286 (2015) 87–106
%
% Syntax:
% out = shapeSPH(centers,gradW [, V, config, silent])
%
% Inputs:
% centers : kxd coordinates of the kernel centers
% gradW : derivative kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
% config: structure with fields coding for Lamé parameters (not used directly by shapeSPH)
% lambda (default = 30 000 Pa)
% mu (default = 3000 Pa)
% forcesilent: flag to force silence mode (default = false)
%
% Output: out structure with fields
% L : k x d^2 shape matrix, where reshape(L(i,:),[d d]) is the dxd matrix respectively to kernel i
% Linv : k x d^2 pseudo-inverse of L so that Linv * L = I
% correctedgradW : k x d corrected gradient for each kernel (used by defgradSPH)
% k,d,V,config : inputs
% engine : 'shapeSPH'
%
% Refer to Ganzenmuller (2015) for details: https://doi.org/10.1016/j.cma.2014.12.005
% ~/han/biblio/Ganzenmuller2015-Hourglass_control_algorithm.pdf
%
%
% See also: defgradSPH, interp2SPH, interp3SPH, kernelSPH, packSPH
%
%
%
% 2023-10-31 | INRAE\Olivier Vitrac | rev. 2023-11-01
%{
% Example:
r = 0.5;
X0 = packSPH(5,r);
X0(sqrt(sum((X0-mean(X0,1)).^2,2))>4*r,:) = [];
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure, hold on
for i = 1:size(X0, 1), surf(xs*r + X0(i,1), ys*r + X0(i,2), zs*r + X0(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none'); end
lighting gouraud, camlight left, shading interp, axis equal, view(3)
% deformation (vertical compression + shearing)
Xc = mean(X0,1); Xmin = min(X0,[],1); Xmax = max(X0,[],1)
% compression along z, with support at zmin, compression rate = 20%
X = X0; X(:,3) = 0.8*(X(:,3)-Xmin(1,3)) + Xmin(1,3);
% shearing 20% along y
X(:,2) = X(:,2) + 0.2 * (Xmax(1,2)-Xmin(1,2)) * (X(:,3)-Xmin(1,3))/(Xmax(1,3)-Xmin(1,3));
% plot
figure, hold on
for i = 1:size(X, 1), surf(xs*r + X(i,1), ys*r + X(i,2), zs*r + X(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none'); end
lighting gouraud, camlight left, shading interp, axis equal, view(3)
% displacement
u = X-X0;
% shape matrix
gradW = kernelSPH(2*r,'lucyder',3);
shapeout = shapeSPH(X0,gradW)
defgradout = defgradSPH(u,shapeout)
% visualization
figure, hold on
scatter3(X(:,1),X(:,2),X(:,3),40,defgradout.G)
f= defgradout.f; fn=sqrt(sum(f.^2,2)); f = f./fn;
f90 = prctile(fn,90); fn(fn>f90) = f90; f = f .* f90;
quiver3(X(:,1),X(:,2),X(:,3),f(:,1),f(:,2),f(:,3))
%}
%Revision history
% 2023-10-31 alpha version
% 2023-11-01 collect all outputs and inputs into out, replace all reshapes by vec2tensor
% 2023-11-13 fixes, RC, example
% Default Lamé parameters
config_default = struct(...
'lambda',3e4, ...first Lamé parameter
'mu',3e3 ... shear modulus (second Lamé parameterà
);
%% arg check
if nargin<1, centers = []; end
if nargin<2, gradW = []; end
if nargin<3, V = []; end
if nargin<4, config = []; end
if nargin<5, forcesilent = []; end
[k,d] = size(centers);
kv = length(V);
if k==0, error('please supply some centers'), end
if d>3, error('3 dimensions maximum'), end
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
if isempty(forcesilent), forcesilent = false; end
if isempty(config), config = config_default; end
for f = fieldnames(config_default)'
if ~isfield(config,f{1}) || isempty(config.(f{1}))
config.(f{1}) = config_default.(f{1});
end
end
%% initialization
verbosity = (k>1e3) & ~forcesilent;
largek = k>200;
t0_ = clock; t1_=t0_; screen='';
% coding linearized outer product (https://en.wikipedia.org/wiki/Outer_product)
[left,right] = ndgrid(1:d,1:d);
outerproductindex = struct('u',left(:),'v',right(:));
% vec2tensor: indices to convert a vector to a 2D tensor (faster than many reshapes)
vec2tensor_reshape = reshape(1:d^2,d,d);
vec2tensor = @(x) x(vec2tensor_reshape);
%% Output correction/shape matrix: L
L = zeros(k,d*d,class(centers));
if verbosity, dispf('SHAPESPH calculate L correction matrix for all %d kernels (K) in %d dimensions...',k,d), end
% loop over all j for summation
for j=1:k
% verbosity
if verbosity
if largek
t_ = clock; %#ok<*CLOCK>
if mod(j,10)==0 || (etime(t_,t1_)>0.5) %#ok<*DETIM>
t1_=t_;
dt_ = etime(t_,t0_); done_ = j/k;
screen = dispb(screen,'[K%d:%d] SHAPESPH | elapsed %0.1f s | done %0.1f %% | remaining %0.1f s', ...
j,k,dt_,100*done_,(1/done_-1)*dt_);
end
else
dispf('... SHAPESPH respectively to kernel %d of %d',j,k);
end
end
% Correction shape matrix (accumulation over j, but computation for all i)
Xij = centers(j,:)-centers; % j - all i
Xij_d = sqrt(dot(Xij,Xij,2));
Xij_n = Xij ./ Xij_d;
gradWi = gradW(Xij_n).*Xij_n;
gradWi(j,:) = 0;
L = L + V(j) * Xij(:,outerproductindex.u).*gradWi(:,outerproductindex.v); % u and v are left and right indices with repetitions
end
%% output Linv (pseudo-inverse of L)
Linv = zeros(size(L),class(centers));
for i = 1:k
if verbosity, screen = dispb(screen,'[K%d:%d] SHAPESPH, pseudoinverse...',i,k); end
tmp = pinv(vec2tensor(L(i,:)));
Linv(i,:) = tmp(:)';
end
%% output correctedgradW (Eq. 14)
correctedgradW = zeros(d,k,k);
for j = 1:k
Xij = centers(j,:)-centers; % j - all i
Xij_d = sqrt(dot(Xij,Xij,2));
Xij_n = Xij ./ Xij_d;
gradWi = gradW(Xij_n).*Xij_n;
gradWi(j,:) = 0; % when i=j
for i=1:k
if verbosity, screen = dispb(screen,'[K%d:%d]-[K%d:%d] SHAPESPH, corrected gradient...',i,k,j,k); end
correctedgradW(:,i,j) = vec2tensor(Linv(i,:))*gradWi(i,:)';
end
end
%% verbosity
if verbosity
dispb(screen,'SHAPESPH %d L matrix calculated in %0.4g s',k,etime(clock,t0_));
end
% collect outputs
out = struct('k',k,'d',d,'V',V,'config',config,...
'L',L,'Linv',Linv,'correctedgradW',correctedgradW,...
'engine','shapeSPH','forcesilent',forcesilent);
UNWRAP Unwrap trajectorycoordinates in a periodic box
USAGE: trajunwrapped = trajunwrap(traj, box, PBC)
This function unwraps the coordinates of atoms in a periodic simulation
box after they have been wrapped due to periodic boundary conditions.
It is designed to handle large sets of atom coordinates efficiently
through vectorized operations. This function should be used when atoms
undergo displacements that might cause them to cross periodic boundaries,
ensuring that their continuous trajectory is accurately represented.
INPUTS:
traj: nx2 or nx3 array coding for the initial coordinates of the n particles
in 2D and 3D, respectively.
box: 2x2 or 3x3 array coding for the dimensions of the periodic simulation box.
The box spans along dimension i between box(i,1) and box(i,2).
It is assumed that all X values initially lie within these box limits.
PBC: 1x2 or 1x3 flags (PBC(j) is true if the jth dimension is periodic)
OUTPUTS:
Xunwrapped: nx2 or nx3 array coding for the unwrapped coordinates of the particles,
reflecting their continuous trajectory across periodic boundaries.
See also
PBCimagesshift, PBCgrid, PBCgridshift, PBCimages, PBCincell
See also
See also
See also
MS, 3.1, |, 2024-04-05, |, INRAE\olivier.vitrac@agroparistech.fr, |, rev.
See also
Revision, history:, 2024-04-05, -, Initial, version
function trajunwrapped = trajunwrap(traj, box)
%UNWRAP Unwrap trajectorycoordinates in a periodic box
%
% USAGE: trajunwrapped = trajunwrap(traj, box, PBC)
%
% This function unwraps the coordinates of atoms in a periodic simulation
% box after they have been wrapped due to periodic boundary conditions.
% It is designed to handle large sets of atom coordinates efficiently
% through vectorized operations. This function should be used when atoms
% undergo displacements that might cause them to cross periodic boundaries,
% ensuring that their continuous trajectory is accurately represented.
%
% INPUTS:
% traj: nx2 or nx3 array coding for the initial coordinates of the n particles
% in 2D and 3D, respectively.
% box: 2x2 or 3x3 array coding for the dimensions of the periodic simulation box.
% The box spans along dimension i between box(i,1) and box(i,2).
% It is assumed that all X values initially lie within these box limits.
% PBC: 1x2 or 1x3 flags (PBC(j) is true if the jth dimension is periodic)
%
% OUTPUTS:
% Xunwrapped: nx2 or nx3 array coding for the unwrapped coordinates of the particles,
% reflecting their continuous trajectory across periodic boundaries.
%
%
% See also: PBCimagesshift, PBCgrid, PBCgridshift, PBCimages, PBCincell
%
%
%
% MS 3.1 | 2024-04-05 | INRAE\olivier.vitrac@agroparistech.fr | rev.
%
% Revision history:
% 2024-04-05 - Initial version
% Calculate the box lengths in each dimension
boxLength = diff(box, 1, 2);
% Calculate displacement as the difference between wrapped and initial positions
displacement = [zeros(size(traj(1,:))) ; diff(traj,1,1) ];
% Identify atoms that crossed the boundary and adjust the displacement accordingly
for j = 1:size(displacement, 2)
positiveCross = displacement(:, j) > boxLength(j) / 2;
negativeCross = displacement(:, j) < -boxLength(j) / 2;
displacement(positiveCross, j) = displacement(positiveCross, j) - boxLength(j);
displacement(negativeCross, j) = displacement(negativeCross, j) + boxLength(j);
end
% Adjust the positions to get the unwrapped coordinates
trajunwrapped = traj(1,:) + cumsum(displacement,1);
end
UNWRAPBC Unwrap atom coordinates in a periodic box
USAGE: Xunwrapped = unwrapPBC(X, Pshift, box, PBC)
This function unwraps the coordinates of atoms in a periodic simulation
box after they have been wrapped due to periodic boundary conditions.
It is designed to handle large sets of atom coordinates efficiently
through vectorized operations. This function should be used when atoms
undergo displacements that might cause them to cross periodic boundaries,
ensuring that their continuous trajectory is accurately represented.
INPUTS:
X: nx2 or nx3 array coding for the initial coordinates of the n particles
in 2D and 3D, respectively.
Pshift: 1x2 or 1x3 array coding for the translation applied to the coordinates,
which is subject to periodic wrapping.
box: 2x2 or 3x3 array coding for the dimensions of the periodic simulation box.
The box spans along dimension i between box(i,1) and box(i,2).
It is assumed that all X values initially lie within these box limits.
PBC: 1x2 or 1x3 flags (PBC(j) is true if the jth dimension is periodic)
OUTPUTS:
Xunwrapped: nx2 or nx3 array coding for the unwrapped coordinates of the particles,
reflecting their continuous trajectory across periodic boundaries.
See also
PBCimagesshift, PBCgrid, PBCgridshift, PBCimages, PBCincell
See also
Example
% Define initial positions, a periodic shift, and box dimensions
X = [1.0, 1.5; 0.5, 2.0]; % Example in 2D
Pshift = [0.5, 0.0]; % Shift right by 0.5 units
box = [0, 3; 0, 3]; % Square box with sides of length 3
Xunwrapped = unwrapPBC(X, Pshift, box);
disp('Unwrapped Coordinates:');
disp(Xunwrapped);
Example
This function is part of a suite designed to facilitate simulations and
analyses using periodic boundary conditions. It relies on MATLAB's ability
to perform array operations efficiently and is optimized for handling
large numbers of particles.
Example
MS 3.1 | 2024-04-04 | INRAE\olivier.vitrac@agroparistech.fr | rev.
Example
Revision history:
2024-04-04 - Initial version
function Xunwrapped = unwrapPBC(X, Pshift, box, PBC)
%UNWRAPBC Unwrap atom coordinates in a periodic box
%
% USAGE: Xunwrapped = unwrapPBC(X, Pshift, box, PBC)
%
% This function unwraps the coordinates of atoms in a periodic simulation
% box after they have been wrapped due to periodic boundary conditions.
% It is designed to handle large sets of atom coordinates efficiently
% through vectorized operations. This function should be used when atoms
% undergo displacements that might cause them to cross periodic boundaries,
% ensuring that their continuous trajectory is accurately represented.
%
% INPUTS:
% X: nx2 or nx3 array coding for the initial coordinates of the n particles
% in 2D and 3D, respectively.
% Pshift: 1x2 or 1x3 array coding for the translation applied to the coordinates,
% which is subject to periodic wrapping.
% box: 2x2 or 3x3 array coding for the dimensions of the periodic simulation box.
% The box spans along dimension i between box(i,1) and box(i,2).
% It is assumed that all X values initially lie within these box limits.
% PBC: 1x2 or 1x3 flags (PBC(j) is true if the jth dimension is periodic)
%
% OUTPUTS:
% Xunwrapped: nx2 or nx3 array coding for the unwrapped coordinates of the particles,
% reflecting their continuous trajectory across periodic boundaries.
%
%
% See also: PBCimagesshift, PBCgrid, PBCgridshift, PBCimages, PBCincell
%
% Example:
% % Define initial positions, a periodic shift, and box dimensions
% X = [1.0, 1.5; 0.5, 2.0]; % Example in 2D
% Pshift = [0.5, 0.0]; % Shift right by 0.5 units
% box = [0, 3; 0, 3]; % Square box with sides of length 3
% Xunwrapped = unwrapPBC(X, Pshift, box);
% disp('Unwrapped Coordinates:');
% disp(Xunwrapped);
%
% This function is part of a suite designed to facilitate simulations and
% analyses using periodic boundary conditions. It relies on MATLAB's ability
% to perform array operations efficiently and is optimized for handling
% large numbers of particles.
%
% MS 3.1 | 2024-04-04 | INRAE\olivier.vitrac@agroparistech.fr | rev.
%
% Revision history:
% 2024-04-04 - Initial version
% Apply the periodic shift and wrapping
%[Xwrapped, ~] = PBCimagesshift(X, Pshift, box);
Xwrapped = PBCincell(X+Pshift, box, PBC);
% Calculate the box lengths in each dimension
boxLength = diff(box, 1, 2);
% Calculate displacement as the difference between wrapped and initial positions
displacement = Xwrapped - X;
% Identify atoms that crossed the boundary and adjust the displacement accordingly
for j = 1:size(X, 2)
positiveCross = displacement(:, j) > boxLength(j) / 2;
negativeCross = displacement(:, j) < -boxLength(j) / 2;
displacement(positiveCross, j) = displacement(positiveCross, j) - boxLength(j);
displacement(negativeCross, j) = displacement(negativeCross, j) + boxLength(j);
end
% Adjust the positions to get the unwrapped coordinates
Xunwrapped = X + displacement;
end
UPDATEVERLETLIST update the verlet list if the number of sampled modified neighbors exceed a threshold
Syntax: [verletList,configout] = updateVerletList(X, previousVerletList, config)
Inputs:
X: n x 3 updated X
previousVerletList: n x 1 cell array corresponding to the previous list
config: output of buildVerletList
config.tolerance set the tolerance
config.nsamples set the number of samples
Outputs:
verletList: n x 1 cell coding for the verletList
config: configuration structure to be used with updateVerletList()
See also
buildVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
function [verletList,configout] = updateVerletList(X, previousVerletList, config)
%UPDATEVERLETLIST update the verlet list if the number of sampled modified neighbors exceed a threshold
%
% Syntax: [verletList,configout] = updateVerletList(X, previousVerletList, config)
%
% Inputs:
% X: n x 3 updated X
% previousVerletList: n x 1 cell array corresponding to the previous list
% config: output of buildVerletList
% config.tolerance set the tolerance
% config.nsamples set the number of samples
%
% Outputs:
% verletList: n x 1 cell coding for the verletList
% config: configuration structure to be used with updateVerletList()
%
%
% See also: buildVerletList, partitionVerletList, selfVerletList, interp3SPHVerlet
% MS 3.0 | 2023-04-01 | INRAE\Olivier.vitrac@agroparistech.fr | rev. 2023-05-17
% Revision history
% 2023-05-17 updated help
%% Check arguments
if nargin<3, error('three arguments are reqiured: [verletList,configout] = updateVerletList(X, previousVerletList, config)'), end
if istable(X), X = table2array(X(:,{'x','y','z'})); end
[n,d] = size(X); % number of particless
typ = class(X); % class of coordinates
if ~iscell(previousVerletList) || length(previousVerletList)~=n
error('the supplied VerletList (%d atoms) is not compatible with X (% atoms)',length(previousVerletList),n)
end
if isstruct(config)
if isfield(config,'engine') && strcmp(config.engine,'buildVerletList')
if (config.natoms ~= n) || (config.dimensions~=d)
error('the number of atoms (%d) or/and dimensions (%d) have changed, expected %d x %d',...
n,d,config.natoms,config.dimensions)
end
else
error('unrecognized configuration structure')
end
end
nsamples = min(config.nsamples,n/10);
%% check the stability of the previousVerletList
cutoff2 = config.cutoff^2;
stable = true;
i = 0;
while (i < nsamples) && stable
i = i + 1;
isample = unidrnd(n);
Xsample = X(isample,:);
jneigh = find( sum((X-Xsample).^2,2) <= cutoff2 );
stable = length(intersect(jneigh,previousVerletList{isample})) > ...
((1-config.tolerance)*length(previousVerletList{isample}));
end
%% take decision
if stable
verletList = previousVerletList;
else
if config.verbose, dispf('The current VerletList is obsolete'), end
verletList = buildVerletList(X, config.cutoff, config.sorton, config.nblocks, config.verbose);
end
% output
if nargout>1, configout = config; end
WALLSTRESS first template to perform post-treatment of Billy' dump files (3D viscosimeter)
INRAE\Olivier Vitrac - rev. 2023-03-31
INRAE\William Jenkinson 2023-04-03
% WALLSTRESS first template to perform post-treatment of Billy' dump files (3D viscosimeter)
% INRAE\Olivier Vitrac - rev. 2023-03-31
% INRAE\William Jenkinson 2023-04-03
% Dependencies (not included in MS, at least not yet)
% lamdumpread2() version 2023-03-23 or later
% buildVerletList() version 2023-03-25 or later (recommended version 2023-03-31 to get the memory efficient block search)
% forceHertz() version 2023-03-26 or later
%
% note: be sure Olivier/INRA/Codes/MS is in your Path (MS=Molecular Studio)
% Revision history
% 2023-03-23 RC, early design based on dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv100_lite
% 2023-03-24 first interaction with Billy
% the file for design was shifted to dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv10_lite
% 2023-03-25 implementation of a full Verletlist, automatic identification of fluid-solid contacts without using any
% particular topology, the template is fully operational and used to plot the number of contacts with time
% 2023-03-26 first implementation of Hertz contacts (to be validated and extended)
% better figure management (previous results can be reloaded)
% 2023-03-30 minor fixes to adapt the new buildVerletList (block)
% 2023-03-31 increase cutoff from 1.5*dmin to 1.7 dmin to select all fluid and wall atoms correctly (it was finally not a bug)
%% read datafile
% path definitions (please add your machine name by typing localname in your command window)
switch localname
case 'LP-OLIVIER2022'
local = 'C:\Users\olivi\OneDrive - agroparistech.fr\Billy\ProductionSandbox_toOV_23-03-2023';
case 'LX-Willy2021'
local = '/Data/billy/Results/Viscosimeter_SMJ_V6/ProductionSandbox_toOV_23-03-2023';
case 'YOUR MACHINE'
local = 'it is the path where the dump file is located, results are stored at the sample place';
otherwise
error('add a case with your machine name, which is ''%s''',localname)
end
%datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv100_lite';
%datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv10_lite';
datafile = 'dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_Hertzdiv10_centrebulk';
fulldatafile = fullfile(local,datafile); % concatenate path (local) and data filename
X = lamdumpread2(fulldatafile); % be sure the last version of lamdumpread2() is in the same folder as this script
% result file (to store results)
resultfile = fullfile(local,['RESULT_' datafile '.mat']);
%% Region-based definitions: Wall and fluid regions
% note that the interface between regions is defined from pair-distances
types = struct('wall',3,'fluid',1);
X.ATOMS.iswall = X.ATOMS.type==types.wall; % add the column iswall to the table X.ATOMS
X.ATOMS.isfluid = X.ATOMS.type==types.fluid;% add the column isfluid to the table X.ATOMS
X.ATOMS.isundef = ~X.ATOMS.iswall & ~X.ATOMS.isfluid; % add the column isundef to the table X.ATOMS
%% Control frame
% note that the data are stored in data.ATOMS, which is a table with named colums allowing hybrid indexing
nsteps = length(X.TIMESTEP);
icurrenttime = ceil(0.5*nsteps); % index of the control frame (used to set basic definitions before more advanced interpretation)
currenttime = X.TIMESTEP(icurrenttime);
rawframe = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','type','x','y','z','vx','vy','vz','iswall','isfluid','isundef'});
frame = table2array(rawframe(:,{'x','y','z'})); % generate an array of coordinates
framev = table2array(rawframe(:,{'vx','vy','vz'})); % generate an array with the velocities
% Build the main Verlet list (for all particles)
% V use the natural indexing instead of rawframe.id
cutoff = 60e-6; % empty or NaN value will force an automatic estimation of cutoff
[V,cutoff,dmin,configVerletList] = buildVerletList(frame,cutoff); % cutoff can be omitted
% Build the secondary Verlet lists (highly vectorized code)
% V1 the neighbors of type 2 (fluid) for type 1 atoms (1..n1)
% V2 the neighbors of type 1 (wall) for type 2 atoms (1..n2)
idx1 = find(rawframe.type==3); n1 = length(idx1); % indices of the wall particles in the current frame
idx2 = find(rawframe.type==1); n2 = length(idx2); % indices of the fluid particles in the current frame
V1 = cellfun(@(v) v(rawframe.type(v)==1),V(idx1),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
V2 = cellfun(@(v) v(rawframe.type(v)==3),V(idx2),'UniformOutput',false); % Verlet list for type 1 in contact with type 2
% corresponding distances d1,d2
% some distances can be empty for d2
d1 = arrayfun(@(i) pdist2(frame(idx1(i),:),frame(V1{i},:)),(1:n1)','UniformOutput',false); % evaluate the distance for each idx1(i)
d2 = arrayfun(@(i) pdist2(frame(idx2(i),:),frame(V2{i},:)),(1:n2)','UniformOutput',false); % evaluate the distance for each idx2(i)
d1min = cellfun(@min,d1,'UniformOutput',false); % minimum distance for each idx1()
[d1min{cellfun(@isempty,d1)}] = deal(NaN);
d1min = cat(1,d1min{:});
d2min = cellfun(@min,d2,'UniformOutput',false); % minimum distance for each idx2()
[d2min{cellfun(@isempty,d2)}] = deal(NaN); % populate empty distances with NaN
d2min = cat(1,d2min{:}); % we collect all now all distances (since the fluid is moving with respect with the wall)
% Identify beads of type 1 (wall) directly in contact with fluids
% this method is general and relies only on pair distances
% the term "contact" is general (vincinity) not a "real" contact as set later
iswallcontact = d1min < 1.7 * dmin; % condition for a wall particle to be considered possibly in contact with the fluid
isfluidcontact = ~isnan(d2min); % the condition is less restrictive for the fluid, based on the cutoff distance
V1contact = V1(iswallcontact); % "contact" Verlet list corresponding to V1
V2contact = V2(isfluidcontact); % "contact" Verlet list corresponding to V2
iwallcontact = idx1(iswallcontact);
ifluidcontact = unique(cat(2,V1contact{:})); % within cutoff
% control plot
% one color is assigned to each phase
% symbols are filled if they are included in the contact Verlet list
% type figure, rgb() to list all available colors
colors = struct('wall',rgb('Crimson'),'fluid',rgb('DeepSkyBlue'),'none','None');
figure, hold on
plot3D(frame(rawframe.isundef,:),'ko');
plot3D(frame(rawframe.iswall,:),'o','MarkerEdgeColor',colors.wall,'MarkerFaceColor',colors.none);
plot3D(frame(iwallcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.wall);
plot3D(frame(rawframe.isfluid,:),'o','MarkerEdgeColor',colors.fluid,'MarkerFaceColor',colors.none);
plot3D(frame(ifluidcontact,:),'o','MarkerEdgeColor',colors.none,'MarkerFaceColor',colors.fluid);
axis equal, axis tight, view(3)
%% [1] INTERPRETATION of Lanshoff foces on the reference frame
R = 0.5*0.001/48; % radius of the particle (please, be very accurate)
h = 2*R;
Vlandhsoff = buildVerletList(frame,1.2*h);
configLandshoff = struct( ...
'gradkernel', kernelSPH(cutoff,'lucyder',3),...
'h', h,...
'c0', 10,...
'q1', 1,...
'rho', 1000 ...
);
[FLandshoff,nLandshoff] = forceLandshoff(frame,framev,Vlandhsoff,configLandshoff);
FLandshoff_vec = FLandshoff .* nLandshoff;
figure, hold on
% quiver plot for the fluid
hqfluid = quiver3( ...
frame(rawframe.isfluid,1), ...
frame(rawframe.isfluid,2), ...
frame(rawframe.isfluid,3), ...
FLandshoff_vec(rawframe.isfluid,1), ...
FLandshoff_vec(rawframe.isfluid,2), ...
FLandshoff_vec(rawframe.isfluid,3), ...
5 ...
); set(hqfluid,'color',colors.fluid)
% quiver plot for the wall
hqwall = quiver3( ...
frame(rawframe.iswall,1), ...
frame(rawframe.iswall,2), ...
frame(rawframe.iswall,3), ...
FLandshoff_vec(rawframe.iswall,1), ...
FLandshoff_vec(rawframe.iswall,2), ...
FLandshoff_vec(rawframe.iswall,3), ...
5 ...
); set(hqwall,'color',colors.wall)
axis tight, axis equal, view(3)
[
mean(FLandshoff_vec(rawframe.isfluid,:),1)
mean(FLandshoff_vec(rawframe.iswall,:),1)
]
%% [2] similar analysis of Landshoff with all frames
% incorporate easy syntaxes developed on April 1, 2023 and later
% the simulation set is too small to conclude anything (not enough layers)
[Vlandhsoff,~,~,configVerletLandshoff] = buildVerletList(frame,1.2*h,false,[],false);
FLandshoff = zeros(nsteps,3);
[t_,t__] = deal(clock); %#ok<*CLOCK>
screen = '';
for icurrenttime = 1:nsteps
currenttime = X.TIMESTEP(icurrenttime);
% --- some display, to encourage the user to be patient
if mod(icurrenttime,5)
if etime(clock,t__)>2 %#ok<*DETIM>
t__ = clock; dt = etime(t__,t_); done = 100*(icurrenttime-1)/nsteps;
screen = dispb(screen,'[%d/%d] Landshoff interpretation [ done %0.3g %% | elapsed %0.4g s | remaining %0.4g s ] ...', ...
icurrenttime,nsteps,done,dt,dt*(100/done-1));
end
end % --- end of display
R0 = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','x','y','z','vx','vy','vz','isfluid'}); % raw data for the current frame
[Vlandhsoff,configVerletLandshoff] = updateVerletList(R0,Vlandhsoff,configVerletLandshoff);
tmpF = forceLandshoff(R0,[],Vlandhsoff,configLandshoff,false);
FLandshoff(icurrenttime,:) = sum(tmpF(R0.isfluid,:),1);
end
%% [3] Analysis of Hertz contacts with similarly as Landshoff forces (procedure implemented on 2023-04-02)
[VHertz,~,~,configVerletHertz] = buildVerletList(frame,1.2*h,false,[],false);
R = 0.5*0.001/48; % radius of the particle (please, be very accurate)
% config setup
E = 2e5 ; % ref:2e6, Hertzdiv10:2e5, Hertzdiv100:2e4
Hertzconfig = struct('name',{'wall','fluid'},'R',R,'E',E); % entries are duplicated if not mentioned
FHertz = zeros(nsteps,3);
[t_,t__] = deal(clock); %#ok<*CLOCK>
screen = '';
for icurrenttime = 1:nsteps
currenttime = X.TIMESTEP(icurrenttime);
R0 = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','x','y','z','type','isfluid','iswall'}); % raw data for the current frame
% --- some display, to encourage the user to be patient
if mod(icurrenttime,5)
if etime(clock,t__)>2 %#ok<*DETIM>
t__ = clock; dt = etime(t__,t_); done = 100*(icurrenttime-1)/nsteps;
screen = dispb(screen,'[%d/%d] Hertz contact interpretation [ done %0.3g %% | elapsed %0.4g s | remaining %0.4g s ] ...', ...
icurrenttime,nsteps,done,dt,dt*(100/done-1));
end
end % --- end of display
[VHertz,configVerletHertz] = updateVerletList(R0,VHertz,configVerletHertz);
partVHertz = partitionVerletList(VHertz,R0); % partition the Verletlist
tmpF = forceHertz(R0,VHertz,Hertzconfig,false);
FHertz(icurrenttime,:) = sum(tmpF(R0.isfluid,:),1); % fluid only
end
%% [4] Analysis of the number of contacts and Hertz forces (first analysis without using the new tools)
% count the number of fluid beads in contact with wall (within 2*R)
R = 0.5*0.001/48; % radius of the particle (please, be very accurate)
dbond = 2*R; % bond = link between 2 atoms
wall_id = rawframe.id(iwallcontact); % extract the ids matching the contact condition in the reference frame
fluid_id = rawframe.id(ifluidcontact); % idem for the fluid particles within the contact Verlet list
% config setup
E = 2e5 ; % ref:2e6, Hertzdiv10:2e5, Hertzdiv100:2e4
Hertzconfig = struct('name',{'wall','fluid'},'R',R,'E',E); % entries are duplicated if not mentioned
% prepare to calculate pair distances between different beads
pairdist = @(X,Y) triu(pdist2(X,Y),0); % note that the diagonal is included here (since X and Y are different)
ncontacts = zeros(nsteps,1);
FHertz = zeros(nsteps,1);
[t_,t__] = deal(clock); %#ok<*CLOCK>
screen = '';
for icurrenttime = 1:nsteps
currenttime = X.TIMESTEP(icurrenttime);
% --- some display, to encourage the user to be patient
if mod(icurrenttime,5)
if etime(clock,t__)>2 %#ok<*DETIM>
t__ = clock; dt = etime(t__,t_); done = 100*(icurrenttime-1)/nsteps;
screen = dispb(screen,'[%d/%d] interpretation [ done %0.3g %% | elapsed %0.3g s | remaining %0.3g s ] ...', ...
icurrenttime,nsteps,done,dt,dt*(100/done-1));
end
end % --- end of display
R0 = X.ATOMS(X.ATOMS.TIMESTEP==currenttime,{'id','x','y','z'}); % raw data for the current frame
[~,idx_wallcontact] = intersect(R0.id,wall_id); % index of wall particles (in contact) in the current frame
[~,idx_fluidcontact] = intersect(R0.id,fluid_id); % index of fluid particles in the contact Verle tlist
Xw = table2array(R0(idx_wallcontact,{'x','y','z'})); % coordinates of wall particles
Xf = table2array(R0(idx_fluidcontact,{'x','y','z'})); % coordinates of fluid particles
% count contacts
dxf = pairdist(Xw,Xf); % calculate all pair distances between the two type of particles (with the builtin pdist2())
dcontacts = find(dxf>0 & dxf% contacts defined by a positive distance greater than dbond
ncontacts(icurrenttime) = length(dcontacts); % store the number of contacts
% Hertz contact forces (note that the number contact could be also extracted from non-zero forces)
Ftmp = forceHertzAB(Xw,Xf,Hertzconfig,false); % calculate all contact forces, false prevents display
FHertz(icurrenttime) = sum(Ftmp(:,1)); % sum forces along x
end
% save the data to enable a refresh of the figure without restarting this block
timesteps = X.TIMESTEP;
save(resultfile,'datafile','FHertz','ncontacts','timesteps','Hertzconfig')
dispf('Results saved (%s):',datafile), fileinfo(resultfile)
%% PLots and figure management
% reload the data
if exist(resultfile,'file'), load(resultfile), end
% plot number of contacts vs. time
contactfigure = figure;
formatfig(contactfigure,'figname',['NumberContact' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,ncontacts,'linewidth',0.5,'Color',rgb('Crimson'))
formatax(gca,'fontsize',14)
xlabel('time (units)','fontsize',16)
ylabel('number of contacts','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% plot number of Hertz projection along x vs. time
hertzfigure = figure;
formatfig(hertzfigure,'figname',['HertzContact' datafile],'PaperPosition',[1.5000 9.2937 18.0000 11.1125])
plot(timesteps,FHertz,'linewidth',0.5,'Color',rgb('Teal'))
mu = 0.01; L = 0.001; A = 2*L^2; U = 0.001 ;
yline = -mu*A*U/L; % Replace with the y-value of your horizontal line
line(get(gca,'xlim'), [yline yline], 'Color', 'r', 'LineStyle', '--')
text(20000, yline*1.1, 'system-wise viscous force', 'HorizontalAlignment', 'center')
formatax(gca,'fontsize',14)
xlabel('time (units)','fontsize',16)
ylabel('Hertz forces along x (N)','fontsize',16)
wtitle = textwrap({'\bfdump file:\rm';datafile},40);
title(regexprep(wtitle,'_','\\_'),'fontsize',10)
% save images in all valid formats (including Matlab one, the data can be extracted with this format)
% filenames are identical to the dump file with the proper extension: fig, pdf, png
for myfig = [contactfigure,hertzfigure] % loop over all figures to print
figure(myfig)
saveas(gcf,fullfile(local,[get(gcf,'filename') '.fig']),'fig') % fig can be open without restarting the code
print_pdf(600,[get(gcf,'filename') '.pdf'],local,'nocheck') % PDF 600 dpi
print_png(600,[get(gcf,'filename') '.png'],local,'',0,0,0) % PNG 600 dpi
end
YAO_INITIALIZATION load data corresponding to tframe
Typical usages
Xframe = yao_initialization(tframe)
[Xframe,details] = yao_initialization(tframe)
function [Xframe,details] = yao_initialization(tframe)
%YAO_INITIALIZATION load data corresponding to tframe
% Typical usages
% Xframe = yao_initialization(tframe)
% [Xframe,details] = yao_initialization(tframe)
% INRAE\Olivier Vitrac, Yao Liu
% 2024-05-03 first version
if nargin<1, tframe = []; end
if isempty(tframe), error('set tframe first'), end
%% Definitions
t0_ = clock;
%% check folders
outputfolder = fullfile(pwd,'preproduction');
savefolder = fullfile(pwd,'results');
prefetchfolder = fullfile(pwd,'prefetch');
if ~exist(outputfolder,'dir'), mkdir(outputfolder); end
if ~exist(prefetchfolder,'dir'), mkdir(prefetchfolder); end
if ~exist(savefolder,'dir'), mkdir(savefolder); end
% Anonymous functions
prefetchvar = @(varargin) fullfile(prefetchfolder,sprintf('t%0.4f_%s.mat',tframe,varargin{1}));
isprefetch = @(varargin) exist(prefetchvar(varargin{1}),'file') && ~RESETPREFETCH;
dispsection = @(s) dispf('\n%s\ntframe=%0.4g s \t[ %s ] elapsed time: %4g s\n%s',repmat('*',1,120),tframe,regexprep(upper(s),'.','$0 '),etime(clock,t0_),repmat('*',1,120)); %#ok
fighandle = @(id) formatfig(figure,'figname',sprintf('t%0.3g_%s',tframe,id));
printhandle = @(hfig) print_png(300,fullfile(outputfolder,[get(hfig,'filename') '.png']),'','',0,0,0);
%% path and metadata
dispsection('INITIALIZATION')
originalroot = '/media/olivi/T7 Shield/Thomazo_V2';
if exist(originalroot,'dir')
root = originalroot;
rootlocal = fullfile(pwd,'smalldumps');
copymode = true;
else
root = fullfile(pwd,'smalldumps');
copymode = false;
end
simfolder = ...
struct(...
'A1',struct('artificial',...
'Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz',...
'Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle.tar.gz' ...
),...
'A2',struct('artificial',...
'./Production/numericalViscosimeter_reference_ulsphBulk_hertzBoundary/dump.ulsphBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_2.tar.gz' ...
),...
'B1',struct('Morris',...
'./Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft.tar.gz' ...
),...
'B2',struct('Morris',...
'Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_1.tar.gz' ...
),...
'B3',struct('Morris',...
'/Production/numericalViscosimeter_reference_morrisBulk_hertzBoundary/dump.morrisBulk_hertzBoundary_referenceParameterExponent+1_with1SuspendedParticle_soft_2_3.tar.gz' ...
) ...
);
% selection (not change it if you do not have the full dataset/hard disk attached to your system)
config = 'A1';
viscosity = 'Morris';
sourcefolder= fullfile(root,rootdir(simfolder.(config).(viscosity)));
sourcefile = regexprep(lastdir(simfolder.(config).(viscosity)),'.tar.gz$','');
dumpfile = fullfile(sourcefolder,sourcefile);
dispf('config: %s | viscosity: %s | source: %s',config,viscosity,dumpfile)
%% extract information
dispsection('OVERVIEW')
X0 = lamdumpread2(dumpfile); % first frame
natoms = X0.NUMBER;
timesteps = X0.TIMESTEPS;
X1 = lamdumpread2(dumpfile,'usesplit',[],timesteps(2));
dt = (X1.TIME-X0.TIME)/(timesteps(2)-timesteps(1)); % integration time step
times = double(timesteps * dt); % in seconds
atomtypes = unique(X0.ATOMS.type);
ntimesteps = length(timesteps);
T = X0.ATOMS.type;
natomspertype = arrayfun(@(t) length(find(T==t)),atomtypes);
[~,ind] = sort(natomspertype,'descend');
% Thomazo simulation details
fluidtype = ind(1);
pillartype = ind(2);
walltype = ind(3);
spheretype = ind(4);
% coordinate system
coords = {'z','x','y'}; % to match Thomazo's movies
vcoords = cellfun(@(c) sprintf('v%s',c),coords,'UniformOutput',false);
icoords = cellfun(@(c) find(ismember({'x','y','z'},c)),coords); %<- use this index for BOX
% Simulation parameters
% Billy choose a reference density of 900 kg/m3 for a physical density of 1000 kg/m3
% Viscosity: 0.13 Pa.s
mbead = 4.38e-12; % kg
rho = 1000; % kg / m3 (density of the fluid)
Vbead = mbead/rho;
dispf('SUMMARY: natoms: %d | dt: %0.3g s | rho: %0.4g',natoms,dt,rho)
%% load the frame closest to simulation time: tframe
% with the mini dataset, are available:
% 0.30s 0.40s 0.45s 0.50s 0.55s 0.60s 0.65s 0.70s 0.75s 0.80s 0.85s 0.90s 0.95s 1.00s 1.05s 1.10s
% tframelist = [0.3 0.4 0.45:0.05:1.10]; % verysmalldumps
tframelist = 0.0:0.01:1.2; % updated time frames
if ~exist('tframe','var')
tframe = 0.85; %0.55; % s <-------------------- select time here
else
tframe = tframelist(nearestpoint(tframe,tframelist)); % restrict to existing tframes
end
iframe = nearestpoint(tframe,times); % closest index
Xframe = lamdumpread2(dumpfile,'usesplit',[],timesteps(iframe));
%% Implement isfluid, issphere, iswall, ispillar
Xframe.ATOMS.isfluid = Xframe.ATOMS.type==fluidtype;
Xframe.ATOMS.ispillar = Xframe.ATOMS.type==pillartype;
Xframe.ATOMS.issphere = Xframe.ATOMS.type==spheretype;
Xframe.ATOMS.iswall = Xframe.ATOMS.type==walltype;
Xframe.ATOMS.issolid = Xframe.ATOMS.type==spheretype | Xframe.ATOMS.type==pillartype;
%% second output
if nargout>1
details = struct(...
'tframe',tframe,...
'iframe',iframe,...
'dt',dt,...
'ntimesteps',ntimesteps,...
'coords',{coords},...
'vcoords',{vcoords},...
'type',struct('fluid',fluidtype,'pillar',pillartype,'wall',walltype,'sphere',spheretype),...
'mbead',mbead,...
'Vbead',Vbead,...
'box',Xframe.BOX(icoords,:),...
'boxsize',diff(Xframe.BOX(icoords,:),1,2),...
'dumpfile',dumpfile ...
);
end
error = imgd/max(imgd,[],'all') - sphereProfile/max(sphereProfile,[],'all');
error = sum(error(:).^2);
% Script to manage results
% 20240517Yao
% INRAE\Olivier Vitrac
% Comments
% Series 1 difficult to inteerpret but principles are there between frames 1à-14
% Series 2 nothing changed
% Series 3
%% Definitions
switch localname
case 'WS-OLIVIER2023'
root = 'C:\Users\olivi\Seafile\Han\Experiments\Yao';
otherwise
root = pwd;
end
result_folder = '20240517Yao';
local = fullfile(root,result_folder);
if ~exist(local,'dir'), error('the folder ''%s'' does not exist'), end
%% load data
if ~exist('raw','var')
f = explore('*.tif',local,[],'abbreviate');
nf = length(f);
raw = repmat(struct('im',[],'finfo',[],'iminfo',[]),nf,1);
screen = '';
for i = 1:nf
raw(i).iminfo = imfinfo(fullfile(f(i).path,f(i).file));
raw(i).finfo = f(i);
npages = length(raw(i).iminfo);
raw(i).im = zeros(raw(i).iminfo(1).Height,raw(i).iminfo(1).Width,raw(i).iminfo(1).BitDepth/8,npages,'uint8');
for j = 1:npages
screen = dispb(screen,'[im: %d/%d] load frame %d of %d',i,nf,j,npages);
raw(i).im(:,:,:,j) = imread(fullfile(f(i).path,f(i).file),j);
end
end
end
%% Series 1
% control
i = 1;
selection = 10:14;
figure, montage(raw(i).im(:,:,:,selection))
title(sprintf('Series %d | frame %d-->%d',i,selection(1),selection(end)))
close all
for j=selection
figure, imagesc(double(raw(i).im(:,:,2,j))), colorbar
title(sprintf('Series %d | frame %d',i,j))
end
% select the image
% Summary of results
% frame 10: r=49.94 r=52.55 (metric = 0.63)
% frame 11: r=61.78 r=33.85 (metric = 0.33)
% frame 12: r=59.02 r=54.27 (metric = 0.63)
% frame 13: r=59.02 r=54.27 (metric = 0.40)
% frame 14: r=68.6 r=59.27 (metric = 0.86)
nselection = length(selection);
nr = 301; cutoff = 2;
R = repmat(struct('Icrop',[],'xmin',NaN,'xmax',NaN,'ymin',NaN,'ymax',NaN,...
'r0',NaN,'r1',NaN,'metric',NaN,'r',[],'intensity',zeros(nr-1,1)),nselection,1);
for iselection = 1:nselection
I = raw(i).im(:,:,2,selection(iselection)); % to show it: figure, imshow(imadjust(rescale(I)))
% find the sphere (assuming only one)
filtwidth = 2; % default filter width
found = false; niter = 0;
while ~found && niter<5
niter = niter +1; % next iteration
filtwidth = 2 * filtwidth; % double filter width
If_ = imgaussfilt(I, filtwidth); % Apply Gaussian filter
[centers,radii,metric] = imfindcircles(If_,round([0.3 2]*sqrt(numel(I))/6));
found = ~isempty(centers);
end
[~,ibest] = max(metric);
dispf('frame %d: %d particles have been found',selection(iselection),length(radii));
dispf('\t best candidate has a radius of %0.4g pixels (metric=%0.4g)',radii(ibest),metric(ibest));
If2 = imgaussfilt(I, 2); % Apply Gaussian filter
[Gmag, Gdir] = imgradient(If2); % Calculate gradient, figure, imagesc(Gmag)
objectiveFunction = @(params) sphereObjective(params, Gmag); % Define objective function for optimization
initialGuess = [centers(ibest,:), radii(ibest)]; % Initial guess for [center_x, center_y, radius]
OptOptions = optimoptions(@lsqnonlin, 'Display', 'iter','MaxFunctionEvaluations',1000); % Optimization options
paramsOptimized = lsqnonlin(objectiveFunction, initialGuess, [], [], OptOptions); % Perform optimization
% paramsOptimized = fminsearch(objectiveFunction, initialGuess); % Perform optimization
% Extract optimized center and radius
center_x = paramsOptimized(1);
center_y = paramsOptimized(2);
radius = paramsOptimized(3);
dispf('\t optimized search found %0.4g pixels',radius);
% Display results
figure, hold on, imshow(imadjust(rescale(I)));
viscircles([center_x, center_y], radius, 'EdgeColor', 'r');
title(sprintf('Series %d, Frame %d of %d (%d): Detected Sphere',i, iselection, nselection, selection(iselection)));
drawnow
% store results
R(iselection).r0 = radii(ibest);
R(iselection).r1 = radius;
R(iselection).metric = metric;
r01 = max(R(iselection).r0,R(iselection).r1);
R(iselection).xmin = max(1,floor(center_x-cutoff*r01));
R(iselection).ymin = max(1,floor(center_y-cutoff*r01));
R(iselection).xmax = min(size(I,2),ceil(center_x+cutoff*r01));
R(iselection).ymax = min(size(I,1),ceil(center_y+cutoff*r01));
R(iselection).Icrop = double(I(R(iselection).ymin:R(iselection).ymax,R(iselection).xmin:R(iselection).xmax));
R(iselection).r = linspace(0,cutoff*r01,nr)';
[X_,Y_] = meshgrid(R(iselection).xmin:R(iselection).xmax,R(iselection).ymin:R(iselection).ymax);
R_ = sqrt((X_-center_x).^2 + (Y_-center_y).^2);
for ir = 1:nr-1
if ir=R(iselection).r(ir)) & (R_else
ok = (R_>=R(iselection).r(ir)) & (R_<=R(iselection).r(ir+1));
end
R(iselection).intensity(ir) = mean(R(iselection).Icrop(ok),'all');
end
R(iselection).intensity(isnan(R(iselection).intensity))=0;
end
figure, hold on
for i=1:length(R)
stairs(R(i).r(1:end-1)+diff(R(i).r)/2,R(i).intensity)
end
%% Series 3
i = 3;
selection = 56:68;
close all
figure, montage(raw(i).im(:,:,:,selection))
title(sprintf('Series %d | frame %d-->%d',i,selection(1),selection(end)))
for j=selection
I = raw(i).im(:,:,2,j);
If = imgaussfilt(I,5);
figure, imagesc(double(If)), colorbar
title(sprintf('Series %d | frame %d',i,j))
end
nselection = length(selection);
nr = 301; cutoff = 2;
R = repmat(struct('Icrop',[],'xmin',NaN,'xmax',NaN,'ymin',NaN,'ymax',NaN,...
'r0',NaN,'r1',NaN,'metric',NaN,'r',[],'intensity',zeros(nr-1,1)),nselection,1);
for iselection = 1:nselection
I = raw(i).im(:,:,2,selection(iselection)); % to show it: figure, imshow(imadjust(rescale(I)))
% find the sphere (assuming only one)
filtwidth = 2; % default filter width
found = false; niter = 0;
while ~found && niter<5
niter = niter +1; % next iteration
filtwidth = 2 * filtwidth; % double filter width
If_ = imgaussfilt(I, filtwidth); % Apply Gaussian filter
[centers,radii,metric] = imfindcircles(If_,round([0.3 2]*sqrt(numel(I))/6));
found = ~isempty(centers);
end
[~,ibest] = max(metric);
dispf('frame %d: %d particles have been found',selection(iselection),length(radii));
dispf('\t best candidate has a radius of %0.4g pixels (metric=%0.4g)',radii(ibest),metric(ibest));
If2 = imgaussfilt(I, 2); % Apply Gaussian filter
[Gmag, Gdir] = imgradient(If2); % Calculate gradient, figure, imagesc(Gmag)
objectiveFunction = @(params) sphereObjective(params, Gmag); % Define objective function for optimization
initialGuess = [centers(ibest,:), radii(ibest)]; % Initial guess for [center_x, center_y, radius]
OptOptions = optimoptions(@lsqnonlin, 'Display', 'iter','MaxFunctionEvaluations',1000); % Optimization options
paramsOptimized = lsqnonlin(objectiveFunction, initialGuess, [], [], OptOptions); % Perform optimization
% paramsOptimized = fminsearch(objectiveFunction, initialGuess); % Perform optimization
% Extract optimized center and radius
center_x = paramsOptimized(1);
center_y = paramsOptimized(2);
radius = paramsOptimized(3);
dispf('\t optimized search found %0.4g pixels',radius);
% Display results
figure, hold on, imshow(imadjust(rescale(I)));
viscircles([center_x, center_y], radius, 'EdgeColor', 'r');
title(sprintf('Series %d, Frame %d of %d (%d): Detected Sphere',i, iselection, nselection, selection(iselection)));
drawnow
% store results
R(iselection).r0 = radii(ibest);
R(iselection).r1 = radius;
R(iselection).metric = metric;
r01 = max(R(iselection).r0,R(iselection).r1);
R(iselection).xmin = max(1,floor(center_x-cutoff*r01));
R(iselection).ymin = max(1,floor(center_y-cutoff*r01));
R(iselection).xmax = min(size(I,2),ceil(center_x+cutoff*r01));
R(iselection).ymax = min(size(I,1),ceil(center_y+cutoff*r01));
R(iselection).Icrop = double(I(R(iselection).ymin:R(iselection).ymax,R(iselection).xmin:R(iselection).xmax));
R(iselection).r = linspace(0,cutoff*r01,nr)';
[X_,Y_] = meshgrid(R(iselection).xmin:R(iselection).xmax,R(iselection).ymin:R(iselection).ymax);
R_ = sqrt((X_-center_x).^2 + (Y_-center_y).^2);
for ir = 1:nr-1
if ir=R(iselection).r(ir)) & (R_else
ok = (R_>=R(iselection).r(ir)) & (R_<=R(iselection).r(ir+1));
end
R(iselection).intensity(ir) = mean(R(iselection).Icrop(ok),'all');
end
R(iselection).intensity(isnan(R(iselection).intensity))=0;
end
figure, hold on
for i=1:length(R)
stairs(R(i).r(1:end-1)+diff(R(i).r)/2,R(i).intensity)
end
%% =================================================================
% the last part of the code is to define functions in a script
%
% =================================================================
function criterion = sphereObjective(params, img)
sgrad = 8;
center_x = params(1);
center_y = params(2);
radius = params(3);
[X, Y] = meshgrid(1:size(img,2), 1:size(img,1));
distFromCenter = sqrt((X - center_x).^2 + (Y - center_y).^2);
sphereProfile = exp(-((distFromCenter - radius).^2)/(2*(radius/sgrad)^2)); % Example halo profile
xmin = max(1,floor(center_x - radius - 3 * sgrad));
xmax = min(size(img,1),ceil(center_x + radius + 3 * sgrad));
ymin = max(1,floor(center_y - radius - 3 * sgrad));
ymax = min(size(img,1),ceil(center_y + radius + 3 * sgrad));
imgd = double(img(ymin:ymax,xmin:xmax));
%error = imgd/max(imgd,[],'all') - sphereProfile/max(sphereProfile,[],'all');
%error = sum(error(:).^2);
localerr = imgd/prctile(imgd(:),95) - sphereProfile(ymin:ymax,xmin:xmax);
criterion = zeros(size(img));
criterion(ymin:ymax,xmin:xmax) = localerr;
criterion = criterion(:);
end
Template for Yao
Authors
INRAE\Olivier Vitrac, INRAE\Yao Liu
Authors
The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
The dataset is accessible on lab PCs via yao_initialization(tframe)
tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
The data corresponding to tframe and additional details are accessible via
[Xframe,details] = yao_initialization(tframe)
Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
Authors
Revision history
2024-05-03 early version
2024-05-05 ROI with PBC implemented
2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
% Template for Yao
%
% Authors: INRAE\Olivier Vitrac, INRAE\Yao Liu
%
% The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
% The dataset is accessible on lab PCs via yao_initialization(tframe)
% tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
% The data corresponding to tframe and additional details are accessible via
% [Xframe,details] = yao_initialization(tframe)
% Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
%
% Revision history
% 2024-05-03 early version
% 2024-05-05 ROI with PBC implemented
% 2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
%% The code is split in blocks starting with "%%", they can be run independently
% by pressing CTRL+Enter or by choosing Run Section.
% The entire script can be run by pressing F5 or by choosing Run
%
% Control sections are enclosed between "%{ ... %}", they can be run by selecting
% the code between {} and by pressing F9 or by using the mouse right click and by
% choosing Evaluating the selection.
%
% visualization of the full dataset as a movie
%{
clf,
coords = {'z','x','y'};
for tframe = 0.11:0.01:1.11
Xframe = yao_initialization(tframe);
clf, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==1,coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==2,coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==3,coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==4,coords},'rs')
drawnow
end
%}
%% initialization
close all
clearvars -global -except tframe ztop
%% actions
actions = {'plot','save'};
if exist('D:\Yao','dir')
local = 'D:\Yao';
else
local = pwd;
end
backupfolder = fullfile(local,'Yao_xy');
if ~exist(backupfolder,'dir'), mkdir(backupfolder), end
%% load the frame
if ~exist('tframe','var')
tframe = 0.67; % choose any frame between 0.11 and 1.11
end
[Xframe,details] = yao_initialization(tframe);
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,details.coords};
% control
%{
figure, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.wall,details.coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.pillar,details.coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% Selection of the thick plane or ROI (region of interest)
if ~exist('ztop','var')
ztop = max(pillarxyz(:,3)); % top z coord of the pillar
end
zthick = ztop*0.15; % define the thickness of the plane around ztop
ROIbox = details.box; % select the ROI (region of interest)
ROIbox(3,:) = ztop + [-0.5 +0.5]*zthick; % update the ROI to ztop-zthick/2 and ztop+zthick/2
insideROIbox = true(height(Xframe.ATOMS),1); % boolean flag (by default all atoms are considered in ROI)
for icoord = 1:3 % for each coordinate
insideROIbox = insideROIbox ... the operator & ('and') enable to uncheck atoms beyond the bounds
& Xframe.ATOMS{:,details.coords{icoord}}>=ROIbox(icoord,1) ...
& Xframe.ATOMS{:,details.coords{icoord}}<=ROIbox(icoord,2);
end
atomsROI = Xframe.ATOMS(insideROIbox,:); % atom table for only ROI atoms
% control
%{
figure, hold on, axis equal, view(3)
plot3D(atomsROI{atomsROI.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(atomsROI{atomsROI.type==details.type.wall,details.coords},'co')
plot3D(atomsROI{atomsROI.type==details.type.pillar,details.coords},'go')
plot3D(atomsROI{atomsROI.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% add PBC images (periodic boundary)
XYZ = atomsROI{:,details.coords}; % coordinates of ROI atoms
vXYZ = atomsROI{:,details.vcoords}; % coordinates of ROI atoms
rhoXYZ = atomsROI.c_rho_smd;
[XYZimagesONLY ,indXimagesONLY]= PBCimages(XYZ,ROIbox,[true,true,false],zthick); % add periodic images within zthick around x and y (z is not periodic)
XYZwithImages = [XYZ;XYZimagesONLY]; % all atoms including their images
vXYZwithImages = [vXYZ;vXYZ(indXimagesONLY,:)]; % all atoms including their images
rhoXYZwithImages = [rhoXYZ;rhoXYZ(indXimagesONLY,:)];
isImages = true(size(XYZwithImages,1),1); isImages(1:size(XYZ,1))=false; % true if the atom is an image
% control - plot particle positions
%{
figure, hold on, axis equal, view(3)
plot3D(XYZ(atomsROI.type==details.type.fluid,:),'b.','markersize',2)
plot3D(XYZ(atomsROI.type==details.type.wall,:),'co')
plot3D(XYZ(atomsROI.type==details.type.pillar,:),'go')
plot3D(XYZ(atomsROI.type==details.type.sphere,:),'rs')
% add images with filled symbols
plot3D(XYZimagesONLY(atomsROI.isfluid(indXimagesONLY),:),'bo','markersize',2,'markerfacecolor','b')
plot3D(XYZimagesONLY(atomsROI.iswall(indXimagesONLY),:),'co','markerfacecolor','c')
plot3D(XYZimagesONLY(atomsROI.ispillar(indXimagesONLY),:),'go','markerfacecolor','g')
plot3D(XYZimagesONLY(atomsROI.issphere(indXimagesONLY),:),'rs','markerfacecolor','r')
%}
% control - plot velocity field
%{
figure, hold on
quiver3(XYZ(:,1),XYZ(:,2),XYZ(:,3),vXYZ(:,1),vXYZ(:,2),vXYZ(:,3))
view(3), axis equal
%}
%% determine the separation distance in simulation
boxdims = ROIbox(:,2) - ROIbox(:,1);
Vbead_guess = prod(boxdims)/size(XYZ,1); % m3
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,~,dmin] = buildVerletList(XYZ,cutoff); % ~ means here that the 2nd output is not used
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
%% add 3D Verlet list
% we focus on fluid atoms (only)
XYZfluid = XYZ(atomsROI.isfluid,:);
natomsfluid = size(XYZfluid,1);
isfluidwithimages = [atomsROI.isfluid;atomsROI.isfluid(indXimagesONLY)];
XYZfluidwithimages = XYZwithImages(isfluidwithimages,:);
vXYZfluidwithImages = vXYZwithImages(isfluidwithimages,:);
isImagesfluid = isImages & isfluidwithimages;
[Vfluidwithimages,cutoff,dmin] = buildVerletList(XYZfluidwithimages,1.2*h,[],[],[],isImagesfluid(isfluidwithimages),isImagesfluid(isfluidwithimages) & false);
% control of neighboring particles or one
%{
figure, hold on
for itest = unidrnd(natomsfluid,1,100)
plot3D(XYZfluidwithimages(~isImagesfluid(isfluidwithimages),:),'bo')
plot3D(XYZfluidwithimages(isImagesfluid(isfluidwithimages),:),'bo','markerfacecolor','b')
plot3D(XYZfluidwithimages(Vfluidwithimages{itest,:},:),'ro','markerfacecolor','r')
plot3D(XYZfluidwithimages(itest,:),'ko','markerfacecolor','k')
end
view(3), axis equal
%}
%% ForceLanshoff
% This pairwise force is an artificial force controlling the dissipation of velocity in the simulation
% its value is not stored in the simulation and it needs to be calculated from pairwise distances and
% relative velocities
% General syntax:
% [F,W] = forceLandshoff(XYZ,vXYZ,V,config)
% XYZ : coordinates
% vXYZ : velocities
% V : corresponding Verlet list
% config: configuration for Landshoff calculations based on the properties of the simulation
c0 = 1500; % speed of sound (m/s) % maxVelocity / MachTarget;
dynamicViscosity = 0.13; % Pa.s (viscosity to find)
q1 = 1; % 8 * dynamicViscosity / (hinformed*c0*rho);
mbead = 4.38e-12; % kg
configL = struct( ...real dynamic viscosity: rho * q1 * h * c0 / 8 (2D) or 10 (3D)
'gradkernel', kernelSPH(h,'lucyder',3),...% kernel gradient (note that h is bound with the kernel)
'h', h,... smoothing length (m)
'c0',1500,... speed of the sound (m/s)
'q1',1,... constant
'rho', rhoXYZwithImages(isfluidwithimages), ... fluid density
'mass', mbead,... bead weight
'vol', mbead./rhoXYZwithImages(isfluidwithimages), ... bead volume (uniquely for virial stress)
'repulsiononly', false ... if true, only Landshoff forces when dot(rij,vij)<0
);
[Fwithimages,Wwithimages] = forceLandshoff(XYZfluidwithimages,vXYZfluidwithImages,Vfluidwithimages,configL);
wihtoutimages = ~isImagesfluid(isfluidwithimages);
Fland = Fwithimages(wihtoutimages,:); % F Landshoff forces
Wland = Wwithimages(wihtoutimages,:); % corresponding Virial Stress Tensor
% plot the Landshoff forces acting on particles
figure, hold on
plot3D(XYZfluid,'bo','markersize',2)
quiver3(XYZfluid(:,1),XYZfluid(:,2),XYZfluid(:,3),Fland(:,1),Fland(:,2),Fland(:,3),'r-')
axis equal, view(3)
figure, histogram(log10(vecnorm(Fland,2,2))) % magnitude of the force on a log10 scale
%% Interpolate Virial Stress along an horizontal plane (has Han did)
nresolution = [1024 1024 1];
xw = linspace(ROIbox(1,1),ROIbox(1,2),nresolution(1));
yw = linspace(ROIbox(2,1),ROIbox(2,2),nresolution(1));
zw = ztop; % vertical position used for interpolation
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZgrid = buildVerletList({XYZgrid XYZfluidwithimages},2*h); % neighbors = fluid particles
W = kernelSPH(2*h,'lucy',3); % kernel expression
Wgrid = zeros(prod(nresolution(1:2)),9,'single');
for i = 1:9 % for all diagonal terms
Wgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages,Wwithimages(:,i),XYZgrid,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
end
%% Interpolate the Velocity, extraction of the strain rate tensor
vxyzgrid = zeros(prod(nresolution(1:2)),3,'single');
vxyzgridabove = vxyzgrid;
XYZgridabove = XYZgrid;
XYZgridabove(:,3) = XYZgridabove(:,3) + h; % information along z
for i = 1:3
vxyzgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgrid,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
vxyzgridabove(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgridabove,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
end
gxz = reshape( (vxyzgridabove(:,1) - vxyzgrid(:,1)) / h, nresolution(1:2));
gyz = reshape( (vxyzgridabove(:,2) - vxyzgrid(:,2)) / h, nresolution(1:2));
gzz = reshape( (vxyzgridabove(:,3) - vxyzgrid(:,3)) / h, nresolution(1:2));
[gxx,gxy] = gradient(reshape(vxyzgrid(:,1),nresolution(1:2)),Xw(1,2)-Xw(1,1),Yw(2,1)-Yw(1,1));
[gyx,gyy] = gradient(reshape(vxyzgrid(:,2),nresolution(1:2)),Xw(1,2)-Xw(1,1),Yw(2,1)-Yw(1,1));
[gzx,gzy] = gradient(reshape(vxyzgrid(:,3),nresolution(1:2)),Xw(1,2)-Xw(1,1),Yw(2,1)-Yw(1,1));
Ggrid = [gxx(:) gxy(:) gxz(:) gyx(:) gyy(:) gyz(:) gzx(:) gzy(:) gzz(:)]; % nine tensor components
% Velocity control
if ismember('plot',actions)
close all
formatfig(figure,'figname',sprintf('YAOxy_t%0.3g_z%0.3g',round(tframe*100),round(ztop*1e6)),'PaperPosition',[1.4920 7.8613 18.0000 14.0000]), hold on
step = 8; [ix,iy] = meshgrid(1:step:nresolution(1),1:step:nresolution(2)); indok = sub2ind(nresolution(1:2),ix,iy);
quiver3(XYZgrid(indok,1),XYZgrid(indok,2),XYZgrid(indok,3),vxyzgrid(indok,1),vxyzgrid(indok,2),vxyzgrid(indok,3),'r-')
axis tight
view(23,29)
daspect([1 1 0.03 ])
xlabel('x (m)','fontsize',12)
ylabel('y (m)','fontsize',12)
title(sprintf('t = %0.3g s - z = %0.3g µm',tframe,round(ztop*1e6)))
print_png(200,fullfile(backupfolder,get(gcf,'filename')),'','',0,0,0)
return
end
%{
figure, hold on
step = 8; [ix,iy] = meshgrid(1:step:nresolution(1),1:step:nresolution(2)); indok = sub2ind(nresolution(1:2),ix,iy);
quiver3(XYZgrid(indok,1),XYZgrid(indok,2),XYZgrid(indok,3),vxyzgrid(indok,1),vxyzgrid(indok,2),vxyzgrid(indok,3),'r-')
quiver3(XYZgridabove(:,1),XYZgridabove(:,2),XYZgridabove(:,3),vxyzgridabove(:,1),vxyzgridabove(:,2),vxyzgridabove(:,3),'g-')
%}
%% save
backfolder = fullfile('backupfolder','details');
backfolder = 'D:\Yao';
if exist(backfolder,'dir')
prefetchresult = sprintf('YAO_xy_t%0.3g_z%0.3g',tframe*1e2,ztop*1e6);
save(fullfile(backfolder,prefetchresult))
end
%% figure of 3D velocity field
figure, hold on
step = 16; [ix,iy] = meshgrid(1:step:nresolution(1),1:step:nresolution(2)); indok = sub2ind(nresolution(1:2),ix,iy);
quiver3(XYZgrid(indok,1),XYZgrid(indok,2),XYZgrid(indok,3),vxyzgrid(indok,1),vxyzgrid(indok,2),vxyzgrid(indok,3),'r-')
view(3)
xlabel('x (m)','fontsize',12)
ylabel('y (m)','fontsize',12)
zlabel('z (m)','fontsize',12)
set(gcf,'color','w')
%% Density
XYZs = atomsROI{atomsROI.issolid,details.coords};
rhobeadXYZ = atomsROI.c_rho_smd; % volume of the bead
rhobeadXYZwithImages = [rhobeadXYZ;rhobeadXYZ(indXimagesONLY)];
VbeadXYZwithImages = mbead./rhobeadXYZwithImages;
VXYZ = buildVerletList({XYZgrid XYZwithImages},1.2*h); % neighbors = fluid particles
VXYZs = buildVerletList({XYZgrid XYZs},h); % neighbors = solid particles (0.85*s)
icontactsolid = find(cellfun(@length,VXYZs)>0);
VXYZ(icontactsolid) = repmat({[]},length(icontactsolid),1);
rhobeadXYZgrid = interp3SPHVerlet(XYZwithImages,rhobeadXYZwithImages,XYZgrid,VXYZ,W,VbeadXYZwithImages);
rhobeadXYZgrid = reshape(rhobeadXYZgrid,size(Xw));
%% Symmetric Strain
Straingrid = [gxx(:) 0.5*(gxy(:)+gyx(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gxy(:)+gyx(:)) gyy(:) 0.5*(gyz(:)+gzy(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gyz(:)+gzy(:)) gzz(:)];
Straingrid(find(isnan(rhobeadXYZgrid)),:) = NaN;
formatfig(figure,'figname',sprintf('Yao_xy_SHEAR_t%d_z%d',round(tframe*100),round(zw*1e6)))
hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'$\dot{\varepsilon}_{xx}$','$\dot{\varepsilon}_{xy}$','$\dot{\varepsilon}_{xz}$',...
'$\dot{\varepsilon}_{yx}$','$\dot{\varepsilon}_{yy}$','$\dot{\varepsilon}_{yz}$',...
'$\dot{\varepsilon}_{zx}$','$\dot{\varepsilon}_{zy}$','$\dot{\varepsilon}_{zz}$'};
for i=1:9
subplot(hs2(i)), imagesc(flipud(reshape(Straingrid(:,i),nresolution([1 2]))))
c=colorbar; if i==7, c.Label.String = 'strain rate (s^{-1})'; end
axis image
caxis([-5 5])
title(leg{i},'fontsize',12,'visible','on','Interpreter','Latex')
end
set(hs2,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Stress
formatfig(figure,'figname',sprintf('Yao_xy_STRESS_t%d_z%d',round(tframe*100),round(zw*1e6)))
hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\tau_{xx}','\tau_{xy}','\tau_{xz}','\tau_{yx}','\tau_{yy}','\tau_{yz}','\tau_{zx}','\tau_{zy}','\tau_{zz}'};
Wgrid(find(isnan(rhobeadXYZgrid)),:) = NaN;
for i=1:9
subplot(hs2(i)), imagesc(flipud(reshape(Wgrid(:,i),nresolution([1 2]))))
c=colorbar; if i==7, c.Label.String = 'stress (Pa)'; end
axis image
caxis([-0.1 0.8])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs2,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Lamé coefficients
% Extract the diagonal terms for the trace calculation
iok = find(~isnan(rhobeadXYZgrid)); nok = length(iok);
% Extract the diagonal terms for the trace calculation
trace_Strain = Straingrid(iok,1) + Straingrid(iok,5) + Straingrid(iok,9);
% Form the matrix A for the global least-square problem
A = zeros(nok* 9, 2);
% Fill in the lambda * trace(Straingrid) * I part
trace_Strain_repeated = repmat(trace_Strain, 9, 1); % Repeat trace_Strain 9 times
A(:, 1) = trace_Strain_repeated(:); % Fill the first column of A with the repeated trace
% Fill in the 2 * mu * Straingrid part
A(:, 2) = reshape(2 * Straingrid(iok,:)', [], 1); % Reshape 2 * Straingrid to a column vector
% Reshape Wgrid to a vector
Wgrid_vectorized = reshape(Wgrid(iok,:)', [], 1);
% Solve the non-negative least squares problem
params = lsqnonneg(A, double(Wgrid_vectorized));
% Extract the results
lam_global = params(1);
mu_global = params(2);
% Display the results
disp(['Lambda (λ): ', num2str(lam_global)]);
disp(['Mu (μ): ', num2str(mu_global)]);
% Local approximates
% Initialize arrays to store the local estimations of mu and lam
mu_local = zeros(1048576, 1);
lam_local = zeros(1048576, 1);
% Extract the diagonal terms of Straingrid for trace calculation
trace_Strain = Straingrid(:,1) + Straingrid(:,5) + Straingrid(:,9);
% Construct the identity matrix as a vector
I_vector = [1 0 0 0 1 0 0 0 1];
for i = 1:1048576
% Create the system of equations for the current grid point
A_local = zeros(9, 2);
% Fill in the lambda * trace(Straingrid) * I part
A_local(:, 1) = trace_Strain(i) * I_vector';
% Fill in the 2 * mu * Straingrid part
A_local(:, 2) = 2 * Straingrid(i, :)';
% Solve for lam and mu using non-negative least squares
params_local = lsqnonneg(A_local, double(Wgrid(i, :))');
% Store the results
lam_local(i) = params_local(1);
mu_local(i) = params_local(2);
end
% Display a sample of the results
disp('Sample of local Lambda (λ) estimates:');
disp(lam_local(1:10)');
disp('Sample of local Mu (μ) estimates:');
disp(mu_local(1:10)');
%% Lambda, Mu plots
formatfig(figure,'figname',sprintf('Yao_xy_VISCO_L2_t%d_z%d',round(tframe*100),round(zw*1e6)))
imagesc(xw,yw,flipud(reshape(mu_local,nresolution([1 2]))))
c=colorbar; c.Label.String = '\mu (Pa\cdot s^{-1})'; caxis([0 0.15])
c.FontSize=12; formatax(gca,'fontsize',12), c.Label.FontSize=16;
xlabel('x (µm)','fontsize',14), ylabel('y (µm)','fontsize',14)
title({'Dynamic Viscosity' sprintf('t = \\bf%0.3g s\\rm, z = \\bf%0.3g\\rm µm',tframe,1e6*ztop)},'fontsize',14,'visible','on')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
formatfig(figure,'figname',sprintf('Yao_xy_VISCOVOL_L2_t%d_z%d',round(tframe*100),round(zw*1e6)))
imagesc(xw,yw,flipud(reshape(lam_local,nresolution([1 2]))))
c=colorbar; c.Label.String = '\lambda (Pa\cdot s^{-1})'; caxis([0 2])
c.FontSize=12; formatax(gca,'fontsize',12), c.Label.FontSize=16;
xlabel('x (µm)','fontsize',14), ylabel('y (µm)','fontsize',14)
title({'Elongational Viscosity' sprintf('t = \\bf%0.3g s\\rm, z = \\bf%0.3g\\rm µm',tframe,1e6*ztop)},'fontsize',14,'visible','on')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
return
RECOresult = fullfile(backfolder,sprintf('YAO_VISCOL2_xy_t%0.3g_z%0.3g',tframe*1e2,ztop*1e6));
save(RECOresult,'mu_local','lam_local','Straingrid','Wgrid','Xw','Yw','xw','yw','tframe','ztop')
%% Comparison
backfolder = 'D:\Yao';
RECO = [
load('D:\Yao\YAO_VISCOL2_xy_t57_z425.mat')
load('D:\Yao\YAO_VISCOL2_xy_t67_z425.mat')
load('D:\Yao\YAO_VISCOL2_xy_t77_z425.mat')
];
formatfig(figure,'figname','Yao_distributionStrain'), hold on
for i=1:length(RECO)
histogram(RECO(i).Straingrid(:,2),linspace(-5,5,100),'DisplayName',sprintf('t=%0.3g s',RECO(i).tframe),'FaceAlpha',0.5)
end
formatax(gca,'fontsize',12)
legend('fontsize',14,'box','off')
xlabel('Strain Rate: $\dot{\varepsilon}_{yx}$ (s$^{-1}$)', 'fontsize', 14, 'Interpreter', 'latex')
ylabel('Counts','fontsize',14)
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
formatfig(figure,'figname','Yao_distributionStress'), hold on
for i=1:length(RECO)
histogram(RECO(i).Wgrid(:,2),linspace(-0.1,0.1,500),'DisplayName',sprintf('t=%0.3g s',RECO(i).tframe),'FaceAlpha',0.5)
end
formatax(gca,'xlim',[-0.07 0.07],'ylim',[0 2.5]*1e4,'fontsize',12)
legend('fontsize',14,'box','off')
xlabel('Shear Stress: \tau_{yx} (Pa)','fontsize',14)
ylabel('Counts','fontsize',14)
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
formatfig(figure,'figname','Yao_distributionVisco'), hold on
for i=1:length(RECO)
histogram(RECO(i).mu_local,linspace(0,0.3,100),'DisplayName',sprintf('t=%0.3g s',RECO(i).tframe),'FaceAlpha',0.5)
end
formatax(gca,'xlim',[0 0.18],'ylim',[0 2.5]*1e4,'fontsize',12)
legend('fontsize',14,'box','off')
xlabel('Dynamic Viscosity: \mu (Pa\cdot s^{-1})','fontsize',14)
ylabel('Counts','fontsize',14)
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
formatfig(figure,'figname','Yao_distributionElongVisco'), hold on
for i=1:length(RECO)
histogram(RECO(i).lam_local,linspace(0,0.5,200),'DisplayName',sprintf('t=%0.3g s',RECO(i).tframe),'FaceAlpha',0.5)
end
formatax(gca,'xlim',[0 0.4],'ylim',[0 2.5]*1e4,'fontsize',12)
legend('fontsize',14,'box','off')
xlabel('Elongational Viscosity: \lambda (Pa\cdot s^{-1})','fontsize',14)
ylabel('Counts','fontsize',14)
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
% obsolete below ---------------------------------------------------------------------------------------------------------
%% Viscosity estimation
% Egrid = abs(Wgrid./Ggrid);
Egrid = 0.5*abs(Wgrid./Straingrid);
formatfig(figure,'figname',sprintf('Yao_xy_VISCO_t%d_z%d',round(tframe*100),round(zw*1e6)))
hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\mu_{xx}','\mu_{xy}','\mu_{xz}','\mu_{yx}','\mu_{yy}','\mu_{yz}','\mu_{zx}','\mu_{zy}','\mu_{zz}'};
for i=1:9
subplot(hs2(i)), imagesc(reshape(Straingrid(:,i),nresolution([1 2])))
c=colorbar; if i==7, c.Label.String = 'stress (Pa\cdot s)'; end
axis image
caxis([0 1])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs2,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
% %% plot stress results
% close all
% offdiag = [2 3 4 6 7 8];
% figstresstensor = figure;
% figsheartensor = figure;
% figviscosity = figure;
% formatfig(figstresstensor,'figname',sprintf('STRESS_t%d_z%d',round(tframe*100),round(zw*1e6)))
% formatfig(figsheartensor,'figname',sprintf('SHEAR_t%d_z%d',round(tframe*100),round(zw*1e6)))
% formatfig(figviscosity,'figname',sprintf('VISCO_t%d_z%d',round(tframe*100),round(zw*1e6)))
%
% figure(figstresstensor), hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
% figure(figsheartensor), hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
% figure(figviscosity), hs3 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
% [Wmin,Wmax,Gmin,Gmax,Emin,Emax] = deal(+Inf,-Inf,+Inf,-Inf,+Inf,-Inf);
% for i=1:9
% figure(figstresstensor), subplot(hs1(i)), imagesc(reshape(Wgrid(:,i),nresolution([1 2]))), colorbar
% figure(figsheartensor), subplot(hs2(i)), imagesc(reshape(Ggrid(:,i),nresolution([1 2]))), colorbar
% figure(figviscosity), subplot(hs3(i)), imagesc(reshape(Egrid(:,i),nresolution([1 2]))), colorbar
% Wmin = min(Wmin,min(Wgrid(:,i)));
% Wmax = max(Wmax,max(Wgrid(:,i)));
% Gmin = min(Wmin,min(Ggrid(:,i)));
% Gmax = max(Wmax,max(Ggrid(:,i)));
% Emin = min(Wmin,min(Egrid(:,i)));
% Emax = max(Wmax,max(Egrid(:,i)));
% end
% set(hs1,'visible','off')
% set(hs2,'visible','off')
% set(hs3,'visible','off')
% for i=1:9
% figure(figstresstensor), subplot(hs1(i)), clim([Wmin Wmax])
% figure(figsheartensor), subplot(hs1(i)), clim([Gmin Gmax])
% figure(figviscosity), subplot(hs1(i)), clim([Emin Emax])
% end
% set(hs3,'clim',[0.01 10 ])
%
% % plot
% figsumall = figure;
% formatfig(figsumall,'figname',sprintf('STRESS_sumoff_t%d_z%d',round(tframe*100)))
% title('all off-diagonal terms are added')
% imagesc(reshape(sum(Wgrid(:,offdiag),2),nresolution([1 2])))
%
% %% print
% outputfolder = 'YAOresults';
% if ~exist(outputfolder,'dir'), mkdir(outputfolder), end
% figure(figstresstensor)
% print_pdf(300,[get(figstresstensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
% print_png(300,fullfile(outputfolder,get(figstresstensor,'filename')),'','',0,0,0)
% figure(figsheartensor)
% print_pdf(300,[get(figsheartensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
% print_png(300,fullfile(outputfolder,get(figsheartensor,'filename')),'','',0,0,0)
% figure(figviscosity)
% print_pdf(300,[get(figviscosity,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
% print_png(300,fullfile(outputfolder,get(figviscosity,'filename')),'','',0,0,0)
% figure(figsumall)
% print_pdf(300,[get(figsumall,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
% print_png(300,fullfile(outputfolder,get(figsumall,'filename')),'','',0,0,0)
Template for Yao
Authors
INRAE\Olivier Vitrac, INRAE\Yao Liu
Authors
The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
The dataset is accessible on lab PCs via yao_initialization(tframe)
tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
The data corresponding to tframe and additional details are accessible via
[Xframe,details] = yao_initialization(tframe)
Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
Authors
Revision history
2024-05-03 early version
2024-05-05 ROI with PBC implemented
2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
% Template for Yao
%
% Authors: INRAE\Olivier Vitrac, INRAE\Yao Liu
%
% The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
% The dataset is accessible on lab PCs via yao_initialization(tframe)
% tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
% The data corresponding to tframe and additional details are accessible via
% [Xframe,details] = yao_initialization(tframe)
% Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
%
% Revision history
% 2024-05-03 early version
% 2024-05-05 ROI with PBC implemented
% 2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
%% The code is split in blocks starting with "%%", they can be run independently
% by pressing CTRL+Enter or by choosing Run Section.
% The entire script can be run by pressing F5 or by choosing Run
%
% Control sections are enclosed between "%{ ... %}", they can be run by selecting
% the code between {} and by pressing F9 or by using the mouse right click and by
% choosing Evaluating the selection.
%
% visualization of the full dataset as a movie
%{
clf,
coords = {'z','x','y'};
for tframe = 0.11:0.01:1.11
Xframe = yao_initialization(tframe);
clf, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==1,coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==2,coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==3,coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==4,coords},'rs')
drawnow
end
%}
%% load the frame
tframe = 0.67; % choose any frame between 0.11 and 1.11
[Xframe,details] = yao_initialization(tframe);
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,details.coords};
% control
%{
figure, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.wall,details.coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.pillar,details.coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% Selection of the thick plane or ROI (region of interest)
ymean = mean(pillarxyz(:,2)); % mean y coord of the pillar
ythick = ymean*0.15*5; % define the thickness of the plane around ztop
ROIbox = details.box; % select the ROI (region of interest)
ROIbox(2,:) = ymean + [-0.5 +0.5]*ythick; % update the ROI to ztop-zthick/2 and ztop+zthick/2
insideROIbox = true(height(Xframe.ATOMS),1); % boolean flag (by default all atoms are considered in ROI)
for icoord = 1:3 % for each coordinate
insideROIbox = insideROIbox ... the operator & ('and') enable to uncheck atoms beyond the bounds
& Xframe.ATOMS{:,details.coords{icoord}}>=ROIbox(icoord,1) ...
& Xframe.ATOMS{:,details.coords{icoord}}<=ROIbox(icoord,2);
end
atomsROI = Xframe.ATOMS(insideROIbox,:); % atom table for only ROI atoms
% control
%{
figure, hold on, axis equal, view(3)
plot3D(atomsROI{atomsROI.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(atomsROI{atomsROI.type==details.type.wall,details.coords},'co')
plot3D(atomsROI{atomsROI.type==details.type.pillar,details.coords},'go')
plot3D(atomsROI{atomsROI.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% add PBC images (periodic boundary)
XYZ = atomsROI{:,details.coords}; % coordinates of ROI atoms
vXYZ = atomsROI{:,details.vcoords}; % coordinates of ROI atoms
rhoXYZ = atomsROI.c_rho_smd;
[XYZimagesONLY ,indXimagesONLY]= PBCimages(XYZ,ROIbox,[true,false,true],ythick); % add periodic images within zthick around x and z (y is not periodic)
XYZwithImages = [XYZ;XYZimagesONLY]; % all atoms including their images
vXYZwithImages = [vXYZ;vXYZ(indXimagesONLY,:)]; % all atoms including their images
rhoXYZwithImages = [rhoXYZ;rhoXYZ(indXimagesONLY,:)];
isImages = true(size(XYZwithImages,1),1); isImages(1:size(XYZ,1))=false; % true if the atom is an image
% control - plot particle positions
%{
figure, hold on, axis equal, view(3)
plot3D(XYZ(atomsROI.type==details.type.fluid,:),'b.','markersize',2)
plot3D(XYZ(atomsROI.type==details.type.wall,:),'co')
plot3D(XYZ(atomsROI.type==details.type.pillar,:),'go')
plot3D(XYZ(atomsROI.type==details.type.sphere,:),'rs')
% add images with filled symbols
plot3D(XYZimagesONLY(atomsROI.isfluid(indXimagesONLY),:),'bo','markersize',2,'markerfacecolor','b')
plot3D(XYZimagesONLY(atomsROI.iswall(indXimagesONLY),:),'co','markerfacecolor','c')
plot3D(XYZimagesONLY(atomsROI.ispillar(indXimagesONLY),:),'go','markerfacecolor','g')
plot3D(XYZimagesONLY(atomsROI.issphere(indXimagesONLY),:),'rs','markerfacecolor','r')
%}
% control - plot velocity field
%{
figure, hold on
quiver3(XYZ(:,1),XYZ(:,2),XYZ(:,3),vXYZ(:,1),vXYZ(:,2),vXYZ(:,3))
view(3), axis equal
%}
%% determine the separation distance in simulation
boxdims = ROIbox(:,2) - ROIbox(:,1);
Vbead_guess = prod(boxdims)/size(XYZ,1); % m3
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,~,dmin] = buildVerletList(XYZ,cutoff); % ~ means here that the 2nd output is not used
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
%% add 3D Verlet list
% we focus on fluid atoms (only)
XYZfluid = XYZ(atomsROI.isfluid,:);
natomsfluid = size(XYZfluid,1);
isfluidwithimages = [atomsROI.isfluid;atomsROI.isfluid(indXimagesONLY)];
XYZfluidwithimages = XYZwithImages(isfluidwithimages,:);
vXYZfluidwithImages = vXYZwithImages(isfluidwithimages,:);
isImagesfluid = isImages & isfluidwithimages;
[Vfluidwithimages,cutoff,dmin] = buildVerletList(XYZfluidwithimages,1.2*h,[],[],[],isImagesfluid(isfluidwithimages),isImagesfluid(isfluidwithimages) & false);
% control of neighboring particles or one
%{
figure, hold on
for itest = unidrnd(natomsfluid,1,100)
plot3D(XYZfluidwithimages(~isImagesfluid(isfluidwithimages),:),'bo')
plot3D(XYZfluidwithimages(isImagesfluid(isfluidwithimages),:),'bo','markerfacecolor','b')
plot3D(XYZfluidwithimages(Vfluidwithimages{itest,:},:),'ro','markerfacecolor','r')
plot3D(XYZfluidwithimages(itest,:),'ko','markerfacecolor','k')
end
view(3), axis equal
%}
%% ForceLanshoff
% This pairwise force is an artificial force controlling the dissipation of velocity in the simulation
% its value is not stored in the simulation and it needs to be calculated from pairwise distances and
% relative velocities
% General syntax:
% [F,W] = forceLandshoff(XYZ,vXYZ,V,config)
% XYZ : coordinates
% vXYZ : velocities
% V : corresponding Verlet list
% config: configuration for Landshoff calculations based on the properties of the simulation
c0 = 1500; % speed of sound (m/s) % maxVelocity / MachTarget;
dynamicViscosity = 0.13; % Pa.s (viscosity to find)
q1 = 1; % 8 * dynamicViscosity / (hinformed*c0*rho);
mbead = 4.38e-12; % kg
configL = struct( ...real dynamic viscosity: rho * q1 * h * c0 / 8 (2D) or 10 (3D)
'gradkernel', kernelSPH(h,'lucyder',3),...% kernel gradient (note that h is bound with the kernel)
'h', h,... smoothing length (m)
'c0',1500,... speed of the sound (m/s)
'q1',1,... constant
'rho', rhoXYZwithImages(isfluidwithimages), ... fluid density
'mass', mbead,... bead weight
'vol', mbead./rhoXYZwithImages(isfluidwithimages), ... bead volume (uniquely for virial stress)
'repulsiononly', false ... if true, only Landshoff forces when dot(rij,vij)<0
);
[Fwithimages,Wwithimages] = forceLandshoff(XYZfluidwithimages,vXYZfluidwithImages,Vfluidwithimages,configL);
wihtoutimages = ~isImagesfluid(isfluidwithimages);
Fland = Fwithimages(wihtoutimages,:); % F Landshoff forces
Wland = Wwithimages(wihtoutimages,:); % corresponding Virial Stress Tensor
% plot the Landshoff forces acting on particles
figure, hold on
plot3D(XYZfluid,'bo','markersize',2)
quiver3(XYZfluid(:,1),XYZfluid(:,2),XYZfluid(:,3),Fland(:,1),Fland(:,2),Fland(:,3),'r-')
axis equal, view(3)
figure, histogram(log10(vecnorm(Fland,2,2))) % magnitude of the force on a log10 scale
%% Interpolate Virial Stress along an xz vertical plane (as Han did)
nresolution = [1024 1 1024];
xw = linspace(ROIbox(1,1),ROIbox(1,2),nresolution(1));
yw = ymean;
zw = linspace(ROIbox(3,1),ROIbox(3,2),nresolution(3)); % vertical position used for interpolation
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZgrid = buildVerletList({XYZgrid, XYZfluidwithimages}, 2*h); % neighbors = fluid particles
W = kernelSPH(2*h, 'lucy', 3); % kernel expression
Wgrid = zeros(prod(nresolution(1:3)), 9, 'single');
for i = 1:9 % for all diagonal terms
Wgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages, Wwithimages(:,i), XYZgrid, VXYZgrid, W, mbead./rhoXYZwithImages(isfluidwithimages));
end
%% Interpolate the Velocity, extraction of the strain rate tensor
vxyzgrid = zeros(prod(nresolution(1:3)),3,'single');
vxyzgridabove = vxyzgrid;
XYZgridabove = XYZgrid;
XYZgridabove(:,2) = XYZgridabove(:,2) + h; % information along z
for i = 1:3
vxyzgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgrid,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
vxyzgridabove(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgridabove,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
end
gxy = reshape( (vxyzgridabove(:,1) - vxyzgrid(:,1)) / h, nresolution(1:3));
gyy = reshape( (vxyzgridabove(:,2) - vxyzgrid(:,2)) / h, nresolution(1:3));
gzy = reshape( (vxyzgridabove(:,3) - vxyzgrid(:,3)) / h, nresolution(1:3));
[gxx,gxz] = gradient(reshape(vxyzgrid(:,1),nresolution([1 3])),Xw(1,2,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
[gyx,gyz] = gradient(reshape(vxyzgrid(:,2),nresolution([1 3])),Xw(1,2,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
[gzx,gzz] = gradient(reshape(vxyzgrid(:,3),nresolution([1 3])),Xw(1,2,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
Ggrid = [gxx(:) gxy(:) gxz(:) gyx(:) gyy(:) gyz(:) gzx(:) gzy(:) gzz(:)]; % nine tensor components
% Velocity control
%{
figure, hold on
step = 16; [ix,iy,iz] = meshgrid(1:step:nresolution(1),1,1:step:nresolution(3)); indok = sub2ind(nresolution,ix,iy,iz);
quiver3(XYZgrid(indok,1),XYZgrid(indok,2),XYZgrid(indok,3),vxyzgrid(indok,1),vxyzgrid(indok,2),vxyzgrid(indok,3),'b-')
quiver3(XYZgridabove(:,1),XYZgridabove(:,2),XYZgridabove(:,3),vxyzgridabove(:,1),vxyzgridabove(:,2),vxyzgridabove(:,3),'g-')
%}
%% save
backfolder = 'D:\Yao';
if exist(backfolder,'dir')
prefetchresult = sprintf('YAO_xz_t%0.3g_y%0.3g',tframe*1e2,ymean*1e6);
save(fullfile(backfolder,prefetchresult))
end
%% Symmetric Strain
Straingrid = [gxx(:) 0.5*(gxy(:)+gyx(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gxy(:)+gyx(:)) gyy(:) 0.5*(gyz(:)+gzy(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gyz(:)+gzy(:)) gzz(:)];
formatfig(figure,'figname',sprintf('Yao_xz_SHEAR_t%d_y%d',round(tframe*100),round(ymean*1e6)))
hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\epsilon_{xx}','\epsilon_{xy}','\epsilon_{xz}','\epsilon_{yx}','\epsilon_{yy}','\epsilon_{yz}','\epsilon_{zx}','\epsilon_{zy}','\epsilon_{zz}'};
for i=1:9
subplot(hs2(i)), imagesc(flipud(reshape(Straingrid(:,i),nresolution([1 3]))'))
c=colorbar; if i==7, c.Label.String = 'strain rate (s^{-1})'; end
axis image
caxis([-5 5])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs2,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Stress Tensor
formatfig(figure,'figname',sprintf('Yao_xz_STRESS_t%d_y%d',round(tframe*100),round(ymean*1e6)))
hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\tau_{xx}','\tau_{xy}','\tau_{xz}','\tau_{yx}','\tau_{yy}','\tau_{yz}','\tau_{zx}','\tau_{zy}','\tau_{zz}'};
for i=1:9
subplot(hs1(i)), imagesc(flipud(reshape(Wgrid(:,i),nresolution([1 3]))'))
c=colorbar; if i==7, c.Label.String = 'stress (Pa)'; end
axis image
caxis([-0.1 0.8])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs1,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Viscosity estimation
Egrid = 0.5*abs(Wgrid./Straingrid);
formatfig(figure,'figname',sprintf('Yao_xz_VISCO_t%d_y%d',round(tframe*100),round(ymean*1e6)))
hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\mu_{xx}','\mu_{xy}','\mu_{xz}','\mu_{yx}','\mu_{yy}','\mu_{yz}','\mu_{zx}','\mu_{zy}','\mu_{zz}'};
for i=1:9
subplot(hs1(i)), imagesc(flipud(reshape(Egrid(:,i),nresolution([1 3]))'))
c=colorbar; if i==7, c.Label.String = 'viscosity (Pa\cdot s)'; end
axis image
caxis([0 0.4])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs1,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% plot stress results
close all
offdiag = [2 3 4 6 7 8];
figstresstensor = figure;
figsheartensor = figure;
figviscosity = figure;
formatfig(figstresstensor,'figname',sprintf('STRESS_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
formatfig(figsheartensor,'figname',sprintf('SHEAR_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
formatfig(figviscosity,'figname',sprintf('VISCO_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
figure(figstresstensor), hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
figure(figsheartensor), hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
figure(figviscosity), hs3 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
[Wmin,Wmax,Gmin,Gmax,Emin,Emax] = deal(+Inf,-Inf,+Inf,-Inf,+Inf,-Inf);
for i=1:9
figure(figstresstensor), subplot(hs1(i)), imagesc(flipud(reshape(Wgrid(:,i),nresolution([1 3]))')), colorbar
figure(figsheartensor), subplot(hs2(i)), imagesc(flipud(reshape(Ggrid(:,i),nresolution([1 3]))')), colorbar
figure(figviscosity), subplot(hs3(i)), imagesc(flipud(reshape(Egrid(:,i),nresolution([1 3]))')), colorbar
Wmin = min(Wmin,min(Wgrid(:,i)));
Wmax = max(Wmax,max(Wgrid(:,i)));
Gmin = min(Wmin,min(Ggrid(:,i)));
Gmax = max(Wmax,max(Ggrid(:,i)));
Emin = min(Wmin,min(Egrid(:,i)));
Emax = max(Wmax,max(Egrid(:,i)));
end
set(hs1,'visible','off')
set(hs2,'visible','off')
set(hs3,'visible','off')
for i=1:9
figure(figstresstensor), subplot(hs1(i)), clim([Wmin Wmax])
figure(figsheartensor), subplot(hs1(i)), clim([Gmin Gmax])
figure(figviscosity), subplot(hs1(i)), clim([Emin Emax])
end
set(hs3,'clim',[0.01 10 ])
% plot
figsumall = figure;
formatfig(figsumall,'figname',sprintf('STRESS_sumoff_t%d_z%d',round(tframe*100)))
title('all off-diagonal terms are added')
imagesc(flipud(reshape(sum(Wgrid(:,offdiag),2),nresolution([1 3]))'))
%% print
outputfolder = 'YAOresults';
if ~exist(outputfolder,'dir'), mkdir(outputfolder), end
figure(figstresstensor)
print_pdf(300,[get(figstresstensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figstresstensor,'filename')),'','',0,0,0)
figure(figsheartensor)
print_pdf(300,[get(figsheartensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figsheartensor,'filename')),'','',0,0,0)
figure(figviscosity)
print_pdf(300,[get(figviscosity,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figviscosity,'filename')),'','',0,0,0)
figure(figsumall)
print_pdf(300,[get(figsumall,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figsumall,'filename')),'','',0,0,0)
Template for Yao
Authors
INRAE\Olivier Vitrac, INRAE\Yao Liu
Authors
The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
The dataset is accessible on lab PCs via yao_initialization(tframe)
tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
The data corresponding to tframe and additional details are accessible via
[Xframe,details] = yao_initialization(tframe)
Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
Authors
Revision history
2024-05-03 early version
2024-05-05 ROI with PBC implemented
2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
% Template for Yao
%
% Authors: INRAE\Olivier Vitrac, INRAE\Yao Liu
%
% The work is based on the simulation of Billy (publication so-called pizza2, and series Thomazo_v2)
% The dataset is accessible on lab PCs via yao_initialization(tframe)
% tframe should be chosen among 0.11:0.01:1.11 (0.11, 0.12, 0.13, ... 1.11 s)
% The data corresponding to tframe and additional details are accessible via
% [Xframe,details] = yao_initialization(tframe)
% Due to the many depencies with pizza3, the code needs to be run from : Thomazo_v2\
%
% Revision history
% 2024-05-03 early version
% 2024-05-05 ROI with PBC implemented
% 2024-05-06 shear stress and shear rate implementated on a two 2D 1024*1024 grids separated by h
%% The code is split in blocks starting with "%%", they can be run independently
% by pressing CTRL+Enter or by choosing Run Section.
% The entire script can be run by pressing F5 or by choosing Run
%
% Control sections are enclosed between "%{ ... %}", they can be run by selecting
% the code between {} and by pressing F9 or by using the mouse right click and by
% choosing Evaluating the selection.
%
% visualization of the full dataset as a movie
%{
clf,
coords = {'z','x','y'};
for tframe = 0.11:0.01:1.11
Xframe = yao_initialization(tframe);
clf, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==1,coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==2,coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==3,coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==4,coords},'rs')
drawnow
end
%}
%% load the frame
tframe = 0.67; % choose any frame between 0.11 and 1.11
[Xframe,details] = yao_initialization(tframe);
pillarxyz = Xframe.ATOMS{Xframe.ATOMS.ispillar,details.coords};
% control
%{
figure, hold on, axis equal, view(3)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.wall,details.coords},'co')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.pillar,details.coords},'go')
plot3D(Xframe.ATOMS{Xframe.ATOMS.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% Selection of the thick plane or ROI (region of interest)
xmean = mean(pillarxyz(:,1)); % mean y coord of the pillar
xthick = xmean*0.15*5; % define the thickness of the plane around ztop
ROIbox = details.box; % select the ROI (region of interest)
ROIbox(1,:) = xmean + [-0.5 +0.5]*xthick; % update the ROI to ztop-zthick/2 and ztop+zthick/2
insideROIbox = true(height(Xframe.ATOMS),1); % boolean flag (by default all atoms are considered in ROI)
for icoord = 1:3 % for each coordinate
insideROIbox = insideROIbox ... the operator & ('and') enable to uncheck atoms beyond the bounds
& Xframe.ATOMS{:,details.coords{icoord}}>=ROIbox(icoord,1) ...
& Xframe.ATOMS{:,details.coords{icoord}}<=ROIbox(icoord,2);
end
atomsROI = Xframe.ATOMS(insideROIbox,:); % atom table for only ROI atoms
% control
%{
figure, hold on, axis equal, view(3)
plot3D(atomsROI{atomsROI.type==details.type.fluid,details.coords},'b.','markersize',2)
plot3D(atomsROI{atomsROI.type==details.type.wall,details.coords},'co')
plot3D(atomsROI{atomsROI.type==details.type.pillar,details.coords},'go')
plot3D(atomsROI{atomsROI.type==details.type.sphere,details.coords},'rs','markerfacecolor','r')
%}
%% add PBC images (periodic boundary)
XYZ = atomsROI{:,details.coords}; % coordinates of ROI atoms
vXYZ = atomsROI{:,details.vcoords}; % coordinates of ROI atoms
rhoXYZ = atomsROI.c_rho_smd;
[XYZimagesONLY ,indXimagesONLY]= PBCimages(XYZ,ROIbox,[false,true,true],xthick); % add periodic images within zthick around x and z (y is not periodic)
XYZwithImages = [XYZ;XYZimagesONLY]; % all atoms including their images
vXYZwithImages = [vXYZ;vXYZ(indXimagesONLY,:)]; % all atoms including their images
rhoXYZwithImages = [rhoXYZ;rhoXYZ(indXimagesONLY,:)];
isImages = true(size(XYZwithImages,1),1); isImages(1:size(XYZ,1))=false; % true if the atom is an image
% control - plot particle positions
%{
figure, hold on, axis equal, view(3)
plot3D(XYZ(atomsROI.type==details.type.fluid,:),'b.','markersize',2)
plot3D(XYZ(atomsROI.type==details.type.wall,:),'co')
plot3D(XYZ(atomsROI.type==details.type.pillar,:),'go')
plot3D(XYZ(atomsROI.type==details.type.sphere,:),'rs')
% add images with filled symbols
plot3D(XYZimagesONLY(atomsROI.isfluid(indXimagesONLY),:),'bo','markersize',2,'markerfacecolor','b')
plot3D(XYZimagesONLY(atomsROI.iswall(indXimagesONLY),:),'co','markerfacecolor','c')
plot3D(XYZimagesONLY(atomsROI.ispillar(indXimagesONLY),:),'go','markerfacecolor','g')
plot3D(XYZimagesONLY(atomsROI.issphere(indXimagesONLY),:),'rs','markerfacecolor','r')
%}
% control - plot velocity field
%{
figure, hold on
quiver3(XYZ(:,1),XYZ(:,2),XYZ(:,3),vXYZ(:,1),vXYZ(:,2),vXYZ(:,3))
view(3), axis equal
%}
%% determine the separation distance in simulation
boxdims = ROIbox(:,2) - ROIbox(:,1);
Vbead_guess = prod(boxdims)/size(XYZ,1); % m3
rbead_guess = (3/(4*pi)*Vbead_guess)^(1/3);
cutoff = 3*rbead_guess;
[verletList,~,dmin] = buildVerletList(XYZ,cutoff); % ~ means here that the 2nd output is not used
rbead = dmin/2;
s = 2*rbead; % separation distance
h = 2*s; % smoothing length
%% add 3D Verlet list
% we focus on fluid atoms (only)
XYZfluid = XYZ(atomsROI.isfluid,:);
natomsfluid = size(XYZfluid,1);
isfluidwithimages = [atomsROI.isfluid;atomsROI.isfluid(indXimagesONLY)];
XYZfluidwithimages = XYZwithImages(isfluidwithimages,:);
vXYZfluidwithImages = vXYZwithImages(isfluidwithimages,:);
isImagesfluid = isImages & isfluidwithimages;
[Vfluidwithimages,cutoff,dmin] = buildVerletList(XYZfluidwithimages,1.2*h,[],[],[],isImagesfluid(isfluidwithimages),isImagesfluid(isfluidwithimages) & false);
% control of neighboring particles or one
%{
figure, hold on
for itest = unidrnd(natomsfluid,1,100)
plot3D(XYZfluidwithimages(~isImagesfluid(isfluidwithimages),:),'bo')
plot3D(XYZfluidwithimages(isImagesfluid(isfluidwithimages),:),'bo','markerfacecolor','b')
plot3D(XYZfluidwithimages(Vfluidwithimages{itest,:},:),'ro','markerfacecolor','r')
plot3D(XYZfluidwithimages(itest,:),'ko','markerfacecolor','k')
end
view(3), axis equal
%}
%% ForceLanshoff
% This pairwise force is an artificial force controlling the dissipation of velocity in the simulation
% its value is not stored in the simulation and it needs to be calculated from pairwise distances and
% relative velocities
% General syntax:
% [F,W] = forceLandshoff(XYZ,vXYZ,V,config)
% XYZ : coordinates
% vXYZ : velocities
% V : corresponding Verlet list
% config: configuration for Landshoff calculations based on the properties of the simulation
c0 = 1500; % speed of sound (m/s) % maxVelocity / MachTarget;
dynamicViscosity = 0.13; % Pa.s (viscosity to find)
q1 = 1; % 8 * dynamicViscosity / (hinformed*c0*rho);
mbead = 4.38e-12; % kg
configL = struct( ...real dynamic viscosity: rho * q1 * h * c0 / 8 (2D) or 10 (3D)
'gradkernel', kernelSPH(h,'lucyder',3),...% kernel gradient (note that h is bound with the kernel)
'h', h,... smoothing length (m)
'c0',1500,... speed of the sound (m/s)
'q1',1,... constant
'rho', rhoXYZwithImages(isfluidwithimages), ... fluid density
'mass', mbead,... bead weight
'vol', mbead./rhoXYZwithImages(isfluidwithimages), ... bead volume (uniquely for virial stress)
'repulsiononly', false ... if true, only Landshoff forces when dot(rij,vij)<0
);
[Fwithimages,Wwithimages] = forceLandshoff(XYZfluidwithimages,vXYZfluidwithImages,Vfluidwithimages,configL);
wihtoutimages = ~isImagesfluid(isfluidwithimages);
Fland = Fwithimages(wihtoutimages,:); % F Landshoff forces
Wland = Wwithimages(wihtoutimages,:); % corresponding Virial Stress Tensor
% plot the Landshoff forces acting on particles
figure, hold on
plot3D(XYZfluid,'bo','markersize',2)
quiver3(XYZfluid(:,1),XYZfluid(:,2),XYZfluid(:,3),Fland(:,1),Fland(:,2),Fland(:,3),'r-')
axis equal, view(3)
figure, histogram(log10(vecnorm(Fland,2,2))) % magnitude of the force on a log10 scale
%% Interpolate Virial Stress along an xz vertical plane (as Han did)
nresolution = [1 1024 1024];
xw = xmean;
yw = linspace(ROIbox(2,1),ROIbox(2,2),nresolution(2));
zw = linspace(ROIbox(3,1),ROIbox(3,2),nresolution(3)); % vertical position used for interpolation
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
XYZgrid = [Xw(:),Yw(:),Zw(:)];
VXYZgrid = buildVerletList({XYZgrid, XYZfluidwithimages}, 2*h); % neighbors = fluid particles
W = kernelSPH(2*h, 'lucy', 3); % kernel expression
Wgrid = zeros(prod(nresolution(1:3)), 9, 'single');
for i = 1:9 % for all diagonal terms
Wgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages, Wwithimages(:,i), XYZgrid, VXYZgrid, W, mbead./rhoXYZwithImages(isfluidwithimages));
end
%% Interpolate the Velocity, extraction of the strain rate tensor
vxyzgrid = zeros(prod(nresolution(1:3)),3,'single');
vxyzgridabove = vxyzgrid;
XYZgridabove = XYZgrid;
XYZgridabove(:,1) = XYZgridabove(:,1) + h; % information along z
for i = 1:3
vxyzgrid(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgrid,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
vxyzgridabove(:,i) = interp3SPHVerlet(XYZfluidwithimages,vXYZfluidwithImages(:,i),XYZgridabove,VXYZgrid,W,mbead./rhoXYZwithImages(isfluidwithimages));
end
gxx = reshape( (vxyzgridabove(:,1) - vxyzgrid(:,1)) / h, nresolution(1:3));
gyx = reshape( (vxyzgridabove(:,2) - vxyzgrid(:,2)) / h, nresolution(1:3));
gzx = reshape( (vxyzgridabove(:,3) - vxyzgrid(:,3)) / h, nresolution(1:3));
[gxy,gxz] = gradient(reshape(vxyzgrid(:,1),nresolution([2 3])),Yw(2,1,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
[gyy,gyz] = gradient(reshape(vxyzgrid(:,2),nresolution([2 3])),Yw(2,1,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
[gzy,gzz] = gradient(reshape(vxyzgrid(:,3),nresolution([2 3])),Yw(2,1,1)-Xw(1,1,1),Zw(1,1,2)-Zw(1,1,1));
Ggrid = [gxx(:) gxy(:) gxz(:) gyx(:) gyy(:) gyz(:) gzx(:) gzy(:) gzz(:)]; % nine tensor components
% Velocity control
%{
figure, hold on
step = 16; [ix,iy,iz] = meshgrid(1,1:step:nresolution(2),1:step:nresolution(3)); indok = sub2ind(nresolution,ix,iy,iz);
quiver3(XYZgrid(indok,1),XYZgrid(indok,2),XYZgrid(indok,3),vxyzgrid(indok,1),vxyzgrid(indok,2),vxyzgrid(indok,3),'g-')
quiver3(XYZgridabove(:,1),XYZgridabove(:,2),XYZgridabove(:,3),vxyzgridabove(:,1),vxyzgridabove(:,2),vxyzgridabove(:,3),'g-')
%}
%% save
backfolder = 'D:\Yao';
if exist(backfolder,'dir')
prefetchresult = sprintf('YAO_yz_t%0.3g_x%0.3g',tframe*1e2,xmean*1e6);
save(fullfile(backfolder,prefetchresult))
end
%% Symmetric Strain
Straingrid = [gxx(:) 0.5*(gxy(:)+gyx(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gxy(:)+gyx(:)) gyy(:) 0.5*(gyz(:)+gzy(:)) 0.5*(gxz(:)+gzx(:)) 0.5*(gyz(:)+gzy(:)) gzz(:)];
formatfig(figure,'figname',sprintf('Yao_yz_SHEAR_t%d_x%d',round(tframe*100),round(xmean*1e6)))
hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\epsilon_{xx}','\epsilon_{xy}','\epsilon_{xz}','\epsilon_{yx}','\epsilon_{yy}','\epsilon_{yz}','\epsilon_{zx}','\epsilon_{zy}','\epsilon_{zz}'};
for i=1:9
subplot(hs2(i)), imagesc(flipud(reshape(Straingrid(:,i),nresolution([2 3]))'))
c=colorbar; if i==7, c.Label.String = 'strain rate (s^{-1})'; end
axis image
caxis([-5 5])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs2,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Stress
formatfig(figure,'figname',sprintf('Yao_yz_STRESS_t%d_x%d',round(tframe*100),round(xmean*1e6)))
hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\tau_{xx}','\tau_{xy}','\tau_{xz}','\tau_{yx}','\tau_{yy}','\tau_{yz}','\tau_{zx}','\tau_{zy}','\tau_{zz}'};
for i=1:9
subplot(hs1(i)), imagesc(flipud(reshape(Straingrid(:,i),nresolution([2 3]))'))
c=colorbar; if i==7, c.Label.String = 'stress (Pa)'; end
axis image
caxis([-0.1 0.8])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs1,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% Viscosity estimation
Egrid = 0.5*abs(Wgrid./Straingrid);
formatfig(figure,'figname',sprintf('Yao_yz_VISCO_t%d_x%d',round(tframe*100),round(xmean*1e6)))
hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
leg = {'\mu_{xx}','\mu_{xy}','\mu_{xz}','\mu_{yx}','\mu_{yy}','\mu_{yz}','\mu_{zx}','\mu_{zy}','\mu_{zz}'};
for i=1:9
subplot(hs1(i)), imagesc(flipud(reshape(Egrid(:,i),nresolution([2 3]))'))
c=colorbar; if i==7, c.Label.String = 'viscosity (Pa\cdot s)'; end
axis image
caxis([0 0.4])
title(leg{i},'fontsize',12,'visible','on')
end
set(hs1,'visible','off')
print_png(400,fullfile(backfolder,get(gcf,'filename')),'','',0,0,0)
%% plot stress results
close all
offdiag = [2 3 4 6 7 8];
figstresstensor = figure;
figsheartensor = figure;
figviscosity = figure;
formatfig(figstresstensor,'figname',sprintf('STRESS_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
formatfig(figsheartensor,'figname',sprintf('SHEAR_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
formatfig(figviscosity,'figname',sprintf('VISCO_xz_t%d_z%d',round(tframe*100),round(yw*1e6)))
figure(figstresstensor), hs1 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
figure(figsheartensor), hs2 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
figure(figviscosity), hs3 = subplots([1 1 1],[1 1 1],0.01,0.01); %,'alive',offdiag);
[Wmin,Wmax,Gmin,Gmax,Emin,Emax] = deal(+Inf,-Inf,+Inf,-Inf,+Inf,-Inf);
for i=1:9
figure(figstresstensor), subplot(hs1(i)), imagesc(flipud(reshape(Wgrid(:,i),nresolution([1 3]))')), colorbar
figure(figsheartensor), subplot(hs2(i)), imagesc(flipud(reshape(Ggrid(:,i),nresolution([1 3]))')), colorbar
figure(figviscosity), subplot(hs3(i)), imagesc(flipud(reshape(Egrid(:,i),nresolution([1 3]))')), colorbar
Wmin = min(Wmin,min(Wgrid(:,i)));
Wmax = max(Wmax,max(Wgrid(:,i)));
Gmin = min(Wmin,min(Ggrid(:,i)));
Gmax = max(Wmax,max(Ggrid(:,i)));
Emin = min(Wmin,min(Egrid(:,i)));
Emax = max(Wmax,max(Egrid(:,i)));
end
set(hs1,'visible','off')
set(hs2,'visible','off')
set(hs3,'visible','off')
for i=1:9
figure(figstresstensor), subplot(hs1(i)), clim([Wmin Wmax])
figure(figsheartensor), subplot(hs1(i)), clim([Gmin Gmax])
figure(figviscosity), subplot(hs1(i)), clim([Emin Emax])
end
set(hs3,'clim',[0.01 10 ])
% plot
figsumall = figure;
formatfig(figsumall,'figname',sprintf('STRESS_sumoff_t%d_z%d',round(tframe*100)))
title('all off-diagonal terms are added')
imagesc(flipud(reshape(sum(Wgrid(:,offdiag),2),nresolution([1 3]))'))
%% print
outputfolder = 'YAOresults';
if ~exist(outputfolder,'dir'), mkdir(outputfolder), end
figure(figstresstensor)
print_pdf(300,[get(figstresstensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figstresstensor,'filename')),'','',0,0,0)
figure(figsheartensor)
print_pdf(300,[get(figsheartensor,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figsheartensor,'filename')),'','',0,0,0)
figure(figviscosity)
print_pdf(300,[get(figviscosity,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figviscosity,'filename')),'','',0,0,0)
figure(figsumall)
print_pdf(300,[get(figsumall,'filename') '.pdf'],outputfolder,'nocheck') % PDF 300 dpi
print_png(300,fullfile(outputfolder,get(figsumall,'filename')),'','',0,0,0)
INTERP2SPH interpolates y at Xq,Yq using the 2D kernel W centered on centers
Syntax:
Vq = interp2SPH(X,y,Xq,Yq [,W,V])
Inputs:
centers : kx2 coordinates of the kernel centers
y : kxny values at X (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculatoin
Xq : array or matrix coordinates along X
Yq : array or matrix coordinates along Y
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
Output:
Vq : same size as Xq, with an additional dimension if y was an array
See also
interp3SPH, kernelSPH, packSPH
See also
function Vq = interp2SPH(centers,y,Xq,Yq,Zq,W,V)
% INTERP2SPH interpolates y at Xq,Yq using the 2D kernel W centered on centers
%
% Syntax:
% Vq = interp2SPH(X,y,Xq,Yq [,W,V])
%
% Inputs:
% centers : kx2 coordinates of the kernel centers
% y : kxny values at X (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculatoin
% Xq : array or matrix coordinates along X
% Yq : array or matrix coordinates along Y
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
%
% Output:
% Vq : same size as Xq, with an additional dimension if y was an array
%
% See also: interp3SPH, kernelSPH, packSPH
%
% 2023-02-20 | INRAE\Olivier Vitrac | rev.
% arg check
if nargin<1, centers = []; end
if nargin<2, y = []; end
if nargin<3, Xq = []; end
if nargin<4, Yq = []; end
if nargin<5, W = []; end
if nargin<6, V = []; end
[k,d] = size(centers);
[ky,ny] = size(y);
kv = length(V);
if k==0, error('please supply some centers'), end
if d~=2, error('2 dimensions (columns) are required'), end
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if ~isequal(size(Xq),size(Yq)) || ~isequal(size(Yq),size(Zq)), error('Xq,Yq and Zq do not have compatible sizes'), end
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
% main
sumW = cell(1,ny);
verbosity = numel(Xq)>1e4;
for i=1:k
% initialization if needed
if i==1
for iy=1:ny
sumW{iy} = zeros(size(Xq),class(Xq));
end
end
% interpolation
if verbosity, dispf('interpolate respectively to kernel %d of %d',i,k); end
R = sqrt( (Xq-centers(i,1)).^2 + (Yq-centers(i,2)).^2 );
for iy = 1:ny
sumW{iy} = sumW{iy} + y(i,iy) * V(i) * W(R);
end
end
% output
if ny==1
Vq = sumW{1};
else
Vq = cat(ndims(Xq)+1,sumW{:});
end
INTERP3SPH interpolates y at Xq,Yq,Zq using the 3D kernel W centered on centers
Syntax:
Vq = interp3SPH(X,y,Xq,Yq,Zq [,W,V])
Inputs:
centers : kx3 coordinates of the kernel centers
y : kxny values at X (m is the number of values associated with the same center)
[] (empty matrix) forces a uniform density calculatoin
Xq : array or matrix coordinates along X
Yq : array or matrix coordinates along Y
Zq : array or matrix coordinates along Z
W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
V : kx1 volume of the kernels (default=1)
[] (empty matrix) or scalar value forces uniform volumes (default =1)
Output:
function Vq = interp3SPH(centers,y,Xq,Yq,Zq,W,V)
% INTERP3SPH interpolates y at Xq,Yq,Zq using the 3D kernel W centered on centers
%
% Syntax:
% Vq = interp3SPH(X,y,Xq,Yq,Zq [,W,V])
%
% Inputs:
% centers : kx3 coordinates of the kernel centers
% y : kxny values at X (m is the number of values associated with the same center)
% [] (empty matrix) forces a uniform density calculatoin
% Xq : array or matrix coordinates along X
% Yq : array or matrix coordinates along Y
% Zq : array or matrix coordinates along Z
% W : kernel function @(r) <-- use kernelSPH() to supply a vectorized kernel
% V : kx1 volume of the kernels (default=1)
% [] (empty matrix) or scalar value forces uniform volumes (default =1)
%
% Output:
,% Vq : same size as Xq, with an additional dimension if y was an array
%
% See also: interp2SPH, kernelSPH, packSPH
%
% Example : interpolate the field x+2*y-3*z
%{
r = 0.5;
h = 2*r;
XYZ = packSPH(5,r);
W = kernelSPH(h,'lucy',3);
y = XYZ*[1;2;-3]; % arbitrary field to be interpolated x+2*y-3*z
nresolution = 50;
xg = linspace(min(XYZ(:,1))-h,max(XYZ(:,1))+h,nresolution);
yg = linspace(min(XYZ(:,2))-h,max(XYZ(:,2))+h,nresolution);
zg = linspace(min(XYZ(:,3))-h,max(XYZ(:,3))+h,nresolution);
[Xg,Yg,Zg] = meshgrid(xg,yg,zg);
Vg = interp3SPH(XYZ,y,Xg,Yg,Zg,W);
figure, hs= slice(Xg,Yg,Zg,Vg,1:3,1:3,[]); set(hs,'edgecolor','none','facealpha',0.5), axis equal
% comparison with standard scattered interpolation
F = scatteredInterpolant(XYZ(:,1),XYZ(:,2),XYZ(:,3),y);
Vg = F(Xg,Yg,Zg);
figure, hs= slice(Xg,Yg,Zg,Vg,1:3,1:3,[]); set(hs,'edgecolor','none','facealpha',0.5), axis equal
%}
%
% Example : calculate the density between the central bead and its closest neighbor
%{
r = 0.5;
h = 2*r;
XYZ = packSPH(5,r);
W = kernelSPH(h,'lucy',3);
[~,icentral] = min(sum((XYZ-mean(XYZ)).^2,2));
dcentral = sqrt(sum((XYZ-XYZ(icentral,:)).^2,2));
icontact = find( (dcentral>=2*r-0.0001) & (dcentral<=2*r+0.0001) );
[~,closest] = min(dcentral(icontact));
icontact = icontact(closest);
reducedcurvilinear = linspace(-2.5,2.5,100)';
curvilinear = reducedcurvilinear*norm(XYZ(icontact,:)-XYZ(icentral,:));
XYZg = XYZ(icentral,:) + reducedcurvilinear*(XYZ(icontact,:)-XYZ(icentral,:));
Vg = interp3SPH(XYZ,[],XYZg(:,1),XYZg(:,2),XYZg(:,3),W);
figure, plot(curvilinear,Vg), xlabel('distance to the central bead'), ylabel('density')
%}
% 2023-02-20 | INRAE\Olivier Vitrac | rev.
% arg check
if nargin<1, centers = []; end
if nargin<2, y = []; end
if nargin<3, Xq = []; end
if nargin<4, Yq = []; end
if nargin<5, Zq = []; end
if nargin<6, W = []; end
if nargin<7, V = []; end
[k,d] = size(centers);
[ky,ny] = size(y);
kv = length(V);
if k==0, error('please supply some centers'), end
if d~=3, error('3 dimensions (columns) are required'), end
if ky*ny==0, y = ones(k,1); ky=k; ny=1; end
if ky~=k, error('the number of y values (%d) does not match the number of kernels (%d)',ky,k), end
if ~isequal(size(Xq),size(Yq)) || ~isequal(size(Yq),size(Zq)), error('Xq,Yq and Zq do not have compatible sizes'), end
if kv==0, V=1; kv=1; end
if kv==1, V = ones(k,1)*V; kv=k; end
if kv~=k, error('the number of V values (%d) does not match the number of kernels (%d)',kv,k); end
% main
sumW = cell(1,ny);
verbosity = numel(Xq)>1e4;
for i=1:k
% initialization if needed
if i==1
for iy=1:ny
sumW{iy} = zeros(size(Xq),class(Xq));
end
end
% interpolation
if verbosity, dispf('interpolate respectively to kernel %d of %d',i,k); end
R = sqrt( (Xq-centers(i,1)).^2 + (Yq-centers(i,2)).^2 + (Zq-centers(i,3)).^2 );
for iy = 1:ny
sumW{iy} = sumW{iy} + y(i,iy) * V(i) * W(R);
end
end
% output
if ny==1
Vq = sumW{1};
else
Vq = cat(ndims(Xq)+1,sumW{:});
end
KERNELSPH return a SPH kernel
Syntax:
W = kernelSPH(h,type,d)
Inputs:
h : cutoff
type : kenel name (default = Lucy)
d : dimension
Output:
W : kernel function @(r)
Example
W = kernelSPH(1,'lucy',3)
Example
Example
See also
interp3SPH, interp2SPH, packSPH
function W = kernelSPH(h,type,d)
% KERNELSPH return a SPH kernel
%
% Syntax:
% W = kernelSPH(h,type,d)
% Inputs:
% h : cutoff
% type : kenel name (default = Lucy)
% d : dimension
% Output:
% W : kernel function @(r)
%
% Example:
% W = kernelSPH(1,'lucy',3)
%
%
% See also: interp3SPH, interp2SPH, packSPH
% 2023-02-20 | INRAE\Olivier Vitrac | rev.
% arg check
if nargin<1, h = []; end
if nargin<2, type = ''; end
if nargin<3, d = []; end
if isempty(h), error('Supply a value for h'), end
if isempty(type), type = 'lucy'; end
if ~ischar(type), error('type must be a char array'), end
if isempty(d), d = 3; end
if (d<2) || (d>3), error('d must be equal to 1, 2 or 3'), end
% main
switch lower(type)
case 'lucy'
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
case 'lucyder'
if d==3
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
assume(R%}
W = @(r) (relseif d==2
%{
syms R h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
assume(R%}
W = @(r) (rend
otherwise
error('the kernel ''%s'' is not implemented',type)
end
PACKSPH returns the HCP or FCC packing of siz spheres of radius r
Syntax:
W = packSPH(siz,r,typ)
Inputs:
siz : [5 5 5] number of spheres along x,y,z
if siz is a scalar, the same siz is applied to all dimensions [siz siz siz]
r : bead radius
typ : 'HCP' (default, period 2) or 'FCC' (period 3)
Output:
X : [size(1)xsize(2)xsize(3)] x 3 centers
Example
X = packSPH(5)
Example
Example
See also
interp3SPH, interp2SPH, kernelSPH
function X = packSPH(siz,r,typ)
% PACKSPH returns the HCP or FCC packing of siz spheres of radius r
%
% Syntax:
% W = packSPH(siz,r,typ)
% Inputs:
% siz : [5 5 5] number of spheres along x,y,z
% if siz is a scalar, the same siz is applied to all dimensions [siz siz siz]
% r : bead radius
% typ : 'HCP' (default, period 2) or 'FCC' (period 3)
% Output:
% X : [size(1)xsize(2)xsize(3)] x 3 centers
%
% Example:
% X = packSPH(5)
%
%
% See also: interp3SPH, interp2SPH, kernelSPH
% 2023-02-20 | INRAE\Olivier Vitrac | rev.
% arg check
rdefault = 0.5;
typdefault = 'HCP';
if nargin<1, siz = []; end
if nargin<2, r = []; end
if nargin<3, typ = ''; end
if numel(siz)==1, siz = [1,1,1]*siz; end
if isempty(siz) || numel(siz)~=3, error('siz must be 1x3 or 3x1 vector'); end
if isempty(r), r = rdefault; end
if isempty(typ), typ = typdefault; end
if ~ischar(typ), error('typ must be a char array'); end
switch upper(typ)
case 'HCP'
forceFCC = 0; % 0 for HCP and 1 for FCC
case 'FCC'
forceFCC = 1; % 0 for HCP and 1 for FCC
otherwise
error('valid packaging typ is ''HCP'' or ''FCC''')
end
% Lattice
[i,j,k] = ndgrid(0:(siz(1)-1),0:(siz(2)-1),0:(siz(3)-1)); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
X = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
Simple HCP (hexagonal closed pack) lattice
generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
%% Simple HCP (hexagonal closed pack) lattice
% generator from: https://en.wikipedia.org/wiki/Close-packing_of_equal_spheres
% INRAE\Olivier Vitrac, Han Chen - rev. 2023-02-20
% Define parameters
r = 0.5; % Radius of spheres
forceFCC = 0; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:2,0:2,0:2); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
for i = 1:size(centers, 1)
surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none');
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
%% Lucy kernel, note that s scales the kernel (different scaling in 2D and 3D)
syms h s W(r)
assume(h,{'real','positive'})
assume(r,{'real','positive'})
W(r) = piecewise(r=h,0); % kernel definition
s2D = solve( int(2*pi*r * W(r),r,0,Inf)==1,s); % scaling factor in 2D
s3D = solve( int(4*pi*r^2 * W(r),r,0,Inf)==1,s); % scaling factor in 3D
W2(r) = subs(W(r),s,s2D); % scaled kernel in 2D
W3(r) = subs(W(r),s,s3D); % scaled kernel in 3D
% radial position where the 3D kernel is equal to its average
r1 = solve(W3(r)==1,r,'Maxdegree',4); % all solutions (4)
% only the second root is real and positive
vpa(subs(r1,h,1))
r1 = simplify(r1(2));
% convert the kernel to a Matlab anonymous function
% function_handle with value:
% @(h,r)(1.0./h.^3.*(r./h-1.0).^3.*((r.*3.0)./h+1.0).*(-1.05e+2./1.6e+1))./pi
syms R
assume(R%% Kernel: numeric implementation
% single accuracy is used instead of double to reduce memory load
r = single(0.5);
h= single(2*r);
cutoff = @(r) single(r'-','linewidth',2), xlabel('r'), ylabel('kernel')
hold on, plot(interpleft(W(rplot),rplot,Wref),Wref,'ro','markerfacecolor',rgb('Crimson'))
line(r1expr(h)*[1;1;0],[0;1;1],'linewidth',1.5,'linestyle',':','color',rgb('deepskyblue'))
line([r h;r h],[0 0;1 1],'linewidth',1.5,'linestyle','--','color',rgb('coral'))
text(double(r),1,sprintf('\\leftarrow r_{bead}=%0.3g',r),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
text(double(h),1,sprintf('\\leftarrow h=%0.3g',h),'HorizontalAlignment','left','VerticalAlignment','top','fontsize',12,'color',rgb('Coral'))
% 3D field
nresolution = 200;
xw = single(linspace(min(centers(:,1))-h,max(centers(:,1))+h,nresolution));
yw = single(linspace(min(centers(:,2))-h,max(centers(:,2))+h,nresolution));
zw = single(linspace(min(centers(:,3))-h,max(centers(:,3))+h,nresolution));
[Xw,Yw,Zw] = meshgrid(xw,yw,zw);
% calculate the radial distance to the center of the ith sphere
R = @(i) sqrt( (Xw-centers(i,1)).^2 + (Yw-centers(i,2)).^2 + (Zw-centers(i,3)).^2 );
sumW = zeros(size(Xw),'single');
for i=1:size(centers,1)
dispf('evaluate field respective to kernel %d',i)
sumW = sumW + W(R(i));
end
% full domain
figure, isosurface(Xw,Yw,Zw,sumW,1), axis equal
% cut domain x>1.3
figure
sumWcut = sumW;
xcut = 1.3;
sumWcut(xw>xcut,:,:) = [];
[Xwcut,Ywcut,Zwcut] = deal(Xw,Yw,Zw);
Xwcut(xw>xcut,:,:) = [];
Ywcut(xw>xcut,:,:) = [];
Zwcut(xw>xcut,:,:) = [];
p1 = patch(isosurface(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor',rgb('tomato'),'EdgeColor','none');
p2 = patch(isocaps(Xwcut,Ywcut,Zwcut,sumWcut, 1),'FaceColor','interp','EdgeColor','none');
colormap(gray(100)), camlight left, camlight, lighting gouraud, view(-138,28), axis equal
% slice
figure, hs= slice(Xw,Yw,Zw,sumW,single(1:3),single(1:3),single([])); set(hs,'edgecolor','none','facealpha',0.5), axis equal
%% Calculate the field between two beads
r = 0.5; % Radius of spheres
forceFCC = 1; % 0 for HCP and 1 for FCC
% Lattice
[i,j,k] = ndgrid(0:4,0:4,0:4); % HCP is period 2, FCC is period 3
[i,j,k] = deal(i(:),j(:),k(:));
centers = [
2*i + mod(j+k,2) ...x
sqrt(3)*(j+mod(k,2)/3) + (mod(k,3)==2)*forceFCC...y
(2*sqrt(6)/3)*k ... z
]*r;
% Create sphere coordinates
[xs,ys,zs] = sphere(100);
% Translate spheres to close-packed positions
figure
ncenters = size(centers,1);
for i = 1:size(centers, 1)
hs(i) = surf(xs*r + centers(i,1), ys*r + centers(i,2), zs*r + centers(i,3),'FaceColor',rgb('deepskyblue'),'EdgeColor','none','facealpha',0.2);
hold on;
end
lighting gouraud
camlight left
shading interp
axis equal
% the most central bead and found the next neighbors (coordination number = 12 with HCP)
[~,icentral] = min(sum((centers-mean(centers)).^2,2));
set(hs(icentral),'facecolor',rgb('Crimson'),'FaceAlpha',1)
dcentral = sqrt(sum((centers-centers(icentral,:)).^2,2));
icontact = find( (dcentral>=2*r-0.0001) & (dcentral<=2*r+0.0001) );
ncontact = length(icontact);
set(hs(icontact),'facecolor',rgb('ForestGreen'),'FaceAlpha',1)
%% Averaged field between the icentral (red) bead and the icontact (green) one
nd = 1000;
d = linspace(-0.1*r,2*r+0.1*r,nd)'; % support
r = 0.5;
hlist = r*linspace(1.5,4,20);
nh = length(hlist);
sumW = zeros(nd,nh,ncontact);
for ih = 1:nh
h= hlist(ih);
cutoff = @(r) single(rfor j = 1:ncontact
xyz0 = centers(icentral,:); % red bead coordinates
xyz = centers(icontact(j),:); % green bead coordinated
direction = (xyz-xyz0)/norm(xyz-xyz0);
xyzd = xyz0 + direction .* d;
R = @(i) sqrt( sum((xyzd-centers(i,:)).^2,2));
for i=1:size(centers,1)
sumW(:,ih,j) = sumW(:,ih,j) + W(R(i));
end
end
end
figure('defaultAxesColorOrder',parula(nh))
leg = arrayfun(@(x) sprintf('h/r_{bead}=%0.3g',x),hlist/r,'UniformOutput',false);
hp = plot(d/r,mean(sumW,3),'-','linewidth',3);
legend(hp,leg,'location','eastoutside','fontsize',10,'box','off')
formatax(gca,'fontsize',12)
xlabel('r/r_{bead}','fontsize',16)
ylabel('density','fontsize',16)