Package particleShear
Provide 2D simulations of frictional and non-frictional, crosslinked and non-crosslinked spheres under oscillatory shear.
The package provides the tools necessary to simulate ensembles of discrete spherical particles as described in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. In addition, the particles can be crosslinked permanently between neighbors to constitute particles
Package written by Thomas Braschler (thomas.braschler@gmail.com)
Expand source code
"""Provide 2D simulations of frictional and non-frictional, crosslinked and non-crosslinked
spheres under oscillatory shear.
The package provides the tools necessary to simulate ensembles of discrete spherical particles as described in
Otsuki, M. and H. Hayakawa, Discontinuous change of
shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. In addition, the particles
can be crosslinked permanently between neighbors to constitute particles\n
Package written by Thomas Braschler (thomas.braschler@gmail.com)
"""
__all__ = ["Force_register","StressTensorEvaluation","Graphical_output_configuration","neighbor_relation",
"Point","PointLeesEdwards",
"Circle","CircleBasicElasticity",
"CircleFrictionElasticity","CircleMass","CircleMassNeighbors",
"CanvasPoints","CanvasPointsBasicElasticity","CanvasPointsBasicElasticityLeesEdwards",
"CanvasPointsFrictionElasticityLeesEdwards","CanvasPointsMass","CanvasPointsShear","CanvasPointsNeighbors",
"elastic_force_law_plateau","elastic_force_law","PlateauConfiguration","distance_transform_plateau",
"Sphere","SphereLeesEdwards","SphereFriction","SphereFrictionLeesEdwards",
"Ensemble",
"EnsembleFriction",
"r_estimate","EnsembleLeesEdwards","EnsembleFrictionLeesEdwards",
"particle_shear_model_parameters",
"neighbor_relation_linkable",
"CanvasPointsLinkable", "SphereLinkable", "SphereLinkableAdjustableInterfaceStrength",
"EnsembleLinkable", "EnsembleCompactParticles",
"EnsembleCompactParticlesAdjustableInterfaceStrength",
"EnsembleCompactParticlesAdjustableInterfaceStrengthFromModelParameters",
"EnsembleCompactParticlesFromModelParameters",
"SphereLinkable", "SphereLinkableAdjustableInterfaceStrength",
"elastic_force_law_tensile","elastic_force_law_tensile_exponential",
"TensileConfiguration",
"OscillatoryShearExperiment",
"OscillatorySimulation",
"OscillatorySimulationFragmentation","Simulation_dermal_filler_rheology",
"Simulation_interlocking_rheology",
"EvaluationHandler",
"EvaluationHandlerPlotter",
"doParticleShearSimulation",
"doParticleShearSimulationSeries"
]
# Basic tools
from particleShearBase import *
from particleShearLinkableObjects import *
from particleShearObjects import *
from particleShearSimulation import *
from particleShearRunSimulation import *
Functions
def EnsembleCompactParticlesAdjustableInterfaceStrengthFromModelParameters(size_x=500, size_y=500, N=100, packing_fraction=1, mu=1, Young_modulus_spheres=10000, density=50, bimodal_factor=1.4, cut_lines=5, do_permanent_links=True, theTk=False, do_pre_equilibration=True, relative_viscosity=0.01, central_repulsion_coefficient=0, anticipated_amplitude=0.1, relative_transversal_link_strength=1, avoid_horizontal_angle_degree=0, avoid_height_spanning_particles=False, interface_reenforcement_central=1, interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=True, cut_top_bottom=True, doCutByTriangulation=True, remove_link_fraction=0, edge_fuzziness=0, doDrawing=True, do_debug=False)
-
Expand source code
def EnsembleCompactParticlesAdjustableInterfaceStrengthFromModelParameters(size_x=500,size_y=500,N=100,packing_fraction=1,mu=1, Young_modulus_spheres=10000,density=50,bimodal_factor=1.4, cut_lines=5,do_permanent_links=True,theTk=False,do_pre_equilibration=True, relative_viscosity=0.01,central_repulsion_coefficient=0,anticipated_amplitude=0.1, relative_transversal_link_strength=1,avoid_horizontal_angle_degree=0, avoid_height_spanning_particles=False, interface_reenforcement_central=1,interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=True,cut_top_bottom=True, doCutByTriangulation=True,remove_link_fraction=0,edge_fuzziness=0,doDrawing=True, do_debug=False): # First, some logics: with permanent links but insufficient tools to cut them, height spanning particles cannot be avoided if avoid_height_spanning_particles and ((doCutByTriangulation and cut_lines <= 1) or ((not doCutByTriangulation) and cut_Lines <= 0)): avoid_height_spanning_particles = False print("Cannot avoid height spanning particles for bulk ensembles") theCanvas = False if doDrawing and not theTk: theTk = Tk() if doDrawing: theCanvas = Canvas(theTk, width=size_x, height=size_y) theCanvas.pack() theTk.title("Interface: tangential=" + str(interface_reenforcement_tangential) + " central=" + \ str(interface_reenforcement_central) + " A=" + str(anticipated_amplitude) + " mu=" + str(mu)) model = particle_shear_model_parameters(size_x=size_x, size_y=size_y, N=N, packing_fraction=packing_fraction, Young_modulus_spheres=Young_modulus_spheres,density=density, bimodal_factor=1) print("Characteristic time constant [s] ",model.time_constant) final_dt = 0.1 * model.time_constant/bimodal_factor print("dt from model before adapation",final_dt) if mu<=1 or relative_transversal_link_strength<1: final_dt=final_dt/2 if anticipated_amplitude<=0.05: final_dt=final_dt/2 if mu <=0.1 or relative_transversal_link_strength<0.1: final_dt = final_dt / 2 if anticipated_amplitude<=0.005: final_dt = final_dt / 2 if anticipated_amplitude>=0.5: final_dt = final_dt / 2 dt_max = 0.25 * model.time_constant/bimodal_factor # Special delicate situations with little viscous components, either central or tangential, and thus # tendency for the ensemble to oscillate if relative_viscosity<=0.1 and mu<0.1 or relative_transversal_link_strength<0.1 or relative_transversal_link_strength>5: dt_max = 0.125 * model.time_constant/bimodal_factor print("Relative viscosity below 0.1, adapting dt_max to ",dt_max) if relative_viscosity<0.01 and mu<0.01 or relative_transversal_link_strength<0.01: dt_max = 0.04 * model.time_constant/bimodal_factor print("Relative viscosity below 0.01, adapting dt_max to", dt_max) if final_dt > dt_max: final_dt=dt_max print("dt from model after adaptaion for friction (mu=", mu,") and amplitude (A=",anticipated_amplitude,"):",final_dt) if relative_viscosity>1: dt_max=dt_max/relative_viscosity # Here, the situation is expressed in terms of interface re-enforcement. So since the # permanent_ratio_central and permanent_ratio_tangential # parameters will be smaller than 1, we need to artificially re-enforce the corresponding k, nu, k_t and nu_t parameters # Then, only the values for the permanent links will be multiplied with the smaller constants, and # so they reach their intended base level in the permanent links. In the non-permanent (interfacial links), # they will remain higher nu_for_ensemble=relative_viscosity*model.k * model.time_constant # Switch to decide whether viscosity constant should remain globally constant or vary the same way as the elastic ones if not keep_viscosity_coefficients_constant: nu_for_ensemble=nu_for_ensemble*interface_reenforcement_central nu_t_for_ensemble = relative_viscosity*model.k * model.time_constant*relative_transversal_link_strength # Switch to decide whether viscosity constant should remain globally constant or vary the same way as the elastic ones if not keep_viscosity_coefficients_constant: nu_t_for_ensemble = relative_viscosity * model.k * model.time_constant * \ relative_transversal_link_strength * interface_reenforcement_tangential theEnsemble = EnsembleCompactParticlesAdjustableInterfaceStrength(size_x, size_y, N, packing_fraction, theCanvas, doDrawing=doDrawing, k=model.k*interface_reenforcement_central, nu= nu_for_ensemble, m=model.m, bimodal_factor=bimodal_factor, k_t=model.k*relative_transversal_link_strength*interface_reenforcement_tangential, nu_t=nu_t_for_ensemble , mu=mu, dt=final_dt, dt_max= dt_max, theTkSimulation=theTk, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, permanent_ratio_central=1/interface_reenforcement_central, permanent_ratio_tangential=1/interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant ) print("Turning on enhanced central repulsion, coefficient = ",central_repulsion_coefficient) theEnsemble.set_central_repulsion_coefficient(central_repulsion_coefficient) print("Avoiding full height spanning particles:", avoid_height_spanning_particles) theEnsemble.avoid_height_spanning_particles = avoid_height_spanning_particles # Record this parameter for file saving theEnsemble.model=model reduction_factor=1 if(do_debug): reduction_factor=10 if do_pre_equilibration: theEnsemble.dt=final_dt mdt=0.25*model.time_constant/bimodal_factor if relative_viscosity <= 0.1: mdt = 0.2 * model.time_constant/bimodal_factor theEnsemble.dt_max=mdt if theEnsemble.packing_fraction > 2: theEnsemble.dt_max = theEnsemble.dt_max / 2 if theEnsemble.permanent_ratio_central==1: theEnsemble.free_pre_equilibration(N=int(300/reduction_factor)) else: theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) if anticipated_amplitude<=0.01 or relative_transversal_link_strength<0.1: if theEnsemble.permanent_ratio_central == 1: theEnsemble.free_pre_equilibration(N=int(300/reduction_factor)) else: theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) theEnsemble.dt = final_dt theEnsemble.dt_max = dt_max if theEnsemble.permanent_ratio_central == 1: theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) else: theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) if anticipated_amplitude <= 0.01 and theEnsemble.permanent_ratio_central==1: theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) if anticipated_amplitude <= 0.001 and theEnsemble.permanent_ratio_central==1: theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) if do_permanent_links: theEnsemble.makeAllLinksPermanent() if cut_top_bottom: theEnsemble.cutTopBottomEdge() theEnsemble.cut_top_bottom=True if(doCutByTriangulation): print("Preparing particles by triangulation; relative fuzziness on edges=",edge_fuzziness) theEnsemble.cutByTriangulation(cut_lines,edge_fuzziness=edge_fuzziness) else: print("Preparing particles by straight line cut") for i in range(cut_lines): theEnsemble.cutRandomLine() max_height=0 if theEnsemble.avoid_height_spanning_particles: max_height=theEnsemble.maximal_particle_height() while theEnsemble.avoid_height_spanning_particles and max_height > theEnsemble.size_y*0.9: print("Discarded ensemble with full height spanning particles due to max height",max_height) print("\twith total particle count", len(theEnsemble.particles)) theEnsemble.particles=[] # Reset the particle registry in the particles theEnsemble.makeAllLinksPermanent() if cut_top_bottom: theEnsemble.cutTopBottomEdge() if doCutByTriangulation: print("Preparing particles by triangulation; relative fuzziness on edges=", edge_fuzziness) theEnsemble.cutByTriangulation(cut_lines,edge_fuzziness=edge_fuzziness) else: print("Preparing particles by straight line cut") for i in range(cut_lines): theEnsemble.cutRandomLine() max_height = theEnsemble.maximal_particle_height() if remove_link_fraction>0: theEnsemble.remove_non_essential_links(max_fraction_to_remove=remove_link_fraction,physical_neighbors_only=True) theEnsemble.join_isolated_spheres() theEnsemble.dt_max = min(theEnsemble.dt*2,theEnsemble.dt_max) theEnsemble.dt = theEnsemble.dt if relative_transversal_link_strength>5 : theEnsemble.dt_max=theEnsemble.dt_max/2 theEnsemble.free_pre_equilibration(N=int(300/reduction_factor)) if anticipated_amplitude < 0.1: print("Extra equilibration for A<0.1") old_mu = theEnsemble.mu theEnsemble.mu = 0 theEnsemble.free_pre_equilibration(N=int(150/reduction_factor)) theEnsemble.mu = old_mu theEnsemble.free_pre_equilibration(N=int(150/reduction_factor)) if anticipated_amplitude <= 0.01: print("Extra equilibration for A<=0.1") theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) old_mu = theEnsemble.mu theEnsemble.mu = 0 theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) theEnsemble.mu = old_mu theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) if anticipated_amplitude <= 0.001: print("Extra equilibration for A<=0.001") theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) old_mu = theEnsemble.mu theEnsemble.mu = 0 theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) theEnsemble.mu = old_mu theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) if anticipated_amplitude <= 0.0001: print("Extra equilibration for A<=0.0001") theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) old_mu = theEnsemble.mu theEnsemble.mu = 0 theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) theEnsemble.mu = old_mu theEnsemble.free_pre_equilibration(N=int(100/reduction_factor)) if anticipated_amplitude <= 0.001 and (not (interface_reenforcement_tangential==1) or not (interface_reenforcement_central==1)): theEnsemble.dt_max = theEnsemble.dt_max / 3 print("Extra equilibration for A<=0.001") theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) old_mu = theEnsemble.mu theEnsemble.mu = 0 theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) theEnsemble.mu = old_mu theEnsemble.free_pre_equilibration(N=int(200/reduction_factor)) return theEnsemble
def EnsembleCompactParticlesFromModelParameters(size_x=500, size_y=500, N=100, packing_fraction=1, mu=1, Young_modulus_spheres=10000, density=50, bimodal_factor=1.4, cut_lines=5, do_permanent_links=True, theTk=False, do_pre_equilibration=True, relative_viscosity=0.01, central_repulsion_coefficient=0, anticipated_amplitude=0.1, relative_transversal_link_strength=1, avoid_horizontal_angle_degree=0, avoid_height_spanning_particles=False, doCutByTriangulation=True, doDrawing=True, do_debug=False)
-
Expand source code
def EnsembleCompactParticlesFromModelParameters(size_x=500,size_y=500,N=100,packing_fraction=1,mu=1, Young_modulus_spheres=10000,density=50,bimodal_factor=1.4, cut_lines=5,do_permanent_links=True,theTk=False,do_pre_equilibration=True, relative_viscosity=0.01,central_repulsion_coefficient=0,anticipated_amplitude=0.1, relative_transversal_link_strength=1,avoid_horizontal_angle_degree=0, avoid_height_spanning_particles=False,doCutByTriangulation=True,doDrawing=True, do_debug=False): # First, some logics: with permanent links but insufficient tools to cut them, height spanning particles cannot be avoided if avoid_height_spanning_particles and ((doCutByTriangulation and cut_lines<=1) or ((not doCutByTriangulation) and cut_Lines<=0)): avoid_height_spanning_particles=False print("Cannot avoid height spanning particles for bulk ensembles") theCanvas=False if doDrawing and not theTk: theTk = Tk() if doDrawing: theCanvas = Canvas(theTk, width=size_x, height=size_y) theCanvas.pack() model = particle_shear_model_parameters(size_x=size_x, size_y=size_y, N=N, packing_fraction=packing_fraction, Young_modulus_spheres=Young_modulus_spheres,density=density, bimodal_factor=1) final_dt = 0.1 * model.time_constant/bimodal_factor print("dt from model before adapation",final_dt) if mu<=1 or relative_transversal_link_strength<1: final_dt=final_dt/2 if anticipated_amplitude<=0.05: final_dt=final_dt/2 if mu <=0.1 or relative_transversal_link_strength<0.1: final_dt = final_dt / 2 if anticipated_amplitude<=0.005: final_dt = final_dt / 2 if anticipated_amplitude>=0.5: final_dt = final_dt / 2 dt_max = 0.25 * model.time_constant/bimodal_factor # Special delicate situations with little viscous components, either central or tangential, and thus # tendency for the ensemble to oscillate if relative_viscosity<=0.1 and mu<0.1 or relative_transversal_link_strength<0.1 or relative_transversal_link_strength>5: dt_max = 0.125 * model.time_constant/bimodal_factor print("Relative viscosity below 0.1, adapting dt_max to ",dt_max) if relative_viscosity<0.01 and mu<0.01 or relative_transversal_link_strength<0.01: dt_max = 0.04 * model.time_constant/bimodal_factor print("Relative viscosity below 0.01, adapting dt_max to", dt_max) if final_dt > dt_max: final_dt=dt_max print("dt from model after adaptaion for friction (mu=", mu,") and amplitude (A=",anticipated_amplitude,"):",final_dt) if relative_viscosity>1: dt_max=dt_max/relative_viscosity theEnsemble = EnsembleCompactParticles(size_x, size_y, N, packing_fraction, theCanvas, doDrawing=doDrawing, k=model.k, nu= relative_viscosity*model.k * model.time_constant, m=model.m, bimodal_factor=bimodal_factor, k_t=model.k*relative_transversal_link_strength, nu_t= relative_viscosity*model.k * model.time_constant*relative_transversal_link_strength, mu=mu, dt=final_dt, dt_max= dt_max, theTkSimulation=theTk, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree) print("Turning on enhanced central repulsion, coefficient = ",central_repulsion_coefficient) theEnsemble.set_central_repulsion_coefficient(central_repulsion_coefficient) print("Avoiding full height spanning particles:",avoid_height_spanning_particles) theEnsemble.avoid_height_spanning_particles=avoid_height_spanning_particles # Record this parameter for file saving theEnsemble.model=model if do_pre_equilibration: theEnsemble.dt=final_dt mdt=0.25*model.time_constant/bimodal_factor if relative_viscosity <= 0.1: mdt = 0.2 * model.time_constant/bimodal_factor theEnsemble.dt_max=mdt N_step=300 if(do_debug): N_step=30 theEnsemble.free_pre_equilibration(N=N_step) if anticipated_amplitude<=0.01 or relative_transversal_link_strength<0.1: theEnsemble.free_pre_equilibration(N=N_step) theEnsemble.dt = final_dt theEnsemble.dt_max = dt_max N_step=200 if (do_debug): N_step = 20 theEnsemble.free_pre_equilibration(N=N_step) if anticipated_amplitude <= 0.01: theEnsemble.free_pre_equilibration(N=N_step) if anticipated_amplitude <= 0.001: theEnsemble.free_pre_equilibration(N=N_step) if do_permanent_links: theEnsemble.makeAllLinksPermanent() theEnsemble.cutTopBottomEdge() if (doCutByTriangulation): print("Preparing particles by triangulation") theEnsemble.cutByTriangulation(cut_lines) else: print("Preparing particles by straight line cut") for i in range(cut_lines): theEnsemble.cutRandomLine() max_height=0 if theEnsemble.avoid_height_spanning_particles: max_height=theEnsemble.maximal_particle_height() while theEnsemble.avoid_height_spanning_particles and max_height > theEnsemble.size_y*0.9: theEnsemble.makeAllLinksPermanent() theEnsemble.cutTopBottomEdge() if (doCutByTriangulation): print("Preparing particles by triangulation") theEnsemble.cutByTriangulation(cut_lines) else: print("Preparing particles by straight line cut") for i in range(cut_lines): theEnsemble.cutRandomLine() if theEnsemble.avoid_height_spanning_particles: max_height = theEnsemble.maximal_particle_height() theEnsemble.join_isolated_spheres() theEnsemble.dt_max = min(theEnsemble.dt*2,theEnsemble.dt_max) theEnsemble.dt = theEnsemble.dt if relative_transversal_link_strength>5: theEnsemble.dt_max=theEnsemble.dt_max/2 theEnsemble.free_pre_equilibration() return theEnsemble
def distance_transform_plateau(d, plateau_begin, plateau_end, relative_plateau_slope)
-
Helper function to transform the separation distance between particles to produce a force profile with a plateau, as typical for spongy materials
- **parameters** <code>d</code> Center-center distance of the interacting particles <code>plateau\_begin</code> Beginning of the plateau (smaller distance) <code>plateau\_end</code> End of the plateau (larger distance) <code>relative\_plateau\_slope</code> Relative slope change on the plateau (1 = linear relation)
Expand source code
def distance_transform_plateau(d, plateau_begin, plateau_end, relative_plateau_slope): """Helper function to transform the separation distance between particles to produce a force profile with a plateau, as typical for spongy materials - **parameters**\n `d` Center-center distance of the interacting particles\n `plateau_begin` Beginning of the plateau (smaller distance)\n `plateau_end` End of the plateau (larger distance)\n `relative_plateau_slope` Relative slope change on the plateau (1 = linear relation)\n """ if d>=plateau_end: return d if d>=plateau_begin: return plateau_end + (d-plateau_end)*relative_plateau_slope return d/plateau_begin*(plateau_end + (plateau_begin-plateau_end)*relative_plateau_slope)
def doParticleShearSimulation(root_folder='', theAmplitude=0.2, theMu=0.01, do_permanent_links=True, cut_lines=5, Young_modulus_spheres=8000, N=150, packing_fraction=1.5, density=50, bimodal_factor=1.4, relative_viscosity=0.1, relative_transversal_link_strength=1, relative_frequency=0.025, avoid_horizontal_angle_degree=15, interface_reenforcement_central=1, interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=False, avoid_height_spanning_particles=True, cut_top_bottom=False, saveOutputImages=False, imageFileType='jpg', remove_link_fraction=0, edge_fuzziness=0, central_repulsion_coefficient=1, doCutByTriangulation=True, doDrawing=True, saveData=False, saveStressTensorData=True, plotStress=True, relative_y_scale_force=100000000.0, pre_periods=2, periods=3, post_periods=1, cool_factor=0.5)
-
Expand source code
def doParticleShearSimulation(root_folder="",theAmplitude=0.2,theMu=0.01,do_permanent_links=True, cut_lines=5,Young_modulus_spheres=8000, N=150,packing_fraction=1.5,density=50,bimodal_factor=1.4, relative_viscosity=0.1,relative_transversal_link_strength=1,relative_frequency=0.025, avoid_horizontal_angle_degree=15,interface_reenforcement_central=1,interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=False,avoid_height_spanning_particles=True,cut_top_bottom=False, saveOutputImages=False,imageFileType ="jpg", remove_link_fraction=0,edge_fuzziness=0, central_repulsion_coefficient=1, doCutByTriangulation=True,doDrawing=True,saveData=False, saveStressTensorData=True, plotStress=True,relative_y_scale_force = 1e8, pre_periods=2,periods=3,post_periods=1,cool_factor=0.5): #if theAmplitude < 0.005: # relative_y_scale_force = relative_y_scale_force * theAmplitude / 0.005 theSimulation = Simulation_interlocking_rheology( root_folder, do_permanent_links=do_permanent_links, cut_lines=cut_lines, N=N, packing_fraction=packing_fraction, density=density, bimodal_factor=bimodal_factor, amplitude=theAmplitude, Young_modulus_spheres=Young_modulus_spheres, mu=theMu, relative_y_scale_force=relative_y_scale_force, relative_frequency=relative_frequency, relative_viscosity=relative_viscosity, relative_transversal_link_strength=relative_transversal_link_strength, central_repulsion_coefficient=central_repulsion_coefficient, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, interface_reenforcement_central=interface_reenforcement_central, interface_reenforcement_tangential=interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant, avoid_height_spanning_particles=avoid_height_spanning_particles, cut_top_bottom=cut_top_bottom, doCutByTriangulation=doCutByTriangulation, remove_link_fraction=remove_link_fraction, edge_fuzziness=edge_fuzziness, doDrawing=doDrawing, saveData=saveData ) theSimulation.function_call = theSimulation.function_call + \ "\n\ttheSimulation.baseline_pre_periods = " + str(pre_periods) +\ "\n\ttheSimulation.baseline_post_periods = " + str(post_periods)+ \ "\n\ttheSimulation.saveOutputImages = " + str(saveOutputImages)+ \ "\n\ttheSimulation.imageFileType = " + str(imageFileType) + \ "\n\ttheSimulation.runSimulation(periods="+str(periods)+", cool_factor="+\ str(cool_factor)+")" theSimulation.baseline_pre_periods = pre_periods theSimulation.baseline_post_periods = post_periods theSimulation.saveOutputImages = saveOutputImages theSimulation.imageFileType = imageFileType theSimulation.saveStressTensorData=saveStressTensorData return(theSimulation.runSimulation(periods=periods, cool_factor=cool_factor,plotStress=plotStress))
def doParticleShearSimulationSeries(root_folder='', amplitudes=[0.2], mu=[0.01], cut_lines=5, Young_modulus_spheres=8000, do_bulk_control=False, do_uncrosslinked_control=False, do_non_frictional_control=False, N_repetitions=6, N=150, packing_fraction=1.5, density=50, bimodal_factor=1.4, relative_viscosity=0.1, relative_transversal_link_strength=1, relative_frequency=0.025, avoid_horizontal_angle_degree=15, interface_reenforcement_central=1, interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=False, avoid_height_spanning_particles=True, cut_top_bottom=False, doCutByTriangulation=True, saveOutputImages=False, imageFileType='jpg', remove_link_fraction=0, edge_fuzziness=0, central_repulsion_coefficient=1, doDrawing=True, saveData=False, saveStressTensorData=True, plotStress=True, pre_periods=2, periods=3, post_periods=1, cool_factor=0.5)
-
Expand source code
def doParticleShearSimulationSeries(root_folder="",amplitudes=[0.2],mu=[0.01],cut_lines=5,Young_modulus_spheres=8000, do_bulk_control=False,do_uncrosslinked_control=False, do_non_frictional_control=False,N_repetitions=6, N=150,packing_fraction=1.5, density=50, bimodal_factor=1.4, relative_viscosity=0.1,relative_transversal_link_strength=1, relative_frequency=0.025, avoid_horizontal_angle_degree=15, interface_reenforcement_central=1,interface_reenforcement_tangential=1, keep_viscosity_coefficients_constant=False, avoid_height_spanning_particles=True,cut_top_bottom=False, doCutByTriangulation=True, saveOutputImages=False,imageFileType = "jpg", remove_link_fraction=0, edge_fuzziness=0,central_repulsion_coefficient=1, doDrawing=True,saveData=False,saveStressTensorData=True,plotStress=True, pre_periods=2, periods=3, post_periods=1, cool_factor=0.5): for i in range(N_repetitions): for theAmplitude in amplitudes: for theMu in mu: # Control: Bulk (this is the same as the biomaterial call, but cut_lines=0 if do_bulk_control: doParticleShearSimulation(root_folder,theAmplitude=theAmplitude, theMu=theMu,do_permanent_links=True,cut_lines=0, Young_modulus_spheres=Young_modulus_spheres, N=N,packing_fraction=packing_fraction, density=density, bimodal_factor=bimodal_factor, relative_viscosity=relative_viscosity, relative_transversal_link_strength=relative_transversal_link_strength, relative_frequency=relative_frequency, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, interface_reenforcement_central=interface_reenforcement_central, interface_reenforcement_tangential=interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant, avoid_height_spanning_particles=avoid_height_spanning_particles,cut_top_bottom=cut_top_bottom, saveOutputImages=saveOutputImages,imageFileType =imageFileType, remove_link_fraction=remove_link_fraction, edge_fuzziness=edge_fuzziness, central_repulsion_coefficient=central_repulsion_coefficient, doCutByTriangulation=doCutByTriangulation, doDrawing=doDrawing, saveData=saveData, saveStressTensorData=saveStressTensorData, plotStress=plotStress, pre_periods=pre_periods,periods=periods,post_periods=post_periods,cool_factor=cool_factor) # Actual biomaterial: crosslinked but cut into particles doParticleShearSimulation(root_folder, theAmplitude=theAmplitude, theMu=theMu, do_permanent_links=True, cut_lines=cut_lines, Young_modulus_spheres=Young_modulus_spheres, N=N, packing_fraction=packing_fraction, density=density, bimodal_factor=bimodal_factor, relative_viscosity=relative_viscosity, relative_transversal_link_strength=relative_transversal_link_strength, relative_frequency=relative_frequency, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, interface_reenforcement_central=interface_reenforcement_central, interface_reenforcement_tangential=interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant, avoid_height_spanning_particles=avoid_height_spanning_particles, cut_top_bottom=cut_top_bottom, saveOutputImages=saveOutputImages,imageFileType =imageFileType, remove_link_fraction=remove_link_fraction, edge_fuzziness=edge_fuzziness, central_repulsion_coefficient=central_repulsion_coefficient, doCutByTriangulation=doCutByTriangulation, doDrawing=doDrawing, saveData=saveData, saveStressTensorData=saveStressTensorData, plotStress=plotStress, pre_periods=pre_periods, periods=periods, post_periods=post_periods, cool_factor=cool_factor) # Control: uncrosslinked spheres only if do_uncrosslinked_control: doParticleShearSimulation(root_folder, theAmplitude=theAmplitude, theMu=theMu, do_permanent_links=False, cut_lines=0, Young_modulus_spheres=Young_modulus_spheres, N=N, packing_fraction=packing_fraction, density=density, bimodal_factor=bimodal_factor, relative_viscosity=relative_viscosity, relative_transversal_link_strength=relative_transversal_link_strength, relative_frequency=relative_frequency, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, interface_reenforcement_central=interface_reenforcement_central, interface_reenforcement_tangential=interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant, avoid_height_spanning_particles=avoid_height_spanning_particles, cut_top_bottom=cut_top_bottom, saveOutputImages=saveOutputImages,imageFileType =imageFileType, remove_link_fraction=remove_link_fraction, edge_fuzziness=edge_fuzziness, central_repulsion_coefficient=central_repulsion_coefficient, doCutByTriangulation=doCutByTriangulation, doDrawing=doDrawing, saveData=saveData, saveStressTensorData=saveStressTensorData, plotStress=plotStress, pre_periods=pre_periods, periods=periods, post_periods=post_periods, cool_factor=cool_factor) # Control: non-frictional if do_non_frictional_control: doParticleShearSimulation(root_folder, theAmplitude=theAmplitude, theMu=0, do_permanent_links=True, cut_lines=cut_lines, Young_modulus_spheres=Young_modulus_spheres, N=N, packing_fraction=packing_fraction, density=density, bimodal_factor=bimodal_factor, relative_viscosity=relative_viscosity, relative_transversal_link_strength=relative_transversal_link_strength, relative_frequency=relative_frequency, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, interface_reenforcement_central=interface_reenforcement_central, interface_reenforcement_tangential=interface_reenforcement_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant, avoid_height_spanning_particles=avoid_height_spanning_particles, cut_top_bottom=cut_top_bottom, saveOutputImages=saveOutputImages,imageFileType =imageFileType, remove_link_fraction=remove_link_fraction, edge_fuzziness=edge_fuzziness, central_repulsion_coefficient=central_repulsion_coefficient, doCutByTriangulation=doCutByTriangulation, doDrawing=doDrawing, saveData=saveData, saveStressTensorData=saveStressTensorData, plotStress=plotStress, pre_periods=pre_periods, periods=periods, post_periods=post_periods, cool_factor=cool_factor)
def elastic_force_law(d, d0, k, central_repulsion_coefficient)
-
Default elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials)
Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction
-
parameters
d
Center-center distance of the interacting particlesd0
Equilibrium center-center distancek
Spring constantcentral_repulsion_coefficient
Possibility to have increased central repulsion at very closed distances.
Expand source code
def elastic_force_law(d, d0, k, central_repulsion_coefficient): """Default elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials) Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction\n - **parameters**\n `d` Center-center distance of the interacting particles\n `d0` Equilibrium center-center distance\n `k` Spring constant\n `central_repulsion_coefficient` Possibility to have increased central repulsion at very closed distances.\n """ if d > d0: return 0 linear_force = -k * (1 - central_repulsion_coefficient) * (d0 - d) nonlinear_addition = -k * central_repulsion_coefficient * d0 * ( d0 / max(d, d0 / 1000) - 1) return linear_force + nonlinear_addition
-
def elastic_force_law_plateau(d, d0, k, central_repulsion_coefficient)
-
Alternate elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials)
-
parameters
d
Center-center distance of the interacting particlesd0
Equilibrium center-center distancek
Spring constantcentral_repulsion_coefficient
Possibility to have increased central repulsion at very closed distances.
Expand source code
def elastic_force_law_plateau(d, d0, k, central_repulsion_coefficient): """Alternate elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials) - **parameters**\n `d` Center-center distance of the interacting particles\n `d0` Equilibrium center-center distance\n `k` Spring constant\n `central_repulsion_coefficient` Possibility to have increased central repulsion at very closed distances.\n """ if d > d0: return 0 plateau_begin = PlateauConfiguration.relative_plateau_begin * d0 plateau_end = PlateauConfiguration.relative_plateau_end * d0 d = distance_transform_plateau(d,plateau_begin,plateau_end, PlateauConfiguration.relative_plateau_slope) linear_force = -k * (1 - central_repulsion_coefficient) * (d0 - d) nonlinear_addition = -k * central_repulsion_coefficient * d0 * ( d0 / max(d, d0 / 1000) - 1) return linear_force + nonlinear_addition
-
def elastic_force_law_tensile(d, d0, k)
-
Expand source code
def elastic_force_law_tensile(d, d0, k): return -k * (d0 - d)
def elastic_force_law_tensile_exponential(d, d0, k)
-
Expand source code
def elastic_force_law_tensile_exponential(d, d0, k): return k*(1-exp(-(d-d0)/d0/TensileConfiguration.d_exponential))*d0*TensileConfiguration.d_exponential
def r_estimate(size_x, size_y, packing_fraction, N, bimodal_upper=1.2, bimodal_lower=0.8)
-
Average radius
TWe find the average radius from the
packing_fraction
, the available areasize_x
timessize_y
and the desired number of spheres. Indeed, collectively theN
spheres should occupy a theoretical area ofsize_x
timessize_y
divided byN
. In a real situation, they will of course occupy a smaller area due to compression. However, as thepacking_fraction
, also referred to as notional phase volume (Evans, I.D. and A. Lips, Concentration-Dependence of the Linear Elastic Behavior of Model Microgel Dispersions. Journal of the Chemical Society-Faraday Transactions, 1990. 86(20): p. 3413-3417.) is defined relative to the fully expanded state, this does not matter. If the spheres had a homogeneous size, the relevant radius would besqrt(size_x * size_y * packing_fraction / N / math.pi)
Now, to avoid crystallization, a bimodal distribution of sphere radii needs to be used Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. Still, the area occupied by the spheres should match size_x * size_y * packing_fraction. In analogy to Otsuki et al., We chose our bimodal distribution at radii +/- 20 percent of the nominal radius, as the sum of the areas of a circle of 1.2 times r + the area of a circle of 0.8 times r is slightly bigger than twice the area of a circle of radius r, we correct for this. The correction is however minor, its just 4 percent.
Expand source code
def r_estimate(size_x,size_y,packing_fraction,N,bimodal_upper=1.2,bimodal_lower=0.8): """Average radius TWe find the average radius from the `packing_fraction`, the available area `size_x` times `size_y` and the desired number of spheres. Indeed, collectively the `N` spheres should occupy a theoretical area of `size_x` times `size_y` divided by `N`. In a real situation, they will of course occupy a smaller area due to compression. However, as the `packing_fraction`, also referred to as notional phase volume (Evans, I.D. and A. Lips, Concentration-Dependence of the Linear Elastic Behavior of Model Microgel Dispersions. Journal of the Chemical Society-Faraday Transactions, 1990. 86(20): p. 3413-3417.) is defined relative to the fully expanded state, this does not matter. If the spheres had a homogeneous size, the relevant radius would be \n sqrt(size_x * size_y * packing_fraction / N / math.pi) \n Now, to avoid crystallization, a bimodal distribution of sphere radii needs to be used Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. Still, the area occupied by the spheres should match size_x * size_y * packing_fraction. In analogy to Otsuki et al., We chose our bimodal distribution at radii +/- 20 percent of the nominal radius, as the sum of the areas of a circle of 1.2 times r + the area of a circle of 0.8 times r is slightly bigger than twice the area of a circle of radius r, we correct for this. The correction is however minor, its just 4 percent. """ return math.sqrt(size_x * size_y * packing_fraction / N / math.pi / (bimodal_upper * bimodal_upper + \ bimodal_lower * bimodal_lower) * 2)
Classes
class CanvasPoints (size_x, size_y, theCanvas=False, doDrawing=False)
-
Canvas for placing objects of type
Point
(or subclasses thereof).This class maintains a list of objects of type
Point
or derived and provides basic functions to handle this collection. These objects are generically referred to as spheres and are stored inCanvasPoints.sphereList
.Class defined in subpackage particleShearBase
Initialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or not
Expand source code
class CanvasPoints(): """Canvas for placing objects of type `particleShear.Point` (or subclasses thereof). This class maintains a list of objects of type `particleShear.Point` or derived and provides basic functions to handle this collection. These objects are generically referred to as spheres and are stored in `particleShear.CanvasPoints.sphereList`.\n Class defined in subpackage particleShearBase""" def __init__(self, size_x, size_y, theCanvas=False, doDrawing=False): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not""" self.theCanvas = theCanvas """tkinter canvas for drawing the simulation objects""" self.size_x = size_x """Width of area to be used in pixels = micrometers for the simulation Generally, but not strictly necessarily, this is also the width of `particleShear.CanvasPoints.theCanvas`""" self.size_y = size_y """Height of area to be used in pixels = micrometers for the simulation Generally, but not strictly necessarily, this is also the height of `particleShear.CanvasPoints.theCanvas`""" self.doDrawing = doDrawing """Flag to indicate whether graphical output should be produced or not (boolean)""" self.sphereList = [] """list of objects of type `particleShear.Point` or derived. These objects are referred to as spheres""" self.graphical_output_configuration=Graphical_output_configuration() # Default graphical output configuration """`particleShear.Graphical_output_configuration` to set options how the spheres and their neighboring relations are displayed""" self.set_graphical_output_configuration(self.graphical_output_configuration) # To propagate it also to the spheres def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.CanvasPoints`""" self.graphical_output_configuration=graphical_output_configuration for theSphere in self.sphereList: theSphere.set_graphical_output_configuration(self.graphical_output_configuration) def initiateGraphics(self): """Initiate drawing of the constituent sphere objects by invokation of `particleShear.Circle.initiate_drawing`. For this, the objects need to derive from `particleShear.Circle` or otherwise provide a initiate_drawing method. \n This method is defined in class `particleShear.CanvasPoints`""" if(self.doDrawing): for theSphere in self.sphereList: theSphere.doDrawing=True theSphere.initiate_drawing() # For recording incoming force reported by the spheres def correct_linear_drift(self): """Subtract average linear speed from all constituent sphere objects (of type `particleShear.Point` or subclasses thereof) This method is defined in class `particleShear.CanvasPoints`""" l=0 vx=0 vy=0 for theSphere in self.sphereList: vx=vx+theSphere.xspeed vy=vy+theSphere.yspeed l=l+1 if l>0: vx=vx/l vy=vy/l for theSphere in self.sphereList: theSphere.xspeed=theSphere.xspeed-vx theSphere.yspeed = theSphere.yspeed - vy def particle_info(self): """Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files""" return "CanvasPoints: Geometrical operations only" # To be overriden by subclasses with special boundary conditions def canMove(self,theSphere): """Return boolean to indicate whether a given sphere (object of type `particleShear.Point` or subclass thereof) to move on the current canvas\n To be overriden by subclasses of `particleShear.CanvasPoints` with special boundary conditions This method is defined in class `particleShear.CanvasPoints`""" return True def do_linear_acceleration(self, dt): """Instruct mobile spheres (objects of type `particleShear.Point` or subclasses thereof) to change their speed depending on the forces acting on them. The objects need to to derive from `particleShear.CircleMass` or otherwise possess a do_linear_acceleration method This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: if self.canMove(theSphere): theSphere.do_linear_acceleration(dt) def cool(self, f=0.8): """Cool the sphere objects (of type `particleShear.Point` or subclass thereof) by a factor of f; for f=0 the objects stop, for f=1 this has no effect. For this function to work, the objects need to derive from `particleShear.PointLeesEdwards` or otherwise present a cool method This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.cool(f) def move(self, dt=1): """Move the spheres (of type `particleShear.Point` or derived) during a time period dt (in s) This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.move(dt) def boundary_conditions(self): """Invoke `particleShear.Point.boundary_conditions` routine on each of the spheres (of type `particleShear.Point` or subclass) This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.boundary_conditions(self.size_x, self.size_y) def movableSphereList(self): """Return the list of mobile spheres (list of objects of type `particleShear.Point` or subclasses thereof); for these mobile objects, `particleShear.CanvasPoints.canMove` returns True This method is defined in class `particleShear.CanvasPoints`""" movable_spheres = [] for theSphere in self.sphereList: if (self.canMove(theSphere)): movable_spheres.append(theSphere) return movable_spheres def regular_sphere_distribution(self,rand_fraction=0.5): """Distributes the spheres (objects derived from `particleShear.Point` class or subclasses) in a regular rectangular grid on the area defined by `particleShear.CanvasPoints.size_x` * `particleShear.CanvasPoints.size_y` - **parameters**\n `rand_fraction` allows local random fluctuations of the sphere positions around the grid positions\n This method is defined in class `particleShear.CanvasPoints`""" N_spheres = len(self.sphereList) N_rows = int(round(math.sqrt(N_spheres * self.size_y / self.size_x))) total_length_to_distribute = N_rows * (self.size_x) length_per_sphere = total_length_to_distribute / N_spheres y_spacing = self.size_y / (N_rows + 1) total_length_to_distribute = total_length_to_distribute + length_per_sphere ni = 0 for theSphere in self.sphereList: current_length_pos = length_per_sphere * ni row_index = int(math.floor(current_length_pos / total_length_to_distribute * N_rows)) col_index = \ (current_length_pos - row_index * total_length_to_distribute / N_rows) / length_per_sphere theSphere.y = row_index * length_per_sphere+(random.random()-0.5)*rand_fraction*length_per_sphere theSphere.x = col_index * y_spacing+(random.random()-0.5)*rand_fraction*y_spacing ni = ni + 1
Subclasses
- particleShearBase.CanvasPointsMass.CanvasPointsMass
Instance variables
var doDrawing
-
Flag to indicate whether graphical output should be produced or not (boolean)
var graphical_output_configuration
-
Graphical_output_configuration
to set options how the spheres and their neighboring relations are displayed var size_x
-
Width of area to be used in pixels = micrometers for the simulation
Generally, but not strictly necessarily, this is also the width of
CanvasPoints.theCanvas
var size_y
-
Height of area to be used in pixels = micrometers for the simulation
Generally, but not strictly necessarily, this is also the height of
CanvasPoints.theCanvas
var sphereList
-
list of objects of type
Point
or derived. These objects are referred to as spheres var theCanvas
-
tkinter canvas for drawing the simulation objects
Methods
def boundary_conditions(self)
-
Invoke
Point.boundary_conditions()
routine on each of the spheres (of typePoint
or subclass)This method is defined in class
CanvasPoints
Expand source code
def boundary_conditions(self): """Invoke `particleShear.Point.boundary_conditions` routine on each of the spheres (of type `particleShear.Point` or subclass) This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.boundary_conditions(self.size_x, self.size_y)
def canMove(self, theSphere)
-
Return boolean to indicate whether a given sphere (object of type
Point
or subclass thereof) to move on the current canvasTo be overriden by subclasses of
CanvasPoints
with special boundary conditionsThis method is defined in class
CanvasPoints
Expand source code
def canMove(self,theSphere): """Return boolean to indicate whether a given sphere (object of type `particleShear.Point` or subclass thereof) to move on the current canvas\n To be overriden by subclasses of `particleShear.CanvasPoints` with special boundary conditions This method is defined in class `particleShear.CanvasPoints`""" return True
def cool(self, f=0.8)
-
Cool the sphere objects (of type
Point
or subclass thereof) by a factor of f; for f=0 the objects stop, for f=1 this has no effect. For this function to work, the objects need to derive fromPointLeesEdwards
or otherwise present a cool methodThis method is defined in class
CanvasPoints
Expand source code
def cool(self, f=0.8): """Cool the sphere objects (of type `particleShear.Point` or subclass thereof) by a factor of f; for f=0 the objects stop, for f=1 this has no effect. For this function to work, the objects need to derive from `particleShear.PointLeesEdwards` or otherwise present a cool method This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.cool(f)
def correct_linear_drift(self)
-
Subtract average linear speed from all constituent sphere objects (of type
Point
or subclasses thereof)This method is defined in class
CanvasPoints
Expand source code
def correct_linear_drift(self): """Subtract average linear speed from all constituent sphere objects (of type `particleShear.Point` or subclasses thereof) This method is defined in class `particleShear.CanvasPoints`""" l=0 vx=0 vy=0 for theSphere in self.sphereList: vx=vx+theSphere.xspeed vy=vy+theSphere.yspeed l=l+1 if l>0: vx=vx/l vy=vy/l for theSphere in self.sphereList: theSphere.xspeed=theSphere.xspeed-vx theSphere.yspeed = theSphere.yspeed - vy
def do_linear_acceleration(self, dt)
-
Instruct mobile spheres (objects of type
Point
or subclasses thereof) to change their speed depending on the forces acting on them. The objects need to to derive fromCircleMass
or otherwise possess a do_linear_acceleration methodThis method is defined in class
CanvasPoints
Expand source code
def do_linear_acceleration(self, dt): """Instruct mobile spheres (objects of type `particleShear.Point` or subclasses thereof) to change their speed depending on the forces acting on them. The objects need to to derive from `particleShear.CircleMass` or otherwise possess a do_linear_acceleration method This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: if self.canMove(theSphere): theSphere.do_linear_acceleration(dt)
def initiateGraphics(self)
-
Initiate drawing of the constituent sphere objects by invokation of
Circle.initiate_drawing()
.For this, the objects need to derive from
Circle
or otherwise provide a initiate_drawing method.This method is defined in class
CanvasPoints
Expand source code
def initiateGraphics(self): """Initiate drawing of the constituent sphere objects by invokation of `particleShear.Circle.initiate_drawing`. For this, the objects need to derive from `particleShear.Circle` or otherwise provide a initiate_drawing method. \n This method is defined in class `particleShear.CanvasPoints`""" if(self.doDrawing): for theSphere in self.sphereList: theSphere.doDrawing=True theSphere.initiate_drawing()
def movableSphereList(self)
-
Return the list of mobile spheres (list of objects of type
Point
or subclasses thereof); for these mobile objects,CanvasPoints.canMove()
returns TrueThis method is defined in class
CanvasPoints
Expand source code
def movableSphereList(self): """Return the list of mobile spheres (list of objects of type `particleShear.Point` or subclasses thereof); for these mobile objects, `particleShear.CanvasPoints.canMove` returns True This method is defined in class `particleShear.CanvasPoints`""" movable_spheres = [] for theSphere in self.sphereList: if (self.canMove(theSphere)): movable_spheres.append(theSphere) return movable_spheres
def move(self, dt=1)
-
Move the spheres (of type
Point
or derived) during a time period dt (in s)This method is defined in class
CanvasPoints
Expand source code
def move(self, dt=1): """Move the spheres (of type `particleShear.Point` or derived) during a time period dt (in s) This method is defined in class `particleShear.CanvasPoints`""" for theSphere in self.sphereList: theSphere.move(dt)
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): """Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files""" return "CanvasPoints: Geometrical operations only"
def regular_sphere_distribution(self, rand_fraction=0.5)
-
Distributes the spheres (objects derived from
Point
class or subclasses) in a regular rectangular grid on the area defined byCanvasPoints.size_x
*CanvasPoints.size_y
-
parameters
rand_fraction
allows local random fluctuations of the sphere positions around the grid positionsThis method is defined in class
CanvasPoints
Expand source code
def regular_sphere_distribution(self,rand_fraction=0.5): """Distributes the spheres (objects derived from `particleShear.Point` class or subclasses) in a regular rectangular grid on the area defined by `particleShear.CanvasPoints.size_x` * `particleShear.CanvasPoints.size_y` - **parameters**\n `rand_fraction` allows local random fluctuations of the sphere positions around the grid positions\n This method is defined in class `particleShear.CanvasPoints`""" N_spheres = len(self.sphereList) N_rows = int(round(math.sqrt(N_spheres * self.size_y / self.size_x))) total_length_to_distribute = N_rows * (self.size_x) length_per_sphere = total_length_to_distribute / N_spheres y_spacing = self.size_y / (N_rows + 1) total_length_to_distribute = total_length_to_distribute + length_per_sphere ni = 0 for theSphere in self.sphereList: current_length_pos = length_per_sphere * ni row_index = int(math.floor(current_length_pos / total_length_to_distribute * N_rows)) col_index = \ (current_length_pos - row_index * total_length_to_distribute / N_rows) / length_per_sphere theSphere.y = row_index * length_per_sphere+(random.random()-0.5)*rand_fraction*length_per_sphere theSphere.x = col_index * y_spacing+(random.random()-0.5)*rand_fraction*y_spacing ni = ni + 1
-
def set_graphical_output_configuration(self, graphical_output_configuration)
-
Set new
Graphical_output_configuration
This method is defined in class
CanvasPoints
Expand source code
def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.CanvasPoints`""" self.graphical_output_configuration=graphical_output_configuration for theSphere in self.sphereList: theSphere.set_graphical_output_configuration(self.graphical_output_configuration)
-
class CanvasPointsBasicElasticity (size_x, size_y, theCanvas=False, doDrawing=False, k=1, nu=0.01, m=1)
-
Canvas for placing
CircleBasicElasticity
objects (referred to as spheres). This class maintains a list of objects of typeCircleBasicElasticity
or derived and adds basic functions to handle this collectionInitialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depth
Expand source code
class CanvasPointsBasicElasticity(CanvasPointsShear): """Canvas for placing `particleShear.CircleBasicElasticity` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.CircleBasicElasticity` or derived and adds basic functions to handle this collection""" def __init__(self, size_x, size_y, theCanvas=False, doDrawing=False, k=1, nu=0.01,m=1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `k` Spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the mass per unit of depth in mg/m of depth""" self.k = k """Central spring constant in (mg/s^2)/m of depth""" self.nu = nu """Central viscosity constant in (mg/s)/m of depth""" self.central_repulsion_coefficient=0 """Central repulsion coefficient to help avoid spheres being able to fully penetrate and cross each other. The central_repulsion_coeffiecient is chosen from 0 (only linear law) to 1 (highest proportion of central repulsion). When central_repulsion_coefficient>0, there is a 1/x contribution to induce very strong repulsion when the spheres approach total compression and thus to avoid crossing of the spheres. For small compressions, even if central_repulsion_coefficient>0, the equations are chosen to give a dF/dx law with a slope of -k when the spheres just touch. See `particleShear.CircleBasicElasticity.get_elastic_force` for details""" super(CanvasPointsBasicElasticity,self).__init__(size_x=size_x,size_y=size_y,theCanvas=theCanvas,doDrawing=doDrawing,m=m) def particle_info(self): return "CanvasPointsBasicElasticity: Basic elastic and neighbor behaviour" def elastic_force(self): """Let all spheres calculate the central elastic force acting on them; this will check for contact and so is more time consuming than `particleShear.CanvasPointsBasicElasticity.elastic_force_from_neighbors` This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].elastic_force(self.sphereList[sphereIndex2], self.k) def elastic_force_from_neighbors(self): """Let all spheres calculate the central elastic force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsBasicElasticity.elastic_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].elastic_force_from_neighbors(self.k) def central_viscous_force(self): """Let all spheres calculate the central viscous force acting on them. This will check for contact and so is more time consuming than `particleShear.CanvasPointsBasicElasticity.central_viscous_force_from_neighbors`.\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].central_viscous_force(self.sphereList[sphereIndex2], self.nu) def central_viscous_force_from_neighbors(self): """Let all spheres calculate the central viscous force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsBasicElasticity.central_viscous_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].central_viscous_force_from_neighbors(self.nu) def mechanical_simulation_step(self, cool_factor=0.97, dt=1): """ Perform full mechanical simulation step. This corresponds to the events associated with time advancing by dt. For this class, this includes resetting all forces to zero, calculation and summation of central and tangential forces on all particles, and calculation of the resulting acceleration, rotational acceleration, linear and rotational movement, including Lees-Edwards boundary conditions and adaption to change of shear rate The step can also include cooling (translational and rotational) by cool_factor. Forces are registered in the `particleShear.CanvasPointsMass.force_register`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.mechanical_simulation_step_calculate_forces() self.record_total_particle_forces() self.mechanical_simulation_step_calculate_acceleration(cool_factor=cool_factor,dt=dt) self.mechanical_simulation_step_calculate_movement(dt=dt) def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.reset_force_register() self.test_neighbor_relation() self.elastic_force_from_neighbors() self.central_viscous_force_from_neighbors() def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): """Calculate the accelerations resulting from the forces This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.do_linear_acceleration(dt) self.cool(cool_factor) def mechanical_simulation_step_calculate_movement(self,dt=1): """ Calculate the displacement resulting from the sphere movement This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.move(dt) self.boundary_conditions() self.t = self.t + dt if self.applyingShear: self.shear = self.shear + self.shear_rate * dt def mechanical_relaxation(self,N=1000,cool_factor=0.97,dt=1,theTk=False,StressTensorEvaluator=False): """N times repeated `particleShear.CanvasPointsBasicElasticity.mechanical_simulation_step` with graphical update This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for i in range(N): self.mechanical_simulation_step(cool_factor,dt) if self.doDrawing: theTk.update() if(StressTensorEvaluator): self.evaluate_stress_tensors(StressTensorEvaluator) def set_central_repulsion_coefficient(self,central_repulsion_coefficient=0): """Transmit the `particleShear.CanvasPointsBasicElasticity.central_repulsion_coefficient` to the spheres. This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.central_repulsion_coefficient=central_repulsion_coefficient for theSphere in self.sphereList: theSphere.central_repulsion_coefficient=central_repulsion_coefficient
Ancestors
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearObjects.Ensemble.Ensemble
Instance variables
var central_repulsion_coefficient
-
Central repulsion coefficient to help avoid spheres being able to fully penetrate and cross each other.
The central_repulsion_coeffiecient is chosen from 0 (only linear law) to 1 (highest proportion of central repulsion). When central_repulsion_coefficient>0, there is a 1/x contribution to induce very strong repulsion when the spheres approach total compression and thus to avoid crossing of the spheres. For small compressions, even if central_repulsion_coefficient>0, the equations are chosen to give a dF/dx law with a slope of -k when the spheres just touch. See
CircleBasicElasticity.get_elastic_force()
for details var k
-
Central spring constant in (mg/s^2)/m of depth
var nu
-
Central viscosity constant in (mg/s)/m of depth
Methods
def central_viscous_force(self)
-
Let all spheres calculate the central viscous force acting on them.
This will check for contact and so is more time consuming than
CanvasPointsBasicElasticity.central_viscous_force_from_neighbors()
.This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def central_viscous_force(self): """Let all spheres calculate the central viscous force acting on them. This will check for contact and so is more time consuming than `particleShear.CanvasPointsBasicElasticity.central_viscous_force_from_neighbors`.\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].central_viscous_force(self.sphereList[sphereIndex2], self.nu)
def central_viscous_force_from_neighbors(self)
-
Let all spheres calculate the central viscous force acting on from their known neighbors.
This is quicker than
CanvasPointsBasicElasticity.central_viscous_force()
but necessitates the neighbor relations to be already established viaCanvasPointsNeighbors.test_neighbor_relation()
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def central_viscous_force_from_neighbors(self): """Let all spheres calculate the central viscous force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsBasicElasticity.central_viscous_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].central_viscous_force_from_neighbors(self.nu)
def elastic_force(self)
-
Let all spheres calculate the central elastic force acting on them; this will check for contact and so is more time consuming than
CanvasPointsBasicElasticity.elastic_force_from_neighbors()
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def elastic_force(self): """Let all spheres calculate the central elastic force acting on them; this will check for contact and so is more time consuming than `particleShear.CanvasPointsBasicElasticity.elastic_force_from_neighbors` This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].elastic_force(self.sphereList[sphereIndex2], self.k)
def elastic_force_from_neighbors(self)
-
Let all spheres calculate the central elastic force acting on from their known neighbors.
This is quicker than
CanvasPointsBasicElasticity.elastic_force()
but necessitates the neighbor relations to be already established viaCanvasPointsNeighbors.test_neighbor_relation()
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def elastic_force_from_neighbors(self): """Let all spheres calculate the central elastic force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsBasicElasticity.elastic_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].elastic_force_from_neighbors(self.k)
def mechanical_relaxation(self, N=1000, cool_factor=0.97, dt=1, theTk=False, StressTensorEvaluator=False)
-
N times repeated
CanvasPointsBasicElasticity.mechanical_simulation_step()
with graphical updateThis method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_relaxation(self,N=1000,cool_factor=0.97,dt=1,theTk=False,StressTensorEvaluator=False): """N times repeated `particleShear.CanvasPointsBasicElasticity.mechanical_simulation_step` with graphical update This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" for i in range(N): self.mechanical_simulation_step(cool_factor,dt) if self.doDrawing: theTk.update() if(StressTensorEvaluator): self.evaluate_stress_tensors(StressTensorEvaluator)
def mechanical_simulation_step(self, cool_factor=0.97, dt=1)
-
Perform full mechanical simulation step.
This corresponds to the events associated with time advancing by dt. For this class, this includes resetting all forces to zero, calculation and summation of central and tangential forces on all particles, and calculation of the resulting acceleration, rotational acceleration, linear and rotational movement, including Lees-Edwards boundary conditions and adaption to change of shear rate The step can also include cooling (translational and rotational) by cool_factor. Forces are registered in the
CanvasPointsMass.force_register
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step(self, cool_factor=0.97, dt=1): """ Perform full mechanical simulation step. This corresponds to the events associated with time advancing by dt. For this class, this includes resetting all forces to zero, calculation and summation of central and tangential forces on all particles, and calculation of the resulting acceleration, rotational acceleration, linear and rotational movement, including Lees-Edwards boundary conditions and adaption to change of shear rate The step can also include cooling (translational and rotational) by cool_factor. Forces are registered in the `particleShear.CanvasPointsMass.force_register`\n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.mechanical_simulation_step_calculate_forces() self.record_total_particle_forces() self.mechanical_simulation_step_calculate_acceleration(cool_factor=cool_factor,dt=dt) self.mechanical_simulation_step_calculate_movement(dt=dt)
def mechanical_simulation_step_calculate_acceleration(self, cool_factor=0.97, dt=1)
-
Calculate the accelerations resulting from the forces
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): """Calculate the accelerations resulting from the forces This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.do_linear_acceleration(dt) self.cool(cool_factor)
def mechanical_simulation_step_calculate_forces(self)
-
Calculate the forces acting on the spheres
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.reset_force_register() self.test_neighbor_relation() self.elastic_force_from_neighbors() self.central_viscous_force_from_neighbors()
def mechanical_simulation_step_calculate_movement(self, dt=1)
-
Calculate the displacement resulting from the sphere movement
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_movement(self,dt=1): """ Calculate the displacement resulting from the sphere movement This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.move(dt) self.boundary_conditions() self.t = self.t + dt if self.applyingShear: self.shear = self.shear + self.shear_rate * dt
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvasPointsBasicElasticity: Basic elastic and neighbor behaviour"
def set_central_repulsion_coefficient(self, central_repulsion_coefficient=0)
-
Transmit the
CanvasPointsBasicElasticity.central_repulsion_coefficient
to the spheres.This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def set_central_repulsion_coefficient(self,central_repulsion_coefficient=0): """Transmit the `particleShear.CanvasPointsBasicElasticity.central_repulsion_coefficient` to the spheres. This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.central_repulsion_coefficient=central_repulsion_coefficient for theSphere in self.sphereList: theSphere.central_repulsion_coefficient=central_repulsion_coefficient
-
class CanvasPointsBasicElasticityLeesEdwards (size_x, size_y, theCanvas=False, doDrawing=False, k=1, nu=0.01, m=1)
-
Canvas for placing
CircleBasicElasticity
objects (referred to as spheres). This class maintains a list of objects of typeCircleBasicElasticity
or derived and adds functions specific to Lees-Edwards boundary conditionsInitialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depth
Expand source code
class CanvasPointsBasicElasticityLeesEdwards(CanvasPointsBasicElasticity): """Canvas for placing `particleShear.CircleBasicElasticity` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.CircleBasicElasticity` or derived and adds functions specific to Lees-Edwards boundary conditions""" def __init__(self, size_x, size_y, theCanvas=False, doDrawing=False, k=1, nu=0.01,m=1): super(CanvasPointsBasicElasticityLeesEdwards,self).__init__(size_x=size_x,size_y=size_y, theCanvas=theCanvas,doDrawing=doDrawing,k=k,nu=nu,m=m) def particle_info(self): return "CanvasPointsBasicElasticityLeesEdwards: Basic elastic properties and Lees-Edwards transmission of " \ "force across boundary conditions" def setShear(self,theShear): """Set new shear value and transmit to the constituent spheres via `particleShear.CanvasPointsBasicElasticityLeesEdwards.setShearInSpheres` This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" self.shear=theShear self.setShearInSpheres() def setShearInSpheres(self): """Propage the shear value to the constituent spheres. This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.shear=self.shear def setShearRate(self, theShearRate): """set a new value for the shear rate and adjust movement of the constituent spheres correspondingly. This method is designed to respect the a priori deformation under the Lees-Edwards boundary conditions via `particleShear.CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change`\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" delta_shear_rate=theShearRate-self.shear_rate self.adjust_sphere_speed_to_shear_rate_change(delta_shear_rate) self.shear_rate = theShearRate self.setShearRateInSpheres() def adjust_sphere_speed_to_shear_rate_change(self,delta_shear_rate): """ Adjust sphere speed via SLLOD stabilized equations of motion. The idea here is that when changing shear rate, we change the speed of the particles so that they conserve their relation compared to the local average speed. This avoids things like shock-wave propagation due to very large speed amplitude changes; similarly, the rotation rate is adjusted to reflect local average vorticity under shear\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.xspeed=theSphere.xspeed+(theSphere.y-self.size_y/2)*delta_shear_rate if hasattr(theSphere,"omega"): # http://www.mate.tue.nl/mate/pdfs/9614.pdf theSphere.omega=theSphere.omega-delta_shear_rate/2 def setShearRateInSpheres(self): """Propagate shear rate to the spheres. To allow for SLLOD-type adjustment to the changed shear rate, `particleShear.CanvasPointsBasicElasticityLeesEdwards.setShearRate` is used.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.shear_rate = self.shear_rate def record_individual_internal_force(self,target,source,force_vector): """Record individual force in the central `particleShear.CanvasPointsMass.force_register`. If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal force.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" if not self.canMove(target): return if self.canMove(source): # Consider only physical pairs physical_distance = target.d_euclidian(source) simulation_distance = target.d(source) if physical_distance == simulation_distance: self.force_register.record_individual_internal_force(target,source,force_vector) else: self.force_register.record_external_force(target, force_vector) else: self.force_register.record_external_force(target,force_vector) def record_individual_internal_torque(self,target,source,moment): """Record individual torque in the central `particleShear.CanvasPointsMass.force_register`. If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal torque.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" if not self.canMove(target): return if self.canMove(source): # Consider only physical pairs physical_distance = target.d_euclidian(source) simulation_distance = target.d(source) if physical_distance == simulation_distance: self.force_register.record_individual_internal_torque(target,source,moment) else: self.force_register.record_external_torque(target, moment) else: self.force_register.record_external_torque(target,moment)
Ancestors
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearObjects.EnsembleLeesEdwards.EnsembleLeesEdwards
Methods
def adjust_sphere_speed_to_shear_rate_change(self, delta_shear_rate)
-
Adjust sphere speed via SLLOD stabilized equations of motion.
The idea here is that when changing shear rate, we change the speed of the particles so that they conserve their relation compared to the local average speed. This avoids things like shock-wave propagation due to very large speed amplitude changes; similarly, the rotation rate is adjusted to reflect local average vorticity under shear
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def adjust_sphere_speed_to_shear_rate_change(self,delta_shear_rate): """ Adjust sphere speed via SLLOD stabilized equations of motion. The idea here is that when changing shear rate, we change the speed of the particles so that they conserve their relation compared to the local average speed. This avoids things like shock-wave propagation due to very large speed amplitude changes; similarly, the rotation rate is adjusted to reflect local average vorticity under shear\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.xspeed=theSphere.xspeed+(theSphere.y-self.size_y/2)*delta_shear_rate if hasattr(theSphere,"omega"): # http://www.mate.tue.nl/mate/pdfs/9614.pdf theSphere.omega=theSphere.omega-delta_shear_rate/2
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvasPointsBasicElasticityLeesEdwards: Basic elastic properties and Lees-Edwards transmission of " \ "force across boundary conditions"
def record_individual_internal_force(self, target, source, force_vector)
-
Record individual force in the central
CanvasPointsMass.force_register
.If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal force.
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def record_individual_internal_force(self,target,source,force_vector): """Record individual force in the central `particleShear.CanvasPointsMass.force_register`. If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal force.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" if not self.canMove(target): return if self.canMove(source): # Consider only physical pairs physical_distance = target.d_euclidian(source) simulation_distance = target.d(source) if physical_distance == simulation_distance: self.force_register.record_individual_internal_force(target,source,force_vector) else: self.force_register.record_external_force(target, force_vector) else: self.force_register.record_external_force(target,force_vector)
def record_individual_internal_torque(self, target, source, moment)
-
Record individual torque in the central
CanvasPointsMass.force_register
.If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal torque.
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def record_individual_internal_torque(self,target,source,moment): """Record individual torque in the central `particleShear.CanvasPointsMass.force_register`. If a pair of spheres concerned is situated across a Lees-Edwards boundary (periodicity), the function counts this as external rather than internal torque.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" if not self.canMove(target): return if self.canMove(source): # Consider only physical pairs physical_distance = target.d_euclidian(source) simulation_distance = target.d(source) if physical_distance == simulation_distance: self.force_register.record_individual_internal_torque(target,source,moment) else: self.force_register.record_external_torque(target, moment) else: self.force_register.record_external_torque(target,moment)
def setShear(self, theShear)
-
Set new shear value and transmit to the constituent spheres via
CanvasPointsBasicElasticityLeesEdwards.setShearInSpheres()
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def setShear(self,theShear): """Set new shear value and transmit to the constituent spheres via `particleShear.CanvasPointsBasicElasticityLeesEdwards.setShearInSpheres` This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" self.shear=theShear self.setShearInSpheres()
def setShearInSpheres(self)
-
Propage the shear value to the constituent spheres.
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def setShearInSpheres(self): """Propage the shear value to the constituent spheres. This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.shear=self.shear
def setShearRate(self, theShearRate)
-
set a new value for the shear rate and adjust movement of the constituent spheres correspondingly.
This method is designed to respect the a priori deformation under the Lees-Edwards boundary conditions via
CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change()
This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def setShearRate(self, theShearRate): """set a new value for the shear rate and adjust movement of the constituent spheres correspondingly. This method is designed to respect the a priori deformation under the Lees-Edwards boundary conditions via `particleShear.CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change`\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" delta_shear_rate=theShearRate-self.shear_rate self.adjust_sphere_speed_to_shear_rate_change(delta_shear_rate) self.shear_rate = theShearRate self.setShearRateInSpheres()
def setShearRateInSpheres(self)
-
Propagate shear rate to the spheres.
To allow for SLLOD-type adjustment to the changed shear rate,
CanvasPointsBasicElasticityLeesEdwards.setShearRate()
is used.This method is defined in class
CanvasPointsBasicElasticityLeesEdwards
Expand source code
def setShearRateInSpheres(self): """Propagate shear rate to the spheres. To allow for SLLOD-type adjustment to the changed shear rate, `particleShear.CanvasPointsBasicElasticityLeesEdwards.setShearRate` is used.\n This method is defined in class `particleShear.CanvasPointsBasicElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.shear_rate = self.shear_rate
-
class CanvasPointsFrictionElasticityLeesEdwards (size_x, size_y, theCanvas=0, doDrawing=0, k=1, nu=0.01, m=1, k_t=1, nu_t=0.01, mu=0.1)
-
Canvas for placing
CircleFrictionElasticity
objects (referred to as spheres). This class maintains a list of objects of typeCircleFrictionElasticity
or derived and adds functions for handling friction and associated non-central forcesInitialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu
Expand source code
class CanvasPointsFrictionElasticityLeesEdwards(CanvasPointsBasicElasticityLeesEdwards): """Canvas for placing `particleShear.CircleFrictionElasticity` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.CircleFrictionElasticity` or derived and adds functions for handling friction and associated non-central forces""" def __init__(self, size_x, size_y, theCanvas=FALSE, doDrawing=FALSE, k=1, nu=0.01,m=1,k_t=1,nu_t=0.01,mu=0.1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the mass per unit of depth in mg/m of depth\n `k_t` Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces\n `nu_t` Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones\n `mu` Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu""" super(CanvasPointsFrictionElasticityLeesEdwards, self).__init__( size_x, size_y, theCanvas, doDrawing, k, nu, m) self.k_t=k_t """`k_t` Tangential spring constant in (mg/s^2)/m of depth;""" self.nu_t=nu_t """`nu_t` Central viscosity force constant in (mg/s)/m of depth.""" self.mu=mu """`mu` Friction coefficient""" def particle_info(self): return "CanvaPointsFrictionElasticityLeesEdwards: Lees Edwards boundary conditions and stick-and-slip type friction" def tangential_force(self): """Let all spheres calculate the total tangental force acting on them. This will check for contact and so is more time consuming than `particleShear.CanvasPointsFrictionElasticityLeesEdwards.tangential_force_from_neighbors`. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity`or a subclass thereof, or otherwise possess a method `tangential_force`\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].tangential_force(self.sphereList[sphereIndex2], self.nu_t, self.mu, self.k, self.k_t) def tangential_force_from_neighbors(self): """Let all spheres calculate the total tangential force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsFrictionElasticityLeesEdwards.tangential_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity`or a subclass thereof, or otherwise possess a method `tangential_force_from_neighbors`.\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].tangential_force_from_neighbors(self.nu_t,self.mu,self.k,self.k_t) def do_rotational_acceleration(self, dt): """Have the spheres calculate their rotational acceleration from the tangential forces. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity` or subclass thereof, or alternatively, possess a method `do_rotational_acceleration`\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.do_rotational_acceleration(dt) def mechanical_simulation_step_calculate_forces(self,dt=1): super(CanvasPointsFrictionElasticityLeesEdwards,self).mechanical_simulation_step_calculate_forces() self.tangential_force_from_neighbors() def record_total_particle_forces(self): super(CanvasPointsFrictionElasticityLeesEdwards,self).record_total_particle_forces() for theSphere in self.sphereList: moment = theSphere.torque self.force_register.record_unbalanced_moment(theSphere,moment) def mechanical_simulation_step_calculate_movement(self,dt=1): self.move(dt) self.setShear(self.shear + self.shear_rate * dt) self.setShearRate(self.shear_rate) self.boundary_conditions() self.t = self.t + dt def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): self.do_linear_acceleration(dt) self.do_rotational_acceleration(dt) self.cool(cool_factor)
Ancestors
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearLinkableObjects.CanvasPointsLinkable.CanvasPointsLinkable
- particleShearObjects.EnsembleFrictionLeesEdwards.EnsembleFrictionLeesEdwards
Instance variables
var k_t
-
k_t
Tangential spring constant in (mg/s^2)/m of depth; var mu
-
mu
Friction coefficient var nu_t
-
nu_t
Central viscosity force constant in (mg/s)/m of depth.
Methods
def do_rotational_acceleration(self, dt)
-
Have the spheres calculate their rotational acceleration from the tangential forces.
For this function to work, the spheres must be of type
CircleFrictionElasticity
or subclass thereof, or alternatively, possess a methoddo_rotational_acceleration
This method is defined in class
CanvasPointsFrictionElasticityLeesEdwards
Expand source code
def do_rotational_acceleration(self, dt): """Have the spheres calculate their rotational acceleration from the tangential forces. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity` or subclass thereof, or alternatively, possess a method `do_rotational_acceleration`\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for theSphere in self.sphereList: theSphere.do_rotational_acceleration(dt)
def mechanical_simulation_step_calculate_acceleration(self, cool_factor=0.97, dt=1)
-
Calculate the accelerations resulting from the forces
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): self.do_linear_acceleration(dt) self.do_rotational_acceleration(dt) self.cool(cool_factor)
def mechanical_simulation_step_calculate_forces(self, dt=1)
-
Calculate the forces acting on the spheres
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_forces(self,dt=1): super(CanvasPointsFrictionElasticityLeesEdwards,self).mechanical_simulation_step_calculate_forces() self.tangential_force_from_neighbors()
def mechanical_simulation_step_calculate_movement(self, dt=1)
-
Calculate the displacement resulting from the sphere movement
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def mechanical_simulation_step_calculate_movement(self,dt=1): self.move(dt) self.setShear(self.shear + self.shear_rate * dt) self.setShearRate(self.shear_rate) self.boundary_conditions() self.t = self.t + dt
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvaPointsFrictionElasticityLeesEdwards: Lees Edwards boundary conditions and stick-and-slip type friction"
def record_total_particle_forces(self)
-
Expand source code
def record_total_particle_forces(self): super(CanvasPointsFrictionElasticityLeesEdwards,self).record_total_particle_forces() for theSphere in self.sphereList: moment = theSphere.torque self.force_register.record_unbalanced_moment(theSphere,moment)
def tangential_force(self)
-
Let all spheres calculate the total tangental force acting on them.
This will check for contact and so is more time consuming than
CanvasPointsFrictionElasticityLeesEdwards.tangential_force_from_neighbors()
. For this function to work, the spheres must be of typeCircleFrictionElasticity
or a subclass thereof, or otherwise possess a methodtangential_force
This method is defined in class
CanvasPointsFrictionElasticityLeesEdwards
Expand source code
def tangential_force(self): """Let all spheres calculate the total tangental force acting on them. This will check for contact and so is more time consuming than `particleShear.CanvasPointsFrictionElasticityLeesEdwards.tangential_force_from_neighbors`. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity`or a subclass thereof, or otherwise possess a method `tangential_force`\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].tangential_force(self.sphereList[sphereIndex2], self.nu_t, self.mu, self.k, self.k_t)
def tangential_force_from_neighbors(self)
-
Let all spheres calculate the total tangential force acting on from their known neighbors.
This is quicker than
CanvasPointsFrictionElasticityLeesEdwards.tangential_force()
but necessitates the neighbor relations to be already established viaCanvasPointsNeighbors.test_neighbor_relation()
. For this function to work, the spheres must be of typeCircleFrictionElasticity
or a subclass thereof, or otherwise possess a methodtangential_force_from_neighbors
.This method is defined in class
CanvasPointsFrictionElasticityLeesEdwards
Expand source code
def tangential_force_from_neighbors(self): """Let all spheres calculate the total tangential force acting on from their known neighbors. This is quicker than `particleShear.CanvasPointsFrictionElasticityLeesEdwards.tangential_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`. For this function to work, the spheres must be of type `particleShear.CircleFrictionElasticity`or a subclass thereof, or otherwise possess a method `tangential_force_from_neighbors`.\n This method is defined in class `particleShear.CanvasPointsFrictionElasticityLeesEdwards`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].tangential_force_from_neighbors(self.nu_t,self.mu,self.k,self.k_t)
-
class CanvasPointsLinkable (size_x, size_y, theCanvas=0, doDrawing=False, k=1, nu=0.01, m=1, k_t=1, nu_t=0.01, mu=0.1)
-
Canvas for placing
SphereLinkable
objects (referred to as spheres). This class maintains a list of objects of typeSphereLinkable
and adds elementary functions related to crosslinkingInitialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu
Expand source code
class CanvasPointsLinkable (CanvasPointsFrictionElasticityLeesEdwards): """Canvas for placing `particleShear.SphereLinkable` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.SphereLinkable` and adds elementary functions related to crosslinking""" def __init__(self, size_x, size_y, theCanvas=FALSE, doDrawing=False, k=1, nu=0.01,m=1,k_t=1,nu_t=0.01,mu=0.1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the mass per unit of depth in mg/m of depth\n `k_t` Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces\n `nu_t` Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones\n `mu` Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu""" super(CanvasPointsLinkable, self).__init__( size_x, size_y, theCanvas=theCanvas, doDrawing=doDrawing, k=k, nu=nu,m=m,k_t=k_t,nu_t=nu_t,mu=mu) def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.CanvasPointsLinkable` """ return "CanvasPointsLinkable: Frictional spheres with possible permanent links, Lees Edwards boundary" def makeAllLinksPermanent(self): """Link all currently touching spheres with permanent links Method defined in `particleShear.CanvasPointsLinkable` """ for theSphere in self.sphereList: for theNeighbor in theSphere.neighbors: theSphere.establish_permanent_link(theNeighbor.theSphere) def cutLine(self,point,angle): """Remove permanent links across a straight line - **parameters**\n `point` Vector of 2 elements for x and y coordinates of a point on the cut line\n `angle` Angle of the cut line, in radians; 0=vertical \n Method defined in `particleShear.CanvasPointsLinkable` """ norm_vector = [math.cos(angle), math.sin(angle)] base = dotProduct2(norm_vector,point) for theSphere in self.sphereList: for theNeighbor in theSphere.neighbors: if (theNeighbor.interface_type == "permanent"): myScalar_product = dotProduct2(norm_vector, [theSphere.x, theSphere.y]) - base dist = theSphere.d(theNeighbor.theSphere) n_vector = theSphere.n(theNeighbor.theSphere) neighbor_scalar_product = dotProduct2(norm_vector, [theSphere.x + n_vector[0] * dist, theSphere.y + n_vector[1] * dist]) - base # Crossline means opposite side or cut through one or two of the points if (myScalar_product * neighbor_scalar_product) <= 0: theSphere.cut_permanent_link(theNeighbor.theSphere) def cutRandomLine(self): """Remove permanent links across a random cut line Method defined in `particleShear.CanvasPointsLinkable` """ point=[random.randint(0,self.size_x), random.randint(0,self.size_y)] angle=random.random()* 2 * math.pi self.cutLine(point,angle) def cutTopBottomEdge(self): """Remove permanent links across a top and bottom boundary lines Method defined in `particleShear.CanvasPointsLinkable` """ self.cutLine([0,0],math.pi/2) self.cutLine([0, self.size_y], math.pi / 2)
Ancestors
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearLinkableObjects.EnsembleCompactParticles.EnsembleCompactParticles
- particleShearLinkableObjects.EnsembleLinkable.EnsembleLinkable
Methods
def cutLine(self, point, angle)
-
Remove permanent links across a straight line
-
parameters
point
Vector of 2 elements for x and y coordinates of a point on the cut lineangle
Angle of the cut line, in radians; 0=verticalMethod defined in
CanvasPointsLinkable
Expand source code
def cutLine(self,point,angle): """Remove permanent links across a straight line - **parameters**\n `point` Vector of 2 elements for x and y coordinates of a point on the cut line\n `angle` Angle of the cut line, in radians; 0=vertical \n Method defined in `particleShear.CanvasPointsLinkable` """ norm_vector = [math.cos(angle), math.sin(angle)] base = dotProduct2(norm_vector,point) for theSphere in self.sphereList: for theNeighbor in theSphere.neighbors: if (theNeighbor.interface_type == "permanent"): myScalar_product = dotProduct2(norm_vector, [theSphere.x, theSphere.y]) - base dist = theSphere.d(theNeighbor.theSphere) n_vector = theSphere.n(theNeighbor.theSphere) neighbor_scalar_product = dotProduct2(norm_vector, [theSphere.x + n_vector[0] * dist, theSphere.y + n_vector[1] * dist]) - base # Crossline means opposite side or cut through one or two of the points if (myScalar_product * neighbor_scalar_product) <= 0: theSphere.cut_permanent_link(theNeighbor.theSphere)
-
def cutRandomLine(self)
-
Remove permanent links across a random cut line
Method defined in
CanvasPointsLinkable
Expand source code
def cutRandomLine(self): """Remove permanent links across a random cut line Method defined in `particleShear.CanvasPointsLinkable` """ point=[random.randint(0,self.size_x), random.randint(0,self.size_y)] angle=random.random()* 2 * math.pi self.cutLine(point,angle)
def cutTopBottomEdge(self)
-
Remove permanent links across a top and bottom boundary lines
Method defined in
CanvasPointsLinkable
Expand source code
def cutTopBottomEdge(self): """Remove permanent links across a top and bottom boundary lines Method defined in `particleShear.CanvasPointsLinkable` """ self.cutLine([0,0],math.pi/2) self.cutLine([0, self.size_y], math.pi / 2)
def makeAllLinksPermanent(self)
-
Link all currently touching spheres with permanent links
Method defined in
CanvasPointsLinkable
Expand source code
def makeAllLinksPermanent(self): """Link all currently touching spheres with permanent links Method defined in `particleShear.CanvasPointsLinkable` """ for theSphere in self.sphereList: for theNeighbor in theSphere.neighbors: theSphere.establish_permanent_link(theNeighbor.theSphere)
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
CanvasPointsLinkable
Expand source code
def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.CanvasPointsLinkable` """ return "CanvasPointsLinkable: Frictional spheres with possible permanent links, Lees Edwards boundary"
-
class CanvasPointsMass (size_x, size_y, theCanvas=0, doDrawing=0, m=1)
-
Canvas for placing objects of type
CircleMass
(or derived).This class maintains a list of objects of type
CircleMass
or derived and adds functions to handle this collection. SeeCanvasPoints.sphereList
of the base classCanvasPoints
.Initialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notm
is the mass per unit of depth in mg/m of depthExpand source code
class CanvasPointsMass(CanvasPoints): """Canvas for placing objects of type `particleShear.CircleMass` (or derived). This class maintains a list of objects of type `particleShear.CircleMass` or derived and adds functions to handle this collection. See `particleShear.CanvasPoints.sphereList` of the base class `particleShear.CanvasPoints`.""" def __init__(self, size_x, size_y, theCanvas=FALSE, doDrawing=FALSE, m=1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `m` is the mass per unit of depth in mg/m of depth""" super(CanvasPointsMass,self).__init__( size_x, size_y, theCanvas=theCanvas, doDrawing=doDrawing) self.t=0 """Keep track of time elapsed during application of shear protocols""" self.force_register = Force_register() """The `particleShear.Force_register` to store the forces acting on the particles""" self.m=m """The mass (identical) for each of the spheres""" def reset_force_register(self): """Empty the `particleShear.CanvasPointsMass.force_register` This method is defined in class `particleShear.CanvasPointsMass`""" self.force_register.reset_force_register() def record_individual_internal_force(self,target,source,force_vector): """Record a force in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for. Also, the function detects external forces acting from immobilized boundary spheres and re-reroutes the call to `particleShear.CanvasPointsMass.record_external_force`.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): # Sphere moved by boundary conditions with unknown force return if self.canMove(source): # running internal spheres moving freely self.force_register.record_individual_internal_force(target, source, force_vector) else: self.record_external_force(target, force_vector) def record_individual_internal_torque(self, target, source, moment): """Record a torque in the `particleShear.CanvasPointsMass.force_register`. Only torques resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for. Also, the function detects external torques acting from immobilized boundary spheres and re-reroutes the call to `particleShear.CanvasPointsMass.record_external_torque`.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): # Sphere moved by boundary conditions with unknown force return if self.canMove(source): # running internal spheres moving freely self.force_register.record_individual_internal_torque(target, source, moment) else: self.record_external_torque(target, moment) def record_total_particle_forces(self): for theSphere in self.sphereList: force_vector = [theSphere.xforce, theSphere.yforce] self.record_total_particle_force(theSphere,force_vector) def record_total_particle_force(self,target,force_vector): """Record a total resultant particle force in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_total_particle_force(target,force_vector) def record_external_force(self, target, force_vector): """Record an external force actin on a mobile sphere in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_external_force(target, force_vector) def record_external_torque(self, target, moment): """Record an external torque acting on a mobile sphere in the `particleShear.CanvasPointsMass.torque_register`. Only torques resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_external_torque(target, moment) def record_unbalanced_moment(self,target,moment): """Record unbalanced (net) angular moment acting on a mobile sphere in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_unbalanced_moment(target, moment) def particle_info(self): return "CanvasPointsMass: Graphical interface with force registry and acceleration" def evaluate_stress_tensors(self,StressTensorEvaluator): """Evaluate the stress tensors from the `particleShear.CanvasPointsMass.force_register` - **parameters**\n The StressTensorEvaluator is expected be of type `particleShear.StressTensorEvaluation` or derived.\n This method is defined in class `particleShear.CanvasPointsMass`""" StressTensorEvaluator.evaluate_stress_tensors(self.force_register, self.movableSphereList(), self.shear_rate) def reset_force(self): """Reset all actual forces on the spheres. Does not reset the `particleShear.CanvasPointsMass.force_register`. This method is defined in class `particleShear.CanvasPointsMass`""" for theSphere in self.sphereList: theSphere.xforce = 0 theSphere.yforce = 0
Ancestors
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
Instance variables
var force_register
-
The
Force_register
to store the forces acting on the particles var m
-
The mass (identical) for each of the spheres
var t
-
Keep track of time elapsed during application of shear protocols
Methods
def evaluate_stress_tensors(self, StressTensorEvaluator)
-
Evaluate the stress tensors from the
CanvasPointsMass.force_register
- parameters
The StressTensorEvaluator is expected be of type
StressTensorEvaluation
or derived.This method is defined in class
CanvasPointsMass
Expand source code
def evaluate_stress_tensors(self,StressTensorEvaluator): """Evaluate the stress tensors from the `particleShear.CanvasPointsMass.force_register` - **parameters**\n The StressTensorEvaluator is expected be of type `particleShear.StressTensorEvaluation` or derived.\n This method is defined in class `particleShear.CanvasPointsMass`""" StressTensorEvaluator.evaluate_stress_tensors(self.force_register, self.movableSphereList(), self.shear_rate)
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvasPointsMass: Graphical interface with force registry and acceleration"
def record_external_force(self, target, force_vector)
-
Record an external force actin on a mobile sphere in the
CanvasPointsMass.force_register
.Only forces resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for.This method is defined in class
CanvasPointsMass
Expand source code
def record_external_force(self, target, force_vector): """Record an external force actin on a mobile sphere in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_external_force(target, force_vector)
def record_external_torque(self, target, moment)
-
Record an external torque acting on a mobile sphere in the
particleShear.CanvasPointsMass.torque_register
.Only torques resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for.This method is defined in class
CanvasPointsMass
Expand source code
def record_external_torque(self, target, moment): """Record an external torque acting on a mobile sphere in the `particleShear.CanvasPointsMass.torque_register`. Only torques resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_external_torque(target, moment)
def record_individual_internal_force(self, target, source, force_vector)
-
Record a force in the
CanvasPointsMass.force_register
.Only forces resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for. Also, the function detects external forces acting from immobilized boundary spheres and re-reroutes the call toCanvasPointsMass.record_external_force()
.This method is defined in class
CanvasPointsMass
Expand source code
def record_individual_internal_force(self,target,source,force_vector): """Record a force in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for. Also, the function detects external forces acting from immobilized boundary spheres and re-reroutes the call to `particleShear.CanvasPointsMass.record_external_force`.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): # Sphere moved by boundary conditions with unknown force return if self.canMove(source): # running internal spheres moving freely self.force_register.record_individual_internal_force(target, source, force_vector) else: self.record_external_force(target, force_vector)
def record_individual_internal_torque(self, target, source, moment)
-
Record a torque in the
CanvasPointsMass.force_register
.Only torques resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for. Also, the function detects external torques acting from immobilized boundary spheres and re-reroutes the call toCanvasPointsMass.record_external_torque()
.This method is defined in class
CanvasPointsMass
Expand source code
def record_individual_internal_torque(self, target, source, moment): """Record a torque in the `particleShear.CanvasPointsMass.force_register`. Only torques resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for. Also, the function detects external torques acting from immobilized boundary spheres and re-reroutes the call to `particleShear.CanvasPointsMass.record_external_torque`.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): # Sphere moved by boundary conditions with unknown force return if self.canMove(source): # running internal spheres moving freely self.force_register.record_individual_internal_torque(target, source, moment) else: self.record_external_torque(target, moment)
def record_total_particle_force(self, target, force_vector)
-
Record a total resultant particle force in the
CanvasPointsMass.force_register
.Only forces resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for.This method is defined in class
CanvasPointsMass
Expand source code
def record_total_particle_force(self,target,force_vector): """Record a total resultant particle force in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_total_particle_force(target,force_vector)
def record_total_particle_forces(self)
-
Expand source code
def record_total_particle_forces(self): for theSphere in self.sphereList: force_vector = [theSphere.xforce, theSphere.yforce] self.record_total_particle_force(theSphere,force_vector)
def record_unbalanced_moment(self, target, moment)
-
Record unbalanced (net) angular moment acting on a mobile sphere in the
CanvasPointsMass.force_register
.Only forces resulting in actual motion (i.e. acting on mobile spheres, see
CanvasPoints.canMove()
) are accounted for.This method is defined in class
CanvasPointsMass
Expand source code
def record_unbalanced_moment(self,target,moment): """Record unbalanced (net) angular moment acting on a mobile sphere in the `particleShear.CanvasPointsMass.force_register`. Only forces resulting in actual motion (i.e. acting on mobile spheres, see `particleShear.CanvasPoints.canMove`) are accounted for.\n This method is defined in class `particleShear.CanvasPointsMass`""" if not self.canMove(target): return self.force_register.record_unbalanced_moment(target, moment)
def reset_force(self)
-
Reset all actual forces on the spheres. Does not reset the
CanvasPointsMass.force_register
.This method is defined in class
CanvasPointsMass
Expand source code
def reset_force(self): """Reset all actual forces on the spheres. Does not reset the `particleShear.CanvasPointsMass.force_register`. This method is defined in class `particleShear.CanvasPointsMass`""" for theSphere in self.sphereList: theSphere.xforce = 0 theSphere.yforce = 0
def reset_force_register(self)
-
Empty the
CanvasPointsMass.force_register
This method is defined in class
CanvasPointsMass
Expand source code
def reset_force_register(self): """Empty the `particleShear.CanvasPointsMass.force_register` This method is defined in class `particleShear.CanvasPointsMass`""" self.force_register.reset_force_register()
class CanvasPointsNeighbors (size_x, size_y, theCanvas=False, doDrawing=False, m=1)
-
Canvas for placing
CircleMassNeighbors
objects (referred to as spheres). This class maintains a list of objects of typeCircleMassNeighbors
or derived and adds basic functions to handle this collectionInitialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notm
is the mass per unit of depth in mg/m of depthExpand source code
class CanvasPointsNeighbors(CanvasPointsMass): """Canvas for placing `particleShear.CircleMassNeighbors` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.CircleMassNeighbors` or derived and adds basic functions to handle this collection""" def __init__(self, size_x, size_y, theCanvas=False, doDrawing=False, m=1): super(CanvasPointsNeighbors,self).__init__(size_x=size_x,size_y=size_y,theCanvas=theCanvas,doDrawing=doDrawing,m=m) def particle_info(self): return "CanvasPointsNeighbors: particles with mass and detection of geometric neighbors" def test_neighbor_relation(self): """Instruct every sphere to test establish its geometric (and possibly permanent) neighbors. This function calls `particleShear.CircleMassNeighbors.test_neighbor_relation` on each member of the `particleShear.CanvasPoints.sphereList`.\n This method is defined in class `particleShear.CanvasPointsNeighbors`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].test_neighbor_relation( self.sphereList[sphereIndex2] )
Ancestors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearBase.CanvasPointsShear.CanvasPointsShear
Methods
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvasPointsNeighbors: particles with mass and detection of geometric neighbors"
def test_neighbor_relation(self)
-
Instruct every sphere to test establish its geometric (and possibly permanent) neighbors.
This function calls
CircleMassNeighbors.test_neighbor_relation()
on each member of theCanvasPoints.sphereList
.This method is defined in class
CanvasPointsNeighbors
Expand source code
def test_neighbor_relation(self): """Instruct every sphere to test establish its geometric (and possibly permanent) neighbors. This function calls `particleShear.CircleMassNeighbors.test_neighbor_relation` on each member of the `particleShear.CanvasPoints.sphereList`.\n This method is defined in class `particleShear.CanvasPointsNeighbors`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].test_neighbor_relation( self.sphereList[sphereIndex2] )
class CanvasPointsShear (size_x, size_y, theCanvas=False, doDrawing=False, m=1)
-
Canvas for placing
CircleMassNeighbors
objects (referred to as spheres). This class maintains a list of objects of typeCircleMassNeighbors
or derived and adds specific functionality linked to shear under Lees-Edwards boundary conditionsInitialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notm
is the mass per unit of depth in mg/m of depthExpand source code
class CanvasPointsShear(CanvasPointsNeighbors): """Canvas for placing `particleShear.CircleMassNeighbors` objects (referred to as spheres). This class maintains a list of objects of type `particleShear.CircleMassNeighbors` or derived and adds specific functionality linked to shear under Lees-Edwards boundary conditions""" def __init__(self, size_x, size_y, theCanvas=False, doDrawing=False,m=1): super(CanvasPointsShear,self).__init__(size_x=size_x,size_y=size_y,theCanvas=theCanvas,doDrawing=doDrawing,m=m) self.t=0 """Record time while applying shear""" self.applyingShear = False """Are we currently applying a shear protocol? Advance time only if True""" self.shear_rate = 0 """Current shear rate (relative, so 1/s)""" self.shear = 0 """Current applied shear (relative, so dimension-less)""" self.use_lees_edwards=True """Flag to indicate whether or not we use Lees-Edwards boundary conditions""" def canMove(self, theSphere): """Indicate whether a given sphere (object of type `particleShear.Point` or subclass thereof) to move on the current canvas This is for appyling boundary conditions where the movement is imposed externally on the top and lower edge. In this case all spheres can move except for in specified shear experiments where `particleShear.CanvasPointsShear.applyingShear` is set True\n This method is defined in class `particleShear.CanvasPointsShear`""" if not self.applyingShear: return True pos = theSphere.coordinates() # Spheres touching the upper and lower boundaries are attached to these boundaries and therefore immobilized return pos[1] > pos[2] and pos[1] < self.size_y - pos[2] def particle_info(self): return "CanvasPointsShear: Points with mass, neighbor detection and shear" def do_linear_acceleration(self, dt): """Instruct mobile spheres (objects of type `particleShear.Point` or subclasses thereof) to change their speed depending on the forces acting on them. The objects need to to derive from `particleShear.CircleMass` or otherwise possess a do_linear_acceleration method. In addition to its parent method (`particleShear.CanvasPoints.do_linear_acceleration` of class `particleShear.CanvasPoints`), this method imposes the local speed given by the current `particleShear.CanvasPointsShear.shear_rate` at the top and lower boundary to the immobile spheres This method is defined in class `particleShear.CanvasPointsShear`""" super(CanvasPointsShear,self).do_linear_acceleration(dt) for theSphere in self.sphereList: if not self.canMove(theSphere): pos = theSphere.coordinates() theSphere.yspeed = 0 if pos[1] > self.size_y / 2: theSphere.xspeed = self.shear_rate * self.size_y / 2 else: theSphere.xspeed = -self.shear_rate * self.size_y / 2 def transmit_lees_edwards_parameters(self, theSphere): """Transmit the parameters pertaining to the Lees-Edwards boundary conditions to the spheres. If `particleShear.CanvasPointsShear.use_lees_edwards` is set to True, transmit the instance variables regarding canvas size (`particleShear.CanvasPoints.size_x`,`particleShear.CanvasPoints.size_y`), and shear regime (`particleShear.CanvasPointsShear.shear`,`particleShear.CanvasPointsShear.shear_rate`, `particleShear.CanvasPointsShear.use_lees_edwards`) to the given target sphere. Else, only set the `particleShear.PointLeesEdwards.use_lees_edwards` field in the target sphere to False\n This method is defined in class `particleShear.CanvasPointsShear`""" if self.use_lees_edwards: theSphere.size_x = self.size_x theSphere.size_y = self.size_y theSphere.shear_rate = self.shear_rate theSphere.shear = self.shear theSphere.use_lees_edwards = self.use_lees_edwards else: if hasattr(theSphere, "use_lees_edwards"): theSphere.use_lees_edwards = False return theSphere
Ancestors
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
Instance variables
var applyingShear
-
Are we currently applying a shear protocol?
Advance time only if True
var shear
-
Current applied shear (relative, so dimension-less)
var shear_rate
-
Current shear rate (relative, so 1/s)
var t
-
Record time while applying shear
var use_lees_edwards
-
Flag to indicate whether or not we use Lees-Edwards boundary conditions
Methods
def canMove(self, theSphere)
-
Indicate whether a given sphere (object of type
Point
or subclass thereof) to move on the current canvasThis is for appyling boundary conditions where the movement is imposed externally on the top and lower edge. In this case all spheres can move except for in specified shear experiments where
CanvasPointsShear.applyingShear
is set TrueThis method is defined in class
CanvasPointsShear
Expand source code
def canMove(self, theSphere): """Indicate whether a given sphere (object of type `particleShear.Point` or subclass thereof) to move on the current canvas This is for appyling boundary conditions where the movement is imposed externally on the top and lower edge. In this case all spheres can move except for in specified shear experiments where `particleShear.CanvasPointsShear.applyingShear` is set True\n This method is defined in class `particleShear.CanvasPointsShear`""" if not self.applyingShear: return True pos = theSphere.coordinates() # Spheres touching the upper and lower boundaries are attached to these boundaries and therefore immobilized return pos[1] > pos[2] and pos[1] < self.size_y - pos[2]
def do_linear_acceleration(self, dt)
-
Instruct mobile spheres (objects of type
Point
or subclasses thereof) to change their speed depending on the forces acting on them.The objects need to to derive from
CircleMass
or otherwise possess a do_linear_acceleration method. In addition to its parent method (CanvasPoints.do_linear_acceleration()
of classCanvasPoints
), this method imposes the local speed given by the currentCanvasPointsShear.shear_rate
at the top and lower boundary to the immobile spheresThis method is defined in class
CanvasPointsShear
Expand source code
def do_linear_acceleration(self, dt): """Instruct mobile spheres (objects of type `particleShear.Point` or subclasses thereof) to change their speed depending on the forces acting on them. The objects need to to derive from `particleShear.CircleMass` or otherwise possess a do_linear_acceleration method. In addition to its parent method (`particleShear.CanvasPoints.do_linear_acceleration` of class `particleShear.CanvasPoints`), this method imposes the local speed given by the current `particleShear.CanvasPointsShear.shear_rate` at the top and lower boundary to the immobile spheres This method is defined in class `particleShear.CanvasPointsShear`""" super(CanvasPointsShear,self).do_linear_acceleration(dt) for theSphere in self.sphereList: if not self.canMove(theSphere): pos = theSphere.coordinates() theSphere.yspeed = 0 if pos[1] > self.size_y / 2: theSphere.xspeed = self.shear_rate * self.size_y / 2 else: theSphere.xspeed = -self.shear_rate * self.size_y / 2
def particle_info(self)
-
Return information on the type of particle ensemble used. This information can for instance be used to properly document output in files
Expand source code
def particle_info(self): return "CanvasPointsShear: Points with mass, neighbor detection and shear"
def transmit_lees_edwards_parameters(self, theSphere)
-
Transmit the parameters pertaining to the Lees-Edwards boundary conditions to the spheres.
If
CanvasPointsShear.use_lees_edwards
is set to True, transmit the instance variables regarding canvas size (CanvasPoints.size_x
,CanvasPoints.size_y
), and shear regime (CanvasPointsShear.shear
,CanvasPointsShear.shear_rate
,CanvasPointsShear.use_lees_edwards
) to the given target sphere. Else, only set thePointLeesEdwards.use_lees_edwards
field in the target sphere to FalseThis method is defined in class
CanvasPointsShear
Expand source code
def transmit_lees_edwards_parameters(self, theSphere): """Transmit the parameters pertaining to the Lees-Edwards boundary conditions to the spheres. If `particleShear.CanvasPointsShear.use_lees_edwards` is set to True, transmit the instance variables regarding canvas size (`particleShear.CanvasPoints.size_x`,`particleShear.CanvasPoints.size_y`), and shear regime (`particleShear.CanvasPointsShear.shear`,`particleShear.CanvasPointsShear.shear_rate`, `particleShear.CanvasPointsShear.use_lees_edwards`) to the given target sphere. Else, only set the `particleShear.PointLeesEdwards.use_lees_edwards` field in the target sphere to False\n This method is defined in class `particleShear.CanvasPointsShear`""" if self.use_lees_edwards: theSphere.size_x = self.size_x theSphere.size_y = self.size_y theSphere.shear_rate = self.shear_rate theSphere.shear = self.shear theSphere.use_lees_edwards = self.use_lees_edwards else: if hasattr(theSphere, "use_lees_edwards"): theSphere.use_lees_edwards = False return theSphere
class Circle (color, x, y, diameter, theCanvas=False, doDrawing=False, use_lees_edwards=False)
-
Provide a circle that can draw itself according to the graphical drawing convention
Inherits from
PointLeesEdwards
, so will respect Lees-Edwards boundary conditions providedPointLeesEdwards.use_lees_edwards
is set to True (default: False)Initialize self
- parameters
x
The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)y
The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)diameter
The diameter of the circle (in pixels, assumed micrometers for the simulation)theCanvas
Tkinter canvas; provide False if no Canvas is available or no drawing is desireddoDrawing
Explicit switch to turn of drawinguse_lees_edwards
Explicit Shoud Lees-Edwards boundary conditions be used?Expand source code
class Circle(PointLeesEdwards): """Provide a circle that can draw itself according to the graphical drawing convention Inherits from `particleShear.PointLeesEdwards`, so will respect Lees-Edwards boundary conditions provided `particleShear.PointLeesEdwards.use_lees_edwards` is set to True (default: False)""" def __init__(self, color, x, y, diameter, theCanvas=False, doDrawing=False,use_lees_edwards=False): """Initialize self - **parameters**\n `x` The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `y` The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `diameter` The diameter of the circle (in pixels, assumed micrometers for the simulation)\n `theCanvas` Tkinter canvas; provide False if no Canvas is available or no drawing is desired\n `doDrawing` Explicit switch to turn of drawing\n `use_lees_edwards` Explicit Shoud Lees-Edwards boundary conditions be used?\n """ super(Circle,self).__init__(x,y,use_lees_edwards=use_lees_edwards) self.theCanvas = theCanvas """tkinter canvas""" self.r = diameter / 2 """Radius of the circle, in pixels (=micrometers for the simulations)""" self.doDrawing = doDrawing """Flag to indicate whether graphical output should be produced or not""" self.shape=False """Shape on the Tkinter canvas when actively drawing""" self.color=color """Color of the spheres""" self.graphical_output_configuration = Graphical_output_configuration() """`particleShear.Graphical_output_configuration` to set options how the spheres and their neighboring relations are displayed""" self.graphical_output_configuration.color_spheres_volume= color self.graphical_output_configuration.color_spheres_boundary = color self.set_graphical_output_configuration(self.graphical_output_configuration) self.initiate_drawing() def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.Circle`""" self.deleteDrawing() self.graphical_output_configuration=graphical_output_configuration self.color=self.graphical_output_configuration.color_spheres_volume self.initiate_drawing() def coordinates(self): """Return the current coordinates: vector of three elements, which are (x,y,radius) This method is defined in class `particleShear.Circle`""" return [self.x, self.y, self.r] def initiate_drawing(self): """Instiante the shapes representing this object on `particleShear.Circle.theCanvas`. This method only has an effect if `particleShear.Circle.doDrawing` is True. If a shape is created, the reference to the circle on the canvas is stored in `particleShear.Circle.shape`""" self.deleteDrawing() self.shape = False if self.doDrawing: if self.graphical_output_configuration.draw_spheres_as=="spheres": self.shape = self.theCanvas.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color) if self.graphical_output_configuration.draw_spheres_as=="dots": self.shape = self.theCanvas.create_oval(self.x - 2, self.y - 2, self.x + 2, self.y + 2, fill=self.color) def move(self, dt=1): """Move for time interval dt, including the graphical shape if present This method is defined in class `particleShear.Circle`""" super(Circle,self).move(dt) if self.doDrawing: self.moveDrawing(dt) def moveDrawing(self, dt): """Move for the drawing on the Tkinter canvas This method is defined in class `particleShear.Circle`""" self.theCanvas.move(self.shape, self.xspeed * dt, self.yspeed * dt) def deleteDrawing(self): """Delete drawing on the Tkinter canvas This method is defined in class `particleShear.Circle`""" if self.theCanvas: self.theCanvas.delete(self.shape) def tangential_speed(self, theSphere): """Tangential component of the relative speed of a given sphere relative to this object. The other sphere object needs to be of type `particleShear.Point` or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation). This method uses `particleShear.Circle.tangential_vector` internally.\n This method is defined in class `particleShear.Circle`""" v_rel = self.relative_speed(theSphere) vx_rel = v_rel[0] vy_rel = v_rel[1] t_vector = self.tangential_vector(theSphere) vt = vx_rel * t_vector[0] + vy_rel * t_vector[1] return vt def tangential_vector(self, theSphere): """Return tangential interface vector of given sphere relative to this object. Calculates the tangential vector describing the direction and orientation of interface located perpendicalurlay to the line relying the centers of the spheres. Counter-clock wise orientation (but: attention to the orientation of the computer graphics device, which often invert the y-axis!)\n This method is defined in class `particleShear.Circle`""" n_vector = self.n(theSphere) return [-n_vector[1], n_vector[0]]
Ancestors
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearBase.CircleMass.CircleMass
Instance variables
var color
-
Color of the spheres
var doDrawing
-
Flag to indicate whether graphical output should be produced or not
var graphical_output_configuration
-
Graphical_output_configuration
to set options how the spheres and their neighboring relations are displayed var r
-
Radius of the circle, in pixels (=micrometers for the simulations)
var shape
-
Shape on the Tkinter canvas when actively drawing
var theCanvas
-
tkinter canvas
Methods
def coordinates(self)
-
Return the current coordinates: vector of three elements, which are (x,y,radius)
This method is defined in class
Circle
Expand source code
def coordinates(self): """Return the current coordinates: vector of three elements, which are (x,y,radius) This method is defined in class `particleShear.Circle`""" return [self.x, self.y, self.r]
def deleteDrawing(self)
-
Delete drawing on the Tkinter canvas
This method is defined in class
Circle
Expand source code
def deleteDrawing(self): """Delete drawing on the Tkinter canvas This method is defined in class `particleShear.Circle`""" if self.theCanvas: self.theCanvas.delete(self.shape)
def initiate_drawing(self)
-
Instiante the shapes representing this object on
Circle.theCanvas
.This method only has an effect if
Circle.doDrawing
is True. If a shape is created, the reference to the circle on the canvas is stored inCircle.shape
Expand source code
def initiate_drawing(self): """Instiante the shapes representing this object on `particleShear.Circle.theCanvas`. This method only has an effect if `particleShear.Circle.doDrawing` is True. If a shape is created, the reference to the circle on the canvas is stored in `particleShear.Circle.shape`""" self.deleteDrawing() self.shape = False if self.doDrawing: if self.graphical_output_configuration.draw_spheres_as=="spheres": self.shape = self.theCanvas.create_oval(self.x - self.r, self.y - self.r, self.x + self.r, self.y + self.r, fill=self.color) if self.graphical_output_configuration.draw_spheres_as=="dots": self.shape = self.theCanvas.create_oval(self.x - 2, self.y - 2, self.x + 2, self.y + 2, fill=self.color)
def move(self, dt=1)
-
Move for time interval dt, including the graphical shape if present
This method is defined in class
Circle
Expand source code
def move(self, dt=1): """Move for time interval dt, including the graphical shape if present This method is defined in class `particleShear.Circle`""" super(Circle,self).move(dt) if self.doDrawing: self.moveDrawing(dt)
def moveDrawing(self, dt)
-
Move for the drawing on the Tkinter canvas
This method is defined in class
Circle
Expand source code
def moveDrawing(self, dt): """Move for the drawing on the Tkinter canvas This method is defined in class `particleShear.Circle`""" self.theCanvas.move(self.shape, self.xspeed * dt, self.yspeed * dt)
def set_graphical_output_configuration(self, graphical_output_configuration)
-
Set new
Graphical_output_configuration
This method is defined in class
Circle
Expand source code
def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.Circle`""" self.deleteDrawing() self.graphical_output_configuration=graphical_output_configuration self.color=self.graphical_output_configuration.color_spheres_volume self.initiate_drawing()
def tangential_speed(self, theSphere)
-
Tangential component of the relative speed of a given sphere relative to this object.
The other sphere object needs to be of type
Point
or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation). This method usesCircle.tangential_vector()
internally.This method is defined in class
Circle
Expand source code
def tangential_speed(self, theSphere): """Tangential component of the relative speed of a given sphere relative to this object. The other sphere object needs to be of type `particleShear.Point` or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation). This method uses `particleShear.Circle.tangential_vector` internally.\n This method is defined in class `particleShear.Circle`""" v_rel = self.relative_speed(theSphere) vx_rel = v_rel[0] vy_rel = v_rel[1] t_vector = self.tangential_vector(theSphere) vt = vx_rel * t_vector[0] + vy_rel * t_vector[1] return vt
def tangential_vector(self, theSphere)
-
Return tangential interface vector of given sphere relative to this object.
Calculates the tangential vector describing the direction and orientation of interface located perpendicalurlay to the line relying the centers of the spheres. Counter-clock wise orientation (but: attention to the orientation of the computer graphics device, which often invert the y-axis!)
This method is defined in class
Circle
Expand source code
def tangential_vector(self, theSphere): """Return tangential interface vector of given sphere relative to this object. Calculates the tangential vector describing the direction and orientation of interface located perpendicalurlay to the line relying the centers of the spheres. Counter-clock wise orientation (but: attention to the orientation of the computer graphics device, which often invert the y-axis!)\n This method is defined in class `particleShear.Circle`""" n_vector = self.n(theSphere) return [-n_vector[1], n_vector[0]]
class CircleBasicElasticity (color, x, y, diameter, m=1, theCanvas=False, doDrawing=False, force_register=<particleShearBase.Force_register.Force_register object>, use_lees_edwards=False)
-
Basic friction-less sphere with drawing functions.
In the 2D approach chosen here, this a circle endowed with a central repulsive elastic force along with viscous dissipation if it comes into contact with another sphere. Inherits from
PointLeesEdwards
, so will respect Lees-Edwards boundary conditions providedPointLeesEdwards.use_lees_edwards
is set to True (default: False)Initialize self
- parameters
x
The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)y
The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)diameter
The diameter of the circle (in pixels, assumed micrometers for the simulation)m
Mass of the sphere in mg (per m of depth). For the 2D representation, this is typically the mass in mg of a 1m high cylinder of diameterdiameter
in micrometerstheCanvas
Tkinter canvas; provide False if no Canvas is available or no drawing is desireddoDrawing
Explicit switch to turn of drawingforce_register
Possibility to provide a reference to aForce_register
variable to register the forces calculated during simulationuse_lees_edwards
Explicit Shoud Lees-Edwards boundary conditions be used?Expand source code
class CircleBasicElasticity(CircleMassNeighbors): """Basic friction-less sphere with drawing functions. In the 2D approach chosen here, this a circle endowed with a central repulsive elastic force along with viscous dissipation if it comes into contact with another sphere. Inherits from `particleShear.PointLeesEdwards`, so will respect Lees-Edwards boundary conditions provided `particleShear.PointLeesEdwards.use_lees_edwards` is set to True (default: False)""" call_back_elastic_force_law = elastic_force_law """Possibility to pass a callback function to modify the force law""" def __init__(self, color, x, y, diameter, m=1,theCanvas=False, doDrawing=False,force_register=Force_register(), use_lees_edwards=False): """Initialize self - **parameters**\n `x` The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `y` The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `diameter` The diameter of the circle (in pixels, assumed micrometers for the simulation)\n `m` Mass of the sphere in mg (per m of depth). For the 2D representation, this is typically the mass in mg of a 1m high cylinder of diameter `diameter` in micrometers\n `theCanvas` Tkinter canvas; provide False if no Canvas is available or no drawing is desired\n `doDrawing` Explicit switch to turn of drawing\n `force_register`Possibility to provide a reference to a `particleShear.Force_register` variable to register the forces calculated during simulation\n `use_lees_edwards` Explicit Shoud Lees-Edwards boundary conditions be used?\n """ super(CircleBasicElasticity,self).__init__(color,x,y,diameter,m=m, theCanvas=theCanvas, doDrawing=doDrawing,force_register=force_register, use_lees_edwards=use_lees_edwards) self.central_repulsion_coefficient=0 """Central repulsion coefficient to help avoid spheres being able to fully penetrate and cross each other. The central_repulsion_coeffiecient is chosen from 0 (only linear law) to 1 (highest proportion of central repulsion). When central_repulsion_coefficient>0, there is a 1/x contribution to induce very strong repulsion when the spheres approach total compression and thus to avoid crossing of the spheres. For small compressions, even if central_repulsion_coefficient>0, the equations are chosen to give a dF/dx law with a slope of -k when the spheres just touch. See `particleShear.CircleBasicElasticity.get_elastic_force` further details""" # def get_elastic_force(self,theSphere,k=1): """Calculate magnitude of the central elastic force Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction (possible only with permanent interaction, not implemented in `particleShear.CircleBasicElasticity` but in some subclasses).\n Implementation of a central repulsive force: at large shear amplitudes and a strictly linear repulsion law, it may happen that spheres cross each other because the force is not enough to step movement despite the centers become nearly, or fully, identical. This is unphysical, and leads to entanglement between particles that we do no observe in this way. To avoid the effect, we need to add a component to the central elastic repulsive force that becomes very strong when the centers of the spheres get too close. At the same time, for small indentations, we aim at conserving a linear Hookes law (as in the paper underlying this implementation: see Eq. 2 from Otsuki, M. and H. Hayakawa,Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). Based on a 1/x law, we proceed as follows:\n The linear part is F=k*(x-xeq), providing the desired negative values for repulsion for x<xeq \n The nonlinear part is F=k*xeq*(1-xeq/x), which is also negative for xeq/x\n At x=xeq, both laws have dF/dx=k so that combining them in arbitrary mixing ratios does not change Hookes law for small indentations.\n This method is defined in class `particleShear.CircleBasicElasticity`""" d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] d0 = r + self.r return CircleBasicElasticity.call_back_elastic_force_law(d,d0,k,self.central_repulsion_coefficient) def elastic_force(self, theSphere, k): """Calculate and apply elastic force excerted on this sphere by `theSphere` This method will check for contact and so is more time consuming than `particleShear.CircleBasicElasticity.elastic_force_from_neighbors` \n This method is defined in class `particleShear.CircleBasicElasticity`""" d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] if d < r + self.r: force = self.get_elastic_force(theSphere,k) n_vector=self.n(theSphere) self.applyInternalForceAndRegister([force*n_vector[0], force*n_vector[1]],theSphere) def elastic_force_from_neighbors(self,k): """ Calculate the central elastic force from known neighbors. For this function, the neighbor relations need to be already established (via `particleShear.CircleMassNeighbors.test_neighbor_relation`).\n This method is defined in class `particleShear.CircleBasicElasticity`""" for neighbor in self.neighbors: self.elastic_force(neighbor.theSphere,k) # Eq. 3 from Otsuki, M. and H. Hayakawa, # Discontinuous change of shear modulus for frictional # jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. def central_viscous_force(self, theSphere, nu): """Calculate and apply the central viscous force on this sphere by a given sphere (`theSphere`) Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. This method will check for contact and so is more time consuming than `particleShear.CircleBasicElasticity.central_viscous_force_from_neighbors`\n This method is defined in class `particleShear.CircleBasicElasticity`. """ force = self.get_central_viscous_force(theSphere,nu) n_vector = self.n(theSphere) self.xforce = self.xforce + force * n_vector[0] self.yforce = self.yforce + force * n_vector[1] self.force_register.record_individual_internal_force(self,theSphere,[force * n_vector[0], force * n_vector[1]]) return [force * n_vector[0], force * n_vector[1]] def get_central_viscous_force(self, theSphere, nu): """Calculate and apply the central viscous force on this sphere by a given sphere (`theSphere`) Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The magnitude of the viscous force depends on the relative speed. The convention is similar to the central elastic force ( `particleShear.CircleBasicElasticity.get_elastic_force`). If the current object is repulsed by an approaching sphere (`theSphere`), a negative force is returned.\n This method is defined in class `particleShear.CircleBasicElasticity`. """ v_rel = self.relative_speed(theSphere) vx_rel = v_rel[0] vy_rel = v_rel[1] norm_vector = self.n(theSphere) v_norm = vx_rel * norm_vector[0] + vy_rel * norm_vector[1] force = nu * v_norm return force def central_viscous_force_from_neighbors(self, nu): """Calculate the central viscous force from known neighbors. For this function, the neighbor relations need to be already established (via `particleShear.CircleMassNeighbors.test_neighbor_relation`).\n This method is defined in class `particleShear.CircleBasicElasticity`""" for neighbor in self.neighbors: self.central_viscous_force(neighbor.theSphere,nu)
Ancestors
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearBase.CircleFrictionElasticity.CircleFrictionElasticity
- particleShearObjects.Sphere.Sphere
- particleShearObjects.SphereLeesEdwards.SphereLeesEdwards
Instance variables
var central_repulsion_coefficient
-
Central repulsion coefficient to help avoid spheres being able to fully penetrate and cross each other.
The central_repulsion_coeffiecient is chosen from 0 (only linear law) to 1 (highest proportion of central repulsion). When central_repulsion_coefficient>0, there is a 1/x contribution to induce very strong repulsion when the spheres approach total compression and thus to avoid crossing of the spheres. For small compressions, even if central_repulsion_coefficient>0, the equations are chosen to give a dF/dx law with a slope of -k when the spheres just touch. See
CircleBasicElasticity.get_elastic_force()
further details
Methods
def call_back_elastic_force_law(d, d0, k, central_repulsion_coefficient)
-
Default elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials)
Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction
-
parameters
d
Center-center distance of the interacting particlesd0
Equilibrium center-center distancek
Spring constantcentral_repulsion_coefficient
Possibility to have increased central repulsion at very closed distances.
Expand source code
def elastic_force_law(d, d0, k, central_repulsion_coefficient): """Default elastic force law with a plateau at mid range of compression (for simulation of sponge-like materials) Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction\n - **parameters**\n `d` Center-center distance of the interacting particles\n `d0` Equilibrium center-center distance\n `k` Spring constant\n `central_repulsion_coefficient` Possibility to have increased central repulsion at very closed distances.\n """ if d > d0: return 0 linear_force = -k * (1 - central_repulsion_coefficient) * (d0 - d) nonlinear_addition = -k * central_repulsion_coefficient * d0 * ( d0 / max(d, d0 / 1000) - 1) return linear_force + nonlinear_addition
-
def central_viscous_force(self, theSphere, nu)
-
Calculate and apply the central viscous force on this sphere by a given sphere (
theSphere
)Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. This method will check for contact and so is more time consuming than
CircleBasicElasticity.central_viscous_force_from_neighbors()
This method is defined in class
CircleBasicElasticity
.Expand source code
def central_viscous_force(self, theSphere, nu): """Calculate and apply the central viscous force on this sphere by a given sphere (`theSphere`) Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. This method will check for contact and so is more time consuming than `particleShear.CircleBasicElasticity.central_viscous_force_from_neighbors`\n This method is defined in class `particleShear.CircleBasicElasticity`. """ force = self.get_central_viscous_force(theSphere,nu) n_vector = self.n(theSphere) self.xforce = self.xforce + force * n_vector[0] self.yforce = self.yforce + force * n_vector[1] self.force_register.record_individual_internal_force(self,theSphere,[force * n_vector[0], force * n_vector[1]]) return [force * n_vector[0], force * n_vector[1]]
def central_viscous_force_from_neighbors(self, nu)
-
Calculate the central viscous force from known neighbors.
For this function, the neighbor relations need to be already established (via
CircleMassNeighbors.test_neighbor_relation()
).This method is defined in class
CircleBasicElasticity
Expand source code
def central_viscous_force_from_neighbors(self, nu): """Calculate the central viscous force from known neighbors. For this function, the neighbor relations need to be already established (via `particleShear.CircleMassNeighbors.test_neighbor_relation`).\n This method is defined in class `particleShear.CircleBasicElasticity`""" for neighbor in self.neighbors: self.central_viscous_force(neighbor.theSphere,nu)
def elastic_force(self, theSphere, k)
-
Calculate and apply elastic force excerted on this sphere by
theSphere
This method will check for contact and so is more time consuming than
CircleBasicElasticity.elastic_force_from_neighbors()
This method is defined in class
CircleBasicElasticity
Expand source code
def elastic_force(self, theSphere, k): """Calculate and apply elastic force excerted on this sphere by `theSphere` This method will check for contact and so is more time consuming than `particleShear.CircleBasicElasticity.elastic_force_from_neighbors` \n This method is defined in class `particleShear.CircleBasicElasticity`""" d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] if d < r + self.r: force = self.get_elastic_force(theSphere,k) n_vector=self.n(theSphere) self.applyInternalForceAndRegister([force*n_vector[0], force*n_vector[1]],theSphere)
def elastic_force_from_neighbors(self, k)
-
Calculate the central elastic force from known neighbors.
For this function, the neighbor relations need to be already established (via
CircleMassNeighbors.test_neighbor_relation()
).This method is defined in class
CircleBasicElasticity
Expand source code
def elastic_force_from_neighbors(self,k): """ Calculate the central elastic force from known neighbors. For this function, the neighbor relations need to be already established (via `particleShear.CircleMassNeighbors.test_neighbor_relation`).\n This method is defined in class `particleShear.CircleBasicElasticity`""" for neighbor in self.neighbors: self.elastic_force(neighbor.theSphere,k)
def get_central_viscous_force(self, theSphere, nu)
-
Calculate and apply the central viscous force on this sphere by a given sphere (
theSphere
)Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The magnitude of the viscous force depends on the relative speed. The convention is similar to the central elastic force (
CircleBasicElasticity.get_elastic_force()
). If the current object is repulsed by an approaching sphere (theSphere
), a negative force is returned.This method is defined in class
CircleBasicElasticity
.Expand source code
def get_central_viscous_force(self, theSphere, nu): """Calculate and apply the central viscous force on this sphere by a given sphere (`theSphere`) Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The magnitude of the viscous force depends on the relative speed. The convention is similar to the central elastic force ( `particleShear.CircleBasicElasticity.get_elastic_force`). If the current object is repulsed by an approaching sphere (`theSphere`), a negative force is returned.\n This method is defined in class `particleShear.CircleBasicElasticity`. """ v_rel = self.relative_speed(theSphere) vx_rel = v_rel[0] vy_rel = v_rel[1] norm_vector = self.n(theSphere) v_norm = vx_rel * norm_vector[0] + vy_rel * norm_vector[1] force = nu * v_norm return force
def get_elastic_force(self, theSphere, k=1)
-
Calculate magnitude of the central elastic force
Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction (possible only with permanent interaction, not implemented in
CircleBasicElasticity
but in some subclasses).Implementation of a central repulsive force: at large shear amplitudes and a strictly linear repulsion law, it may happen that spheres cross each other because the force is not enough to step movement despite the centers become nearly, or fully, identical. This is unphysical, and leads to entanglement between particles that we do no observe in this way. To avoid the effect, we need to add a component to the central elastic repulsive force that becomes very strong when the centers of the spheres get too close. At the same time, for small indentations, we aim at conserving a linear Hookes law (as in the paper underlying this implementation: see Eq. 2 from Otsuki, M. and H. Hayakawa,Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). Based on a 1/x law, we proceed as follows:
The linear part is F=k*(x-xeq), providing the desired negative values for repulsion for x<xeq
The nonlinear part is F=kxeq(1-xeq/x), which is also negative for xeq/x
At x=xeq, both laws have dF/dx=k so that combining them in arbitrary mixing ratios does not change Hookes law for small indentations.
This method is defined in class
CircleBasicElasticity
Expand source code
def get_elastic_force(self,theSphere,k=1): """Calculate magnitude of the central elastic force Scalar value of the elastic force exerted on this sphere. A negative value signifies that the current object is pushed back by the other sphere, a positive value signifies attraction (possible only with permanent interaction, not implemented in `particleShear.CircleBasicElasticity` but in some subclasses).\n Implementation of a central repulsive force: at large shear amplitudes and a strictly linear repulsion law, it may happen that spheres cross each other because the force is not enough to step movement despite the centers become nearly, or fully, identical. This is unphysical, and leads to entanglement between particles that we do no observe in this way. To avoid the effect, we need to add a component to the central elastic repulsive force that becomes very strong when the centers of the spheres get too close. At the same time, for small indentations, we aim at conserving a linear Hookes law (as in the paper underlying this implementation: see Eq. 2 from Otsuki, M. and H. Hayakawa,Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). Based on a 1/x law, we proceed as follows:\n The linear part is F=k*(x-xeq), providing the desired negative values for repulsion for x<xeq \n The nonlinear part is F=k*xeq*(1-xeq/x), which is also negative for xeq/x\n At x=xeq, both laws have dF/dx=k so that combining them in arbitrary mixing ratios does not change Hookes law for small indentations.\n This method is defined in class `particleShear.CircleBasicElasticity`""" d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] d0 = r + self.r return CircleBasicElasticity.call_back_elastic_force_law(d,d0,k,self.central_repulsion_coefficient)
class CircleFrictionElasticity (color, x, y, diameter, m=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>, use_lees_edwards=False)
-
Basic sphere with frictional interaction with drawing functions.
In the 2D approach chosen here, this is a circle endowed with a central viscous and elastic forces (inherited), and additionnally tangential friction forces and rotational movement. Inherits from
PointLeesEdwards
, so will respect Lees-Edwards boundary conditions providedPointLeesEdwards.use_lees_edwards
is set to True (default: False)Initialize self
- parameters
x
The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)y
The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)diameter
The diameter of the circle (in pixels, assumed micrometers for the simulation)m
Mass of the sphere in mg (per m of depth). For the 2D representation, this is typically the mass in mg of a 1m high cylinder of diameterdiameter
in micrometerstheCanvas
Tkinter canvas; provide False if no Canvas is available or no drawing is desireddoDrawing
Explicit switch to turn of drawingforce_register
Possibility to provide a reference to aForce_register
variable to register the forces calculated during simulationuse_lees_edwards
Explicit Should Lees-Edwards boundary conditions be used?Expand source code
class CircleFrictionElasticity(CircleBasicElasticity): """Basic sphere with frictional interaction with drawing functions. In the 2D approach chosen here, this is a circle endowed with a central viscous and elastic forces (inherited), and additionnally tangential friction forces and rotational movement. Inherits from `particleShear.PointLeesEdwards`, so will respect Lees-Edwards boundary conditions provided `particleShear.PointLeesEdwards.use_lees_edwards` is set to True (default: False)""" def __init__(self, color, x, y, diameter, m=1, theCanvas=FALSE, doDrawing=FALSE,force_register=Force_register(), use_lees_edwards=False): """Initialize self - **parameters**\n `x` The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `y` The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `diameter` The diameter of the circle (in pixels, assumed micrometers for the simulation)\n `m` Mass of the sphere in mg (per m of depth). For the 2D representation, this is typically the mass in mg of a 1m high cylinder of diameter `diameter` in micrometers\n `theCanvas` Tkinter canvas; provide False if no Canvas is available or no drawing is desired\n `doDrawing` Explicit switch to turn of drawing\n `force_register`Possibility to provide a reference to a `particleShear.Force_register` variable to register the forces calculated during simulation\n `use_lees_edwards` Explicit Should Lees-Edwards boundary conditions be used?\n """ self.phi = -math.pi/2 """Rotational position""" self.rotation_line = False """Should a line be drawn to indicate the current rotational position?""" self.omega=0 """Current rotation speed, in rad/s""" self.torque=0 """Current torque, in microN*m / (m of depth) for the simulation""" super(CircleFrictionElasticity, self).__init__(color, x, y, diameter, m, theCanvas, doDrawing, force_register, use_lees_edwards=use_lees_edwards) self.inertia = self.m * self.r * self.r / 2 """Rotational moment of inertia in mg*micrometers^2 / (m of depth)""" def cool(self,f=0.8,shear_rate_to_rotation_rate_coefficient=0): """Cool by decreasing local speed and rotation rate By inheritage from `particleShear.PointLeesEdwards` this includes SLLOD type of cooling, where only movement relative to the shearing environment is performed. This function adds cooling on particle rotation. There is some incertitude in the literature as to how the rotational aspect of shear should be counted, this can be addressed by introducing a conversion of shear rate to anticipated local rotation. By default, this is not used and rotation damped towards zero (shear_rate_to_rotation_rate_coefficient=0).\n This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity,self).cool(f) omega_local = (self.shear_rate*(self.y-self.size_y/2))*shear_rate_to_rotation_rate_coefficient self.omega=omega_local+f*(self.omega-omega_local) def initiate_drawing(self): """Instiante the shapes representing this object on `particleShear.Circle.theCanvas`. This method only has an effect if `particleShear.Circle.doDrawing` is True. If a shape is created, the reference to the circle on the canvas is stored in `particleShear.Circle.shape`. Compared to its parent method ( `particleShear.Circle.initiate_drawing` defined in `particleShear.Circle`), here, a line indicating the rotational position `particleShear.CircleFrictionElasticity.phi` is optionnally added. This method is defined in class `particleShear.CircleFrictionElasticity`""" if self.doDrawing: super(CircleFrictionElasticity,self).initiate_drawing() if self.graphical_output_configuration.draw_rotation_line: pos = self.coordinates() x = pos[0] y = pos[1] self.rotation_line = self.theCanvas.create_line(x, y, x + self.r * math.cos(self.phi), y + self.r * math.sin(self.phi), fill=self.graphical_output_configuration.color_rotation_line ) def moveDrawing(self, dt): """Move for the drawing on the Tkinter canvas This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity, self).moveDrawing(dt) self.theCanvas.delete(self.rotation_line) if self.graphical_output_configuration.draw_rotation_line: pos = self.coordinates() x = pos[0] y = pos[1] self.rotation_line = self.theCanvas.create_line(x, y, x + self.r * math.cos(self.phi), y + self.r * math.sin(self.phi), fill=self.graphical_output_configuration.color_rotation_line) def deleteDrawing(self): """Delete drawing on the Tkinter canvas This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity, self).deleteDrawing() if self.theCanvas: self.theCanvas.delete(self.rotation_line) def tangential_speed(self, theSphere): """Tangential component of the relative speed of a given sphere relative to this object. The other sphere object needs to be of type `particleShear.CircleFrictionElasticity` or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation).\n This method adds to different origins of tangential velocity, as explained by eq. 6 in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902: The first contribution is the difference in linear velocity of the two involved spheres, projected onto the common tangential interface. This contribution is evaluated by invoking the parent method `particleShear.Circle.tangential_speed` defined in `particleShear.Circle`. The second part arises due to the rotation of the two neighboring spheres; this part is calculated and added here.\n This method is defined in class `particleShear.CircleFrictionElasticity`""" vt=super(CircleFrictionElasticity,self).tangential_speed(theSphere) pos=theSphere.coordinates() r=pos[2] return vt-(self.r*self.omega+r*theSphere.omega) def tangential_force(self, theSphere, nu,mu,k,k_t): """Calculate and appropriately distribute the tangential force of `theSphere` on the current object The calculation of the tangential force is calculated in a manner to ensure a high level of conservation of angular and linear momentum, including possible rounding errors. For this, it is safest to distribute the forces in a manner that conserves angular momentum and linear momentum rather than anticipating that an opposing force will arise when the same interface is considered from the point of view of the partner sphere. It could for instance happen that the other sphere considers the same interface as slipping while here it is locked and so the magnitude of force would be different.\n So when calculating the interface from the perspective of the current sphere, we count half the force but distribute it immediately and with conservation of both linear and angular momentum to the two involved spheres, by using the `particleShear.CircleFrictionElasticity.distribute_tangential_couple`. The same interface will be visited again from the perspective of `theSphere`, but even if the force magnitude is slightly different due to numerical or simulation imprecision, this will not generate a net linear or angular momentum. \n To further reduce chances of erroneous calculation, changes in slip / locked status of the interface are immediately transmitted to the partner interface under control of the partner sphere.\n Compared to Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902, this is intended to be the same torque compensation mechanism (eq. 8 of Otsuki et al.) except for that the actual separation distance is used and so angular momentum is conserved exactly, not just approximately.\n This method is defined in class `particleShear.CircleFrictionElasticity` """ d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] foundNeighborIndex=self.findNeighborIndex(theSphere) found = foundNeighborIndex >= 0 if found: # Check whether symmetric backlink exists backlink=self.neighbors[foundNeighborIndex].theSphere.findNeighborIndex(self) if(not backlink>=0): print("SphereLinkable: Asymmetric neighbor problem") if d < r + self.r: if not found: print("tangential_force: Problem with neighbors, found touching sphere not in neighbors list") return viscous_force=self.tangential_force_viscous(theSphere,nu) if abs(viscous_force-theSphere.tangential_force_viscous(self,nu)) > 1e-15: print("viscous force mis-match",viscous_force-theSphere.tangential_force_viscous(self,nu)) elastic_force=self.tangential_force_elastic(theSphere,k_t) total_adherence_force = viscous_force+elastic_force friction_force=self.get_elastic_force(theSphere, k)*mu sig=1 if(total_adherence_force<0): sig=-1 if(abs(total_adherence_force)<=abs(friction_force)): self.neighbors[foundNeighborIndex].interface_type = "stick" theSphere.neighbors[backlink].interface_type = "stick" else: self.neighbors[foundNeighborIndex].interface_type = "slip" self.neighbors[foundNeighborIndex].friction_position=0 theSphere.neighbors[backlink].interface_type = "slip" theSphere.neighbors[backlink].friction_position = 0 force=min(abs(total_adherence_force),abs(friction_force)) force=force*sig # The idea here is that the same friction point will be visited again when the neighboring sphere is examined, # so we assign only half the force here # The distribution is a bit complicated because of the conservation of angular momentum, so we use a dedicated function self.distribute_tangential_couple(theSphere, force/2) def tangential_force_from_neighbors(self, nu,mu,k,k_t): """Calculate and appropriately distribute the tangential forces from all known neighbors This method is defined in class `particleShear.CircleFrictionElasticity` """ for neighbor in self.neighbors: self.tangential_force(neighbor.theSphere,nu,mu,k,k_t) def tangential_force_viscous(self, theSphere, nu): """Return the scalar value of the tangential vicous drag force on the current object by `theSphere This method is defined in class `particleShear.CircleFrictionElasticity` """ d = self.d(theSphere) pos=theSphere.coordinates() r=pos[2] if d < r + self.r: return nu*self.tangential_speed(theSphere) else: return 0 def distribute_tangential_couple(self, theSphere, force): """Distribution of a frictional couple on the two involved spheres The distribution of a frictional (tangential) couple of forces is a bit complicated because of the necessity of conservation of angular momentum. The friction force couple arises at a single point at the common contact surface of the two spheres, so it adds zero total angular momentum change as it ought to be for an internal force. It leads however to acceleration of the involved spheres, simulated by forces applied to the sphere center. This centrally applied force couple DOES apply a total torque, this is separation_distance * force. The separation_distance can be quite different from r1 + r2 since the spheres can be quite substantially compressed in the case of large packing densities, and so applying a compensatory torque of -r1*force respectively -r2*force as proposed by Otsuki et al. only partially solves the issue and leads to a net angular momentum that may add spuriously to the measured stress since this tends to turn the entire ensemble.\n The question of the actual distribution of the torque to the two spheres is a tricky one, since it is not a priori clear where the actual contact line will be (unequal compression of the two partners). To simplify, and for consistence with the solution by Otsuki et al. which is correct for non-compressed spheres, we distribute the torque proportional to the original particle radii\n This method is defined in class `particleShear.CircleFrictionElasticity`\n Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902 """ t_vector = self.tangential_vector(theSphere) self.xforce = self.xforce + force * t_vector[0] self.yforce = self.yforce + force * t_vector[1] theSphere.xforce = theSphere.xforce - force * t_vector[0] theSphere.yforce = theSphere.yforce - force * t_vector[1] self.force_register.record_individual_internal_force(self, theSphere, [force * t_vector[0], force * t_vector[1]] ) self.force_register.record_individual_internal_force(theSphere, self, [-force * t_vector[0], -force * t_vector[1]] ) separation_distance=self.d(theSphere) # The equal distribution of the torque is an approximation that is OK if the spheres have about the same size # This might need reconsideration if spheres of very different sizes are used. However, the most important # is conservation of angular momentum, so the sum of the distribution factors must necessarily be 1. T_self = separation_distance * force*self.r/(self.r+theSphere.r) T_theSphere = separation_distance * force*theSphere.r/(self.r+theSphere.r) self.torque = self.torque + T_self theSphere.torque = theSphere.torque+T_theSphere self.force_register.record_individual_internal_torque(self,theSphere,T_self) self.force_register.record_individual_internal_torque(theSphere,self,T_theSphere) def tangential_force_elastic(self, theSphere, k_t): """Return the scalar value of the tangential elastic force on the current object by `theSphere` This method implements the elastic part of eq. 5 in Otsuki et al. This method is defined in class `particleShear.CircleFrictionElasticity`\n Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902 """ d = self.d(theSphere) pos=theSphere.coordinates() r=pos[2] if d < r + self.r: found = FALSE foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex found = TRUE if not found: print("Problem with neighbors, found touching sphere not in neighbors list") else: return k_t*self.neighbors[foundNeighborIndex].friction_position else: return 0 def do_rotational_acceleration(self, dt): """Calculate and apply rotational accelaration from the accumulated torque This method is defined in class `particleShear.CircleFrictionElasticity` """ self.omega = self.omega + self.torque/self.inertia*dt self.force_register.record_unbalanced_moment(self,self.torque) self.torque = 0 def move(self, dt=1): """Move for time interval dt, including rotation, and including the graphical shape if present This method is defined in class `particleShear.CircleFrictionElasticity`""" self.phi = self.phi + self.omega * dt for theNeighbor in self.neighbors: if theNeighbor.interface_type=="stick" or theNeighbor.interface_type=="permanent": # This is not correct, to be corrected theNeighbor.friction_position=theNeighbor.friction_position+self.tangential_speed(theNeighbor.theSphere)*dt super(CircleFrictionElasticity, self).move(dt)
Ancestors
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearObjects.SphereFriction.SphereFriction
- particleShearObjects.SphereFrictionLeesEdwards.SphereFrictionLeesEdwards
Instance variables
var inertia
-
Rotational moment of inertia in mg*micrometers^2 / (m of depth)
var omega
-
Current rotation speed, in rad/s
var phi
-
Rotational position
var rotation_line
-
Should a line be drawn to indicate the current rotational position?
var torque
-
Current torque, in microN*m / (m of depth) for the simulation
Methods
def cool(self, f=0.8, shear_rate_to_rotation_rate_coefficient=0)
-
Cool by decreasing local speed and rotation rate
By inheritage from
PointLeesEdwards
this includes SLLOD type of cooling, where only movement relative to the shearing environment is performed. This function adds cooling on particle rotation. There is some incertitude in the literature as to how the rotational aspect of shear should be counted, this can be addressed by introducing a conversion of shear rate to anticipated local rotation. By default, this is not used and rotation damped towards zero (shear_rate_to_rotation_rate_coefficient=0).This method is defined in class
CircleFrictionElasticity
Expand source code
def cool(self,f=0.8,shear_rate_to_rotation_rate_coefficient=0): """Cool by decreasing local speed and rotation rate By inheritage from `particleShear.PointLeesEdwards` this includes SLLOD type of cooling, where only movement relative to the shearing environment is performed. This function adds cooling on particle rotation. There is some incertitude in the literature as to how the rotational aspect of shear should be counted, this can be addressed by introducing a conversion of shear rate to anticipated local rotation. By default, this is not used and rotation damped towards zero (shear_rate_to_rotation_rate_coefficient=0).\n This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity,self).cool(f) omega_local = (self.shear_rate*(self.y-self.size_y/2))*shear_rate_to_rotation_rate_coefficient self.omega=omega_local+f*(self.omega-omega_local)
def deleteDrawing(self)
-
Delete drawing on the Tkinter canvas
This method is defined in class
CircleFrictionElasticity
Expand source code
def deleteDrawing(self): """Delete drawing on the Tkinter canvas This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity, self).deleteDrawing() if self.theCanvas: self.theCanvas.delete(self.rotation_line)
def distribute_tangential_couple(self, theSphere, force)
-
Distribution of a frictional couple on the two involved spheres
The distribution of a frictional (tangential) couple of forces is a bit complicated because of the necessity of conservation of angular momentum. The friction force couple arises at a single point at the common contact surface of the two spheres, so it adds zero total angular momentum change as it ought to be for an internal force. It leads however to acceleration of the involved spheres, simulated by forces applied to the sphere center. This centrally applied force couple DOES apply a total torque, this is separation_distance * force. The separation_distance can be quite different from r1 + r2 since the spheres can be quite substantially compressed in the case of large packing densities, and so applying a compensatory torque of -r1force respectively -r2force as proposed by Otsuki et al. only partially solves the issue and leads to a net angular momentum that may add spuriously to the measured stress since this tends to turn the entire ensemble.
The question of the actual distribution of the torque to the two spheres is a tricky one, since it is not a priori clear where the actual contact line will be (unequal compression of the two partners). To simplify, and for consistence with the solution by Otsuki et al. which is correct for non-compressed spheres, we distribute the torque proportional to the original particle radii
This method is defined in class
CircleFrictionElasticity
Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902
Expand source code
def distribute_tangential_couple(self, theSphere, force): """Distribution of a frictional couple on the two involved spheres The distribution of a frictional (tangential) couple of forces is a bit complicated because of the necessity of conservation of angular momentum. The friction force couple arises at a single point at the common contact surface of the two spheres, so it adds zero total angular momentum change as it ought to be for an internal force. It leads however to acceleration of the involved spheres, simulated by forces applied to the sphere center. This centrally applied force couple DOES apply a total torque, this is separation_distance * force. The separation_distance can be quite different from r1 + r2 since the spheres can be quite substantially compressed in the case of large packing densities, and so applying a compensatory torque of -r1*force respectively -r2*force as proposed by Otsuki et al. only partially solves the issue and leads to a net angular momentum that may add spuriously to the measured stress since this tends to turn the entire ensemble.\n The question of the actual distribution of the torque to the two spheres is a tricky one, since it is not a priori clear where the actual contact line will be (unequal compression of the two partners). To simplify, and for consistence with the solution by Otsuki et al. which is correct for non-compressed spheres, we distribute the torque proportional to the original particle radii\n This method is defined in class `particleShear.CircleFrictionElasticity`\n Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902 """ t_vector = self.tangential_vector(theSphere) self.xforce = self.xforce + force * t_vector[0] self.yforce = self.yforce + force * t_vector[1] theSphere.xforce = theSphere.xforce - force * t_vector[0] theSphere.yforce = theSphere.yforce - force * t_vector[1] self.force_register.record_individual_internal_force(self, theSphere, [force * t_vector[0], force * t_vector[1]] ) self.force_register.record_individual_internal_force(theSphere, self, [-force * t_vector[0], -force * t_vector[1]] ) separation_distance=self.d(theSphere) # The equal distribution of the torque is an approximation that is OK if the spheres have about the same size # This might need reconsideration if spheres of very different sizes are used. However, the most important # is conservation of angular momentum, so the sum of the distribution factors must necessarily be 1. T_self = separation_distance * force*self.r/(self.r+theSphere.r) T_theSphere = separation_distance * force*theSphere.r/(self.r+theSphere.r) self.torque = self.torque + T_self theSphere.torque = theSphere.torque+T_theSphere self.force_register.record_individual_internal_torque(self,theSphere,T_self) self.force_register.record_individual_internal_torque(theSphere,self,T_theSphere)
def do_rotational_acceleration(self, dt)
-
Calculate and apply rotational accelaration from the accumulated torque
This method is defined in class
CircleFrictionElasticity
Expand source code
def do_rotational_acceleration(self, dt): """Calculate and apply rotational accelaration from the accumulated torque This method is defined in class `particleShear.CircleFrictionElasticity` """ self.omega = self.omega + self.torque/self.inertia*dt self.force_register.record_unbalanced_moment(self,self.torque) self.torque = 0
def initiate_drawing(self)
-
Instiante the shapes representing this object on
Circle.theCanvas
.This method only has an effect if
Circle.doDrawing
is True. If a shape is created, the reference to the circle on the canvas is stored inCircle.shape
. Compared to its parent method (Circle.initiate_drawing()
defined inCircle
), here, a line indicating the rotational positionCircleFrictionElasticity.phi
is optionnally added. This method is defined in classCircleFrictionElasticity
Expand source code
def initiate_drawing(self): """Instiante the shapes representing this object on `particleShear.Circle.theCanvas`. This method only has an effect if `particleShear.Circle.doDrawing` is True. If a shape is created, the reference to the circle on the canvas is stored in `particleShear.Circle.shape`. Compared to its parent method ( `particleShear.Circle.initiate_drawing` defined in `particleShear.Circle`), here, a line indicating the rotational position `particleShear.CircleFrictionElasticity.phi` is optionnally added. This method is defined in class `particleShear.CircleFrictionElasticity`""" if self.doDrawing: super(CircleFrictionElasticity,self).initiate_drawing() if self.graphical_output_configuration.draw_rotation_line: pos = self.coordinates() x = pos[0] y = pos[1] self.rotation_line = self.theCanvas.create_line(x, y, x + self.r * math.cos(self.phi), y + self.r * math.sin(self.phi), fill=self.graphical_output_configuration.color_rotation_line )
def move(self, dt=1)
-
Move for time interval dt, including rotation, and including the graphical shape if present
This method is defined in class
CircleFrictionElasticity
Expand source code
def move(self, dt=1): """Move for time interval dt, including rotation, and including the graphical shape if present This method is defined in class `particleShear.CircleFrictionElasticity`""" self.phi = self.phi + self.omega * dt for theNeighbor in self.neighbors: if theNeighbor.interface_type=="stick" or theNeighbor.interface_type=="permanent": # This is not correct, to be corrected theNeighbor.friction_position=theNeighbor.friction_position+self.tangential_speed(theNeighbor.theSphere)*dt super(CircleFrictionElasticity, self).move(dt)
def moveDrawing(self, dt)
-
Move for the drawing on the Tkinter canvas
This method is defined in class
CircleFrictionElasticity
Expand source code
def moveDrawing(self, dt): """Move for the drawing on the Tkinter canvas This method is defined in class `particleShear.CircleFrictionElasticity`""" super(CircleFrictionElasticity, self).moveDrawing(dt) self.theCanvas.delete(self.rotation_line) if self.graphical_output_configuration.draw_rotation_line: pos = self.coordinates() x = pos[0] y = pos[1] self.rotation_line = self.theCanvas.create_line(x, y, x + self.r * math.cos(self.phi), y + self.r * math.sin(self.phi), fill=self.graphical_output_configuration.color_rotation_line)
def tangential_force(self, theSphere, nu, mu, k, k_t)
-
Calculate and appropriately distribute the tangential force of
theSphere
on the current objectThe calculation of the tangential force is calculated in a manner to ensure a high level of conservation of angular and linear momentum, including possible rounding errors. For this, it is safest to distribute the forces in a manner that conserves angular momentum and linear momentum rather than anticipating that an opposing force will arise when the same interface is considered from the point of view of the partner sphere. It could for instance happen that the other sphere considers the same interface as slipping while here it is locked and so the magnitude of force would be different.
So when calculating the interface from the perspective of the current sphere, we count half the force but distribute it immediately and with conservation of both linear and angular momentum to the two involved spheres, by using the
CircleFrictionElasticity.distribute_tangential_couple()
. The same interface will be visited again from the perspective oftheSphere
, but even if the force magnitude is slightly different due to numerical or simulation imprecision, this will not generate a net linear or angular momentum.To further reduce chances of erroneous calculation, changes in slip / locked status of the interface are immediately transmitted to the partner interface under control of the partner sphere.
Compared to Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902, this is intended to be the same torque compensation mechanism (eq. 8 of Otsuki et al.) except for that the actual separation distance is used and so angular momentum is conserved exactly, not just approximately.
This method is defined in class
CircleFrictionElasticity
Expand source code
def tangential_force(self, theSphere, nu,mu,k,k_t): """Calculate and appropriately distribute the tangential force of `theSphere` on the current object The calculation of the tangential force is calculated in a manner to ensure a high level of conservation of angular and linear momentum, including possible rounding errors. For this, it is safest to distribute the forces in a manner that conserves angular momentum and linear momentum rather than anticipating that an opposing force will arise when the same interface is considered from the point of view of the partner sphere. It could for instance happen that the other sphere considers the same interface as slipping while here it is locked and so the magnitude of force would be different.\n So when calculating the interface from the perspective of the current sphere, we count half the force but distribute it immediately and with conservation of both linear and angular momentum to the two involved spheres, by using the `particleShear.CircleFrictionElasticity.distribute_tangential_couple`. The same interface will be visited again from the perspective of `theSphere`, but even if the force magnitude is slightly different due to numerical or simulation imprecision, this will not generate a net linear or angular momentum. \n To further reduce chances of erroneous calculation, changes in slip / locked status of the interface are immediately transmitted to the partner interface under control of the partner sphere.\n Compared to Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902, this is intended to be the same torque compensation mechanism (eq. 8 of Otsuki et al.) except for that the actual separation distance is used and so angular momentum is conserved exactly, not just approximately.\n This method is defined in class `particleShear.CircleFrictionElasticity` """ d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] foundNeighborIndex=self.findNeighborIndex(theSphere) found = foundNeighborIndex >= 0 if found: # Check whether symmetric backlink exists backlink=self.neighbors[foundNeighborIndex].theSphere.findNeighborIndex(self) if(not backlink>=0): print("SphereLinkable: Asymmetric neighbor problem") if d < r + self.r: if not found: print("tangential_force: Problem with neighbors, found touching sphere not in neighbors list") return viscous_force=self.tangential_force_viscous(theSphere,nu) if abs(viscous_force-theSphere.tangential_force_viscous(self,nu)) > 1e-15: print("viscous force mis-match",viscous_force-theSphere.tangential_force_viscous(self,nu)) elastic_force=self.tangential_force_elastic(theSphere,k_t) total_adherence_force = viscous_force+elastic_force friction_force=self.get_elastic_force(theSphere, k)*mu sig=1 if(total_adherence_force<0): sig=-1 if(abs(total_adherence_force)<=abs(friction_force)): self.neighbors[foundNeighborIndex].interface_type = "stick" theSphere.neighbors[backlink].interface_type = "stick" else: self.neighbors[foundNeighborIndex].interface_type = "slip" self.neighbors[foundNeighborIndex].friction_position=0 theSphere.neighbors[backlink].interface_type = "slip" theSphere.neighbors[backlink].friction_position = 0 force=min(abs(total_adherence_force),abs(friction_force)) force=force*sig # The idea here is that the same friction point will be visited again when the neighboring sphere is examined, # so we assign only half the force here # The distribution is a bit complicated because of the conservation of angular momentum, so we use a dedicated function self.distribute_tangential_couple(theSphere, force/2)
def tangential_force_elastic(self, theSphere, k_t)
-
Return the scalar value of the tangential elastic force on the current object by
theSphere
This method implements the elastic part of eq. 5 in Otsuki et al. This method is defined in class
CircleFrictionElasticity
Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902
Expand source code
def tangential_force_elastic(self, theSphere, k_t): """Return the scalar value of the tangential elastic force on the current object by `theSphere` This method implements the elastic part of eq. 5 in Otsuki et al. This method is defined in class `particleShear.CircleFrictionElasticity`\n Otsuki et al. is Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902 """ d = self.d(theSphere) pos=theSphere.coordinates() r=pos[2] if d < r + self.r: found = FALSE foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex found = TRUE if not found: print("Problem with neighbors, found touching sphere not in neighbors list") else: return k_t*self.neighbors[foundNeighborIndex].friction_position else: return 0
def tangential_force_from_neighbors(self, nu, mu, k, k_t)
-
Calculate and appropriately distribute the tangential forces from all known neighbors
This method is defined in class
CircleFrictionElasticity
Expand source code
def tangential_force_from_neighbors(self, nu,mu,k,k_t): """Calculate and appropriately distribute the tangential forces from all known neighbors This method is defined in class `particleShear.CircleFrictionElasticity` """ for neighbor in self.neighbors: self.tangential_force(neighbor.theSphere,nu,mu,k,k_t)
def tangential_force_viscous(self, theSphere, nu)
-
Return the scalar value of the tangential vicous drag force on the current object by `theSphere
This method is defined in class
CircleFrictionElasticity
Expand source code
def tangential_force_viscous(self, theSphere, nu): """Return the scalar value of the tangential vicous drag force on the current object by `theSphere This method is defined in class `particleShear.CircleFrictionElasticity` """ d = self.d(theSphere) pos=theSphere.coordinates() r=pos[2] if d < r + self.r: return nu*self.tangential_speed(theSphere) else: return 0
def tangential_speed(self, theSphere)
-
Tangential component of the relative speed of a given sphere relative to this object.
The other sphere object needs to be of type
CircleFrictionElasticity
or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation).This method adds to different origins of tangential velocity, as explained by eq. 6 in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902: The first contribution is the difference in linear velocity of the two involved spheres, projected onto the common tangential interface. This contribution is evaluated by invoking the parent method
Circle.tangential_speed()
defined inCircle
. The second part arises due to the rotation of the two neighboring spheres; this part is calculated and added here.This method is defined in class
CircleFrictionElasticity
Expand source code
def tangential_speed(self, theSphere): """Tangential component of the relative speed of a given sphere relative to this object. The other sphere object needs to be of type `particleShear.CircleFrictionElasticity` or derived. Returns a scalar, describing the relative speed component parallel to a tangential vector with counter-clockwise orientation. The speed is returned in pixels/s (= micrometers/s for the simulation).\n This method adds to different origins of tangential velocity, as explained by eq. 6 in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902: The first contribution is the difference in linear velocity of the two involved spheres, projected onto the common tangential interface. This contribution is evaluated by invoking the parent method `particleShear.Circle.tangential_speed` defined in `particleShear.Circle`. The second part arises due to the rotation of the two neighboring spheres; this part is calculated and added here.\n This method is defined in class `particleShear.CircleFrictionElasticity`""" vt=super(CircleFrictionElasticity,self).tangential_speed(theSphere) pos=theSphere.coordinates() r=pos[2] return vt-(self.r*self.omega+r*theSphere.omega)
class CircleMass (color, x, y, diameter, m=1, theCanvas=False, doDrawing=False, force_register=<particleShearBase.Force_register.Force_register object>, use_lees_edwards=False)
-
Provide a circle with an attribued mass
Describes a sphere (circle in 2D) that possess a mass and stores the forces acting on the sphere.
In a simulation, the concept is that before each simulation step, the force known to each sphere itself (the ìnstance variables
CircleMass.xforce
andCircleMass.yforce
) are reset to 0; as the spheres are meant to be associated with a simulation area object derived fromCanvasPointsMass
, this typically happens through a call to the methodCanvasPointsMass.reset_force()
ofCanvasPointsMass
or relevant subclass. In addition, for the purpose of stress tensor evaluation, details about the forces are registered during the various calculations in a simulation step in theCircleMass.force_register
, which is provided through the canvas class (derived fromCanvasPointsMass
). At the beginning of each simulation step, the force register common to all the spheres on a simulaton canvas also needs to be reset, this is done via theCanvasPointsMass.reset_force_register()
ofCanvasPointsMass
or relevant subclass.When running the simulation, forces are calculated in various ways, and attributed to the sphere particles by direct change of the instance variables
CircleMass.xforce
andCircleMass.yforce
. Depending on their nature, they are also registered in various ways in the commonForce_register
object, which is the instance variableCanvasPointsMass.force_register
of the relevant subclass ofCanvasPointsMass
; for practical purposes, this very sameForce_register
is also known to each sphere as its own instance variableCircleMass.force_register
.Once all the forces known after a complete simulation step, the net force can be converted to acceleration (by explicit call to
CircleMass.do_linear_acceleration()
). This then calculates the change in speed due to the acceleration, and resets the force for the particle at hand to zero. At this point, theCircleMass.force_register
, common to all the spheres, still contains the detailed information about the forces and can be exploited for stress tensor evaluation by aStressTensorEvaluation
object. TheCircleMass.force_register
needs to be reset separately prior to the next simulation step, see above.Inherits from
PointLeesEdwards
, so will respect Lees-Edwards boundary conditions providedPointLeesEdwards.use_lees_edwards
is set to True (default: False)Initialize self
- parameters
x
The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)y
The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)diameter
The diameter of the circle (in pixels, assumed micrometers for the simulation)m
is the mass per unit of depth in mg/m of depththeCanvas
Tkinter canvas; provide False if no Canvas is available or no drawing is desireddoDrawing
Explicit switch to turn of drawingforce_register
Possibility to provide a reference to a commonForce_register
variable to register the forces calculated during simulationuse_lees_edwards
Explicit Shoud Lees-Edwards boundary conditions be used?Expand source code
class CircleMass(Circle): """Provide a circle with an attribued mass Describes a sphere (circle in 2D) that possess a mass and stores the forces acting on the sphere. \n In a simulation, the concept is that before each simulation step, the force known to each sphere itself (the ìnstance variables `particleShear.CircleMass.xforce` and `particleShear.CircleMass.yforce`) are reset to 0; as the spheres are meant to be associated with a simulation area object derived from `particleShear.CanvasPointsMass`, this typically happens through a call to the method `particleShear.CanvasPointsMass.reset_force` of `particleShear.CanvasPointsMass` or relevant subclass. In addition, for the purpose of stress tensor evaluation, details about the forces are registered during the various calculations in a simulation step in the `particleShear.CircleMass.force_register`, which is provided through the canvas class (derived from `particleShear.CanvasPointsMass`). At the beginning of each simulation step, the force register common to all the spheres on a simulaton canvas also needs to be reset, this is done via the `particleShear.CanvasPointsMass.reset_force_register` of `particleShear.CanvasPointsMass` or relevant subclass.\n When running the simulation, forces are calculated in various ways, and attributed to the sphere particles by direct change of the instance variables `particleShear.CircleMass.xforce` and `particleShear.CircleMass.yforce`. Depending on their nature, they are also registered in various ways in the common `particleShear.Force_register` object, which is the instance variable `particleShear.CanvasPointsMass.force_register` of the relevant subclass of `particleShear.CanvasPointsMass`; for practical purposes, this very same `particleShear.Force_register` is also known to each sphere as its own instance variable `particleShear.CircleMass.force_register`.\n Once all the forces known after a complete simulation step, the net force can be converted to acceleration (by explicit call to `particleShear.CircleMass.do_linear_acceleration`). This then calculates the change in speed due to the acceleration, and resets the force for the particle at hand to zero. At this point, the `particleShear.CircleMass.force_register`, common to all the spheres, still contains the detailed information about the forces and can be exploited for stress tensor evaluation by a `particleShear.StressTensorEvaluation` object. The `particleShear.CircleMass.force_register` needs to be reset separately prior to the next simulation step, see above.\n Inherits from `particleShear.PointLeesEdwards`, so will respect Lees-Edwards boundary conditions provided `particleShear.PointLeesEdwards.use_lees_edwards` is set to True (default: False)""" def __init__(self, color, x, y, diameter, m=1, theCanvas=False, doDrawing=False,force_register=Force_register(), use_lees_edwards=False): """Initialize self - **parameters**\n `x` The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `y` The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)\n `diameter` The diameter of the circle (in pixels, assumed micrometers for the simulation)\n `m` is the mass per unit of depth in mg/m of depth\n `theCanvas` Tkinter canvas; provide False if no Canvas is available or no drawing is desired\n `doDrawing` Explicit switch to turn of drawing\n `force_register`Possibility to provide a reference to a common `particleShear.Force_register` variable to register the forces calculated during simulation\n `use_lees_edwards` Explicit Shoud Lees-Edwards boundary conditions be used?\n """ super(CircleMass,self).__init__(color,x,y,diameter, theCanvas=theCanvas, doDrawing=doDrawing, use_lees_edwards=use_lees_edwards) self.m = m """Mass of the particle, in mg/m of depth (2D simulaton)""" self.force_register = force_register """Reference to the common `particleShear.CanvasPointsMass.force_register` to store the forces acting on the spheres""" self.xforce = 0 """x-component of the net force on this sphere, in N per m of depth in the 2D simulation""" self.yforce = 0 """y-component of the net force on this sphere, in N per m of depth in the 2D simulation""" def record_total_particle_force(self): """ Record the current total force on this particle This functions transmits the current net force having served to do the calculation to the `particleShear.CircleMass.force_register` This method is defined in class `particleShear.CircleMass`""" self.force_register.record_total_particle_force(self, [self.xforce, self.yforce]) def do_linear_acceleration(self, dt): """ Apply the linear acceleration from the net force This method calculates the linear acceleration from F=m*a and applies it by dv=a*dt. It also transmits the current total force to the force register (via `particleShear.CircleMass.record_total_particle_force`) before resetting the net force to 0\n This method is defined in class `particleShear.CircleMass`""" self.record_total_particle_force() self.xspeed = self.xspeed + self.xforce * dt / self.m self.yspeed = self.yspeed + self.yforce * dt / self.m self.xforce = 0 self.yforce = 0 def applyInternalForceAndRegister(self,force_vector,source,register_homolog=False): """ Apply an internal force and update the `particleShear.CircleMass.force_register` This method adds a given force_vector to the net force acting on this particle. It also registers the force in the `particleShear.CircleMass.force_register` as an internal force (between two mobile spheres). If the `register_homolog` parameter is provided as True, then the opposing equal force is also registered in the `particleShear.CircleMass.force_register`. Even in that case, the opposing equal reaction force has to be explicity applied on the `source` sphere elsewhere, this function never applies a force directly to the `source` sphere.\n This method is defined in class `particleShear.CircleMass`""" self.xforce = self.xforce + force_vector[0] self.yforce = self.yforce + force_vector[1] self.force_register.record_individual_internal_force(self, source,force_vector) if register_homolog: opposing_force = [-force_vector[0], -force_vector[1]] source.force_register.record_individual_internal_force(source, self, opposing_force)
Ancestors
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
Instance variables
var force_register
-
Reference to the common
CanvasPointsMass.force_register
to store the forces acting on the spheres var m
-
Mass of the particle, in mg/m of depth (2D simulaton)
var xforce
-
x-component of the net force on this sphere, in N per m of depth in the 2D simulation
var yforce
-
y-component of the net force on this sphere, in N per m of depth in the 2D simulation
Methods
def applyInternalForceAndRegister(self, force_vector, source, register_homolog=False)
-
Apply an internal force and update the
CircleMass.force_register
This method adds a given force_vector to the net force acting on this particle. It also registers the force in the
CircleMass.force_register
as an internal force (between two mobile spheres). If theregister_homolog
parameter is provided as True, then the opposing equal force is also registered in theCircleMass.force_register
. Even in that case, the opposing equal reaction force has to be explicity applied on thesource
sphere elsewhere, this function never applies a force directly to thesource
sphere.This method is defined in class
CircleMass
Expand source code
def applyInternalForceAndRegister(self,force_vector,source,register_homolog=False): """ Apply an internal force and update the `particleShear.CircleMass.force_register` This method adds a given force_vector to the net force acting on this particle. It also registers the force in the `particleShear.CircleMass.force_register` as an internal force (between two mobile spheres). If the `register_homolog` parameter is provided as True, then the opposing equal force is also registered in the `particleShear.CircleMass.force_register`. Even in that case, the opposing equal reaction force has to be explicity applied on the `source` sphere elsewhere, this function never applies a force directly to the `source` sphere.\n This method is defined in class `particleShear.CircleMass`""" self.xforce = self.xforce + force_vector[0] self.yforce = self.yforce + force_vector[1] self.force_register.record_individual_internal_force(self, source,force_vector) if register_homolog: opposing_force = [-force_vector[0], -force_vector[1]] source.force_register.record_individual_internal_force(source, self, opposing_force)
def do_linear_acceleration(self, dt)
-
Apply the linear acceleration from the net force
This method calculates the linear acceleration from F=ma and applies it by dv=adt. It also transmits the current total force to the force register (via
CircleMass.record_total_particle_force()
) before resetting the net force to 0This method is defined in class
CircleMass
Expand source code
def do_linear_acceleration(self, dt): """ Apply the linear acceleration from the net force This method calculates the linear acceleration from F=m*a and applies it by dv=a*dt. It also transmits the current total force to the force register (via `particleShear.CircleMass.record_total_particle_force`) before resetting the net force to 0\n This method is defined in class `particleShear.CircleMass`""" self.record_total_particle_force() self.xspeed = self.xspeed + self.xforce * dt / self.m self.yspeed = self.yspeed + self.yforce * dt / self.m self.xforce = 0 self.yforce = 0
def record_total_particle_force(self)
-
Record the current total force on this particle
This functions transmits the current net force having served to do the calculation to the
CircleMass.force_register
This method is defined in class
CircleMass
Expand source code
def record_total_particle_force(self): """ Record the current total force on this particle This functions transmits the current net force having served to do the calculation to the `particleShear.CircleMass.force_register` This method is defined in class `particleShear.CircleMass`""" self.force_register.record_total_particle_force(self, [self.xforce, self.yforce])
class CircleMassNeighbors (color, x, y, diameter, m=1, theCanvas=False, doDrawing=False, force_register=<particleShearBase.Force_register.Force_register object>, use_lees_edwards=False)
-
Provide a circle with a neighbor list
Add capacity to handle the relation to neighboring spheres to the
CircleMass
parent classInitialize self
- parameters
x
The x-position of the center of the circle (in pixels, assumed micrometers for the simulation)y
The y-position of the center of the circle (in pixels, assumed micrometers for the simulation)diameter
The diameter of the circle (in pixels, assumed micrometers for the simulation)m
is the mass per unit of depth in mg/m of depththeCanvas
Tkinter canvas; provide False if no Canvas is available or no drawing is desireddoDrawing
Explicit switch to turn of drawingforce_register
Possibility to provide a reference to a commonForce_register
variable to register the forces calculated during simulationuse_lees_edwards
Explicit Shoud Lees-Edwards boundary conditions be used?Expand source code
class CircleMassNeighbors(CircleMass): """Provide a circle with a neighbor list Add capacity to handle the relation to neighboring spheres to the `particleShear.CircleMass` parent class """ def __init__(self, color, x, y, diameter, m=1, theCanvas=False, doDrawing=False,force_register=Force_register(), use_lees_edwards=False): self.neighbors = [] """The list of neighbors, of class `particleShear.neighbor_relation` (or subclass)""" super(CircleMassNeighbors,self).__init__(color,x,y,diameter,m=m, theCanvas=theCanvas, doDrawing=doDrawing, force_register=force_register,use_lees_edwards=use_lees_edwards) def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.CircleMassNeighbors`""" self.deleteNeighborDrawing() super(CircleMassNeighbors,self).set_graphical_output_configuration(graphical_output_configuration) self.initiateNeighborDrawing() def deleteNeighborDrawing(self): """Deletes the drawing objects for the neighbor interfaces There seems to be no good way to move a line, so deleting and drawing is done at every simulation step for the neighbor lines.\n This method is defined in class `particleShear.CircleMassNeighbors`""" for theNeighbor in self.neighbors: if theNeighbor.graphical_line_index!=-1: if self.theCanvas: self.theCanvas.delete(theNeighbor.graphical_line_index) theNeighbor.graphical_line_index=-1 def contactLineColor(self,interface_type): """Find current contact line color Color codes for the interface state; by default, red is locked, green is slipping. These settings can be changed in the `particleShear.CircleMassNeighbors.graphical_output_configuration` instance variable\n This method is defined in class `particleShear.CircleMassNeighbors`""" col=self.graphical_output_configuration.color_interface_slip if(interface_type == "stick"): col=self.graphical_output_configuration.color_interface_locked return col def initiateNeighborDrawing(self): """Generate the graphical representation of the neighboring lines This method is defined in class `particleShear.CircleMassNeighbors`""" if self.graphical_output_configuration.draw_active_interface: for theNeighbor in self.neighbors: if not theNeighbor.interface_type=="permanent" or self.graphical_output_configuration.draw_permanent_interfaces: if theNeighbor.graphical_line_index == -1: dist = self.d(theNeighbor.theSphere) # From geometrical considerations: intersection circles with shared height segment # self.r^2-x^2=theNeighbor.theSphere.r^2-(dist-x)^2 # self.r^2-x^2+dist^2-2*x*dist+x^2=theNeighbor.theSphere.r^2 # self.r^2+dist^2-2*x*dist=theNeighbor.theSphere.r^2 # dist^2-2*x*dist=theNeighbor.theSphere.r^2-self.r^2 # -dist^2+2*x*dist=self.r^2-theNeighbor.theSphere.r^2 # -dist+2*x=(self.r^2-theNeighbor.theSphere.r^2)/dist # x=1/2*(dist+(self.r^2-theNeighbor.theSphere.r^2)/dist) if (dist <= self.r + theNeighbor.theSphere.r) and dist > 0: l = (dist + ( self.r * self.r - theNeighbor.theSphere.r * theNeighbor.theSphere.r) / dist) / 2 if self.r * self.r - l * l >= 0: h = math.sqrt(self.r * self.r - l * l) norm_vector = self.n(theNeighbor.theSphere) pos = self.coordinates() touch_point_x = pos[0] + norm_vector[0] * l touch_point_y = pos[1] + norm_vector[1] * l endpoint_1_x = touch_point_x - norm_vector[1] * h endpoint_1_y = touch_point_y + norm_vector[0] * h endpoint_2_x = touch_point_x + norm_vector[1] * h endpoint_2_y = touch_point_y - norm_vector[0] * h color = self.contactLineColor(theNeighbor.interface_type) if self.theCanvas: theNeighbor.graphical_line_index = self.theCanvas.create_line( endpoint_1_x, endpoint_1_y, endpoint_2_x, endpoint_2_y, width=3, fill=color ) def moveDrawing(self, dt): """Move the drawings associated with this object This method is defined in class `particleShear.CircleMassNeighbors`""" super(CircleMassNeighbors,self).moveDrawing(dt) self.deleteNeighborDrawing() self.initiateNeighborDrawing() def deleteDrawing(self): """Delete the drawings associated with this object This method is defined in class `particleShear.CircleMassNeighbors`""" self.deleteNeighborDrawing() super(CircleMassNeighbors,self).deleteDrawing() def test_neighbor_relation(self, theSphere): """Test whether a given sphere is a neighbor Tests whether or not the sphere is a geometrical neighbor. Depending on the result, update the list of `particleShear.CircleMassNeighbors.neighbors`. If necessary, draw or delete the associated shapes.\n This method is defined in class `particleShear.CircleMassNeighbors`""" if abs(self.y-theSphere.y) > self.r+theSphere.r and abs(self.y-theSphere.y)< self.size_y-self.r-theSphere.r: is_neighbor=False else: d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] is_neighbor = (d < r + self.r) # Regardless of the distance, first check whether we find the sphere in the neighbors list found = FALSE foundNeighborIndex=-1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex found = TRUE if not is_neighbor: #If the sphere is not a neighbor (anymore) if found: #It was previously a neighbor, but no more, so delete it from the neighbors list if self.doDrawing: self.deleteNeighborDrawing() del self.neighbors[foundNeighborIndex] if self.doDrawing: self.initiateNeighborDrawing() if is_neighbor: if not found: # Not found previously, so add a new entry info = neighbor_relation(0,"stick",theSphere) if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if self.doDrawing: self.initiateNeighborDrawing() def findNeighborIndex(self, theSphere): """Return whether theSphere is among the currently listed `particleShear.CircleMassNeighbors.neighbors`""" foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex return foundNeighborIndex
Ancestors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
Instance variables
var neighbors
-
The list of neighbors, of class
neighbor_relation
(or subclass)
Methods
def contactLineColor(self, interface_type)
-
Find current contact line color
Color codes for the interface state; by default, red is locked, green is slipping. These settings can be changed in the
particleShear.CircleMassNeighbors.graphical_output_configuration
instance variableThis method is defined in class
CircleMassNeighbors
Expand source code
def contactLineColor(self,interface_type): """Find current contact line color Color codes for the interface state; by default, red is locked, green is slipping. These settings can be changed in the `particleShear.CircleMassNeighbors.graphical_output_configuration` instance variable\n This method is defined in class `particleShear.CircleMassNeighbors`""" col=self.graphical_output_configuration.color_interface_slip if(interface_type == "stick"): col=self.graphical_output_configuration.color_interface_locked return col
def deleteDrawing(self)
-
Delete the drawings associated with this object
This method is defined in class
CircleMassNeighbors
Expand source code
def deleteDrawing(self): """Delete the drawings associated with this object This method is defined in class `particleShear.CircleMassNeighbors`""" self.deleteNeighborDrawing() super(CircleMassNeighbors,self).deleteDrawing()
def deleteNeighborDrawing(self)
-
Deletes the drawing objects for the neighbor interfaces
There seems to be no good way to move a line, so deleting and drawing is done at every simulation step for the neighbor lines.
This method is defined in class
CircleMassNeighbors
Expand source code
def deleteNeighborDrawing(self): """Deletes the drawing objects for the neighbor interfaces There seems to be no good way to move a line, so deleting and drawing is done at every simulation step for the neighbor lines.\n This method is defined in class `particleShear.CircleMassNeighbors`""" for theNeighbor in self.neighbors: if theNeighbor.graphical_line_index!=-1: if self.theCanvas: self.theCanvas.delete(theNeighbor.graphical_line_index) theNeighbor.graphical_line_index=-1
def findNeighborIndex(self, theSphere)
-
Return whether theSphere is among the currently listed
CircleMassNeighbors.neighbors
Expand source code
def findNeighborIndex(self, theSphere): """Return whether theSphere is among the currently listed `particleShear.CircleMassNeighbors.neighbors`""" foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex return foundNeighborIndex
def initiateNeighborDrawing(self)
-
Generate the graphical representation of the neighboring lines
This method is defined in class
CircleMassNeighbors
Expand source code
def initiateNeighborDrawing(self): """Generate the graphical representation of the neighboring lines This method is defined in class `particleShear.CircleMassNeighbors`""" if self.graphical_output_configuration.draw_active_interface: for theNeighbor in self.neighbors: if not theNeighbor.interface_type=="permanent" or self.graphical_output_configuration.draw_permanent_interfaces: if theNeighbor.graphical_line_index == -1: dist = self.d(theNeighbor.theSphere) # From geometrical considerations: intersection circles with shared height segment # self.r^2-x^2=theNeighbor.theSphere.r^2-(dist-x)^2 # self.r^2-x^2+dist^2-2*x*dist+x^2=theNeighbor.theSphere.r^2 # self.r^2+dist^2-2*x*dist=theNeighbor.theSphere.r^2 # dist^2-2*x*dist=theNeighbor.theSphere.r^2-self.r^2 # -dist^2+2*x*dist=self.r^2-theNeighbor.theSphere.r^2 # -dist+2*x=(self.r^2-theNeighbor.theSphere.r^2)/dist # x=1/2*(dist+(self.r^2-theNeighbor.theSphere.r^2)/dist) if (dist <= self.r + theNeighbor.theSphere.r) and dist > 0: l = (dist + ( self.r * self.r - theNeighbor.theSphere.r * theNeighbor.theSphere.r) / dist) / 2 if self.r * self.r - l * l >= 0: h = math.sqrt(self.r * self.r - l * l) norm_vector = self.n(theNeighbor.theSphere) pos = self.coordinates() touch_point_x = pos[0] + norm_vector[0] * l touch_point_y = pos[1] + norm_vector[1] * l endpoint_1_x = touch_point_x - norm_vector[1] * h endpoint_1_y = touch_point_y + norm_vector[0] * h endpoint_2_x = touch_point_x + norm_vector[1] * h endpoint_2_y = touch_point_y - norm_vector[0] * h color = self.contactLineColor(theNeighbor.interface_type) if self.theCanvas: theNeighbor.graphical_line_index = self.theCanvas.create_line( endpoint_1_x, endpoint_1_y, endpoint_2_x, endpoint_2_y, width=3, fill=color )
def moveDrawing(self, dt)
-
Move the drawings associated with this object
This method is defined in class
CircleMassNeighbors
Expand source code
def moveDrawing(self, dt): """Move the drawings associated with this object This method is defined in class `particleShear.CircleMassNeighbors`""" super(CircleMassNeighbors,self).moveDrawing(dt) self.deleteNeighborDrawing() self.initiateNeighborDrawing()
def set_graphical_output_configuration(self, graphical_output_configuration)
-
Set new
Graphical_output_configuration
This method is defined in class
CircleMassNeighbors
Expand source code
def set_graphical_output_configuration(self, graphical_output_configuration): """Set new `particleShear.Graphical_output_configuration` This method is defined in class `particleShear.CircleMassNeighbors`""" self.deleteNeighborDrawing() super(CircleMassNeighbors,self).set_graphical_output_configuration(graphical_output_configuration) self.initiateNeighborDrawing()
def test_neighbor_relation(self, theSphere)
-
Test whether a given sphere is a neighbor
Tests whether or not the sphere is a geometrical neighbor. Depending on the result, update the list of
CircleMassNeighbors.neighbors
. If necessary, draw or delete the associated shapes.This method is defined in class
CircleMassNeighbors
Expand source code
def test_neighbor_relation(self, theSphere): """Test whether a given sphere is a neighbor Tests whether or not the sphere is a geometrical neighbor. Depending on the result, update the list of `particleShear.CircleMassNeighbors.neighbors`. If necessary, draw or delete the associated shapes.\n This method is defined in class `particleShear.CircleMassNeighbors`""" if abs(self.y-theSphere.y) > self.r+theSphere.r and abs(self.y-theSphere.y)< self.size_y-self.r-theSphere.r: is_neighbor=False else: d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] is_neighbor = (d < r + self.r) # Regardless of the distance, first check whether we find the sphere in the neighbors list found = FALSE foundNeighborIndex=-1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere): foundNeighborIndex = theNeighborIndex found = TRUE if not is_neighbor: #If the sphere is not a neighbor (anymore) if found: #It was previously a neighbor, but no more, so delete it from the neighbors list if self.doDrawing: self.deleteNeighborDrawing() del self.neighbors[foundNeighborIndex] if self.doDrawing: self.initiateNeighborDrawing() if is_neighbor: if not found: # Not found previously, so add a new entry info = neighbor_relation(0,"stick",theSphere) if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if self.doDrawing: self.initiateNeighborDrawing()
class Ensemble (size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01, m=1, bimodal_factor=1.4)
-
Ensemble of particles of type
SphereFriction
, with regular boundary conditionsThis class creates and maintains a list of objects of type
SphereFriction
. These objects are generically referred to as spheres and are stored inCanvasPoints.sphereList
.The ensemble class does not implement proper Lees-Edwards boundaries, but emulates neighboring spheres by creating replicates of the spheres near the boundares displaced by one positive or negative multiple of the xy dimensions of the simulation area.
Class defined in subpackage particleShearObjects
Initialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionExpand source code
class Ensemble(CanvasPointsBasicElasticity): """Ensemble of particles of type `particleShear.SphereFriction`, with regular boundary conditions This class creates and maintains a list of objects of type `particleShear.SphereFriction`. These objects are generically referred to as spheres and are stored in `particleShear.CanvasPoints.sphereList`.\n The ensemble class does not implement proper Lees-Edwards boundaries, but emulates neighboring spheres by creating replicates of the spheres near the boundares displaced by one positive or negative multiple of the xy dimensions of the simulation area.\n Class defined in subpackage particleShearObjects""" def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01,m=1, bimodal_factor=1.4): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the mass per unit of depth in mg/m of depth\n `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution\n """ super(Ensemble,self).__init__(size_x=size_x,size_y=size_y,theCanvas=theCanvas,doDrawing=doDrawing,k=k, nu=nu,m=m) self.bimodal_upper = math.sqrt(bimodal_factor) """Upper multiplication factor for the bimodal sphere size distribution""" self.bimodal_lower = math.sqrt(1/bimodal_factor) """Lower multiplication factor for the bimodal sphere size distribution""" self.N = N """Number of spheres""" self.packing_fraction = packing_fraction """Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area""" self.k = k """Central spring constant in (mg/s^2)/m of depth""" self.nu = nu """Central viscosity constant in (mg/s)/m of depth""" self.record_type = "internal" """Helper variable to distinguish between internal spheres and boundary replicates for the accounting of internal and external forces""" r=self.size_y/2 # If there are no spheres specified, keep some default value if N>0: r = r_estimate(self.size_x , self.size_y ,packing_fraction , N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower ) self.r_max = 0 """Largest sphere radius""" for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereFriction("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i,theCanvas, False,self)) self.spheres_boundary = [] """List of the spheres on the boundary""" self.set_graphical_output_configuration(self.graphical_output_configuration) # To propagate it also to the spheres self.initiateGraphics() def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.Ensemble`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1)) # For recording incoming force reported by the spheres def record_individual_internal_force(self,target,source,force_vector): """Record internal force Internal forces are the forces that act between spheres within the interior of the simulation. That is, they are able to move, and are not replicates to emulate periodic or other boundaries. In this class, the distinction between internal and external forces is made by using the instance variable `particleShear.Ensemble.record_type` which switches between internal and external depending on whether forces originating from internal spheres or boundary replicates are considered.\n\n Method defined in `particleShear.Ensemble`""" if not self.canMove(target): return if self.record_type=="internal": super(Ensemble,self).record_individual_internal_force(target,source,force_vector) else: # running virtual boundary replicates, this is also external here self.record_external_force(target, force_vector) def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.Ensemble` """ return "Non-frictional spheres, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def test_neighbor_relation(self): """Instruct every sphere to test establish its geometric (and possibly permanent) neighbors. This function calls `particleShear.CircleMassNeighbors.test_neighbor_relation` on each member of the `particleShear.CanvasPoints.sphereList`. It does so not only on the actuale sphere list, but also on the replicates used to emulate the boundary environment in this class.\n This method is defined in class `particleShear.Ensemble`""" super(Ensemble,self).test_neighbor_relation() for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].test_neighbor_relation(self.spheres_boundary[sphereIndexBoundary]) def elastic_force(self): """Let all spheres calculate the central elastic force acting on them This includes forces acting from the replicated boundary spheres. This method is defined in class `particleShear.Ensemble`""" self.record_type = "interal" super(Ensemble,self).elastic_force() self.record_type = "external" for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].elastic_force(self.spheres_boundary[sphereIndexBoundary], self.k) def central_viscous_force(self): """Let all spheres calculate the central elastic force acting on from their known neighbors. This includes forces acting from the replicated boundary spheres. \n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.record_type = "interal" super(Ensemble, self).central_viscous_force() self.record_type = "external" for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].central_viscous_force(self.spheres_boundary[sphereIndexBoundary], self.nu) def periodic_boundary_extension(self): """Create periodic boundary extension. The function replicates spheres near the boundary to obtain virtual copies displaced by one positive or negative of the simulation area, in both x and y direction and also in corners by displacement in both x and y direction. This method is defined in class `particleShear.Ensemble`""" if self.doDrawing: for theSphere in self.spheres_boundary: theSphere.deleteDrawing() max_index=0 for theSphere in self.sphereList: if max_index<theSphere.myindex: max_index=theSphere.myindex self.spheres_boundary = [] index=max_index+1 for theSphere in self.sphereList: pos = theSphere.coordinates() left_edge = pos[0] - pos[2] <= self.r_max right_edge = pos[0] + pos[2] >= self.size_x - self.r_max upper_edge = pos[1] - pos[2] <= self.r_max lower_edge = pos[1] + pos[2] >= self.size_y - self.r_max if left_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1], 2 * pos[2], theSphere.m,index,self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1] + self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1] - self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if right_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1], 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1] + self.size_y, 2 * pos[2], theSphere.m,index,self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1] - self.size_y, 2 * pos[2], theSphere.m, index,self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0], pos[1] + self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0], pos[1] - self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.Ensemble`""" self.reset_force_register() self.periodic_boundary_extension() self.test_neighbor_relation() self.elastic_force_from_neighbors() self.central_viscous_force_from_neighbors()
Ancestors
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearObjects.EnsembleFriction.EnsembleFriction
Instance variables
var N
-
Number of spheres
var bimodal_lower
-
Lower multiplication factor for the bimodal sphere size distribution
var bimodal_upper
-
Upper multiplication factor for the bimodal sphere size distribution
var k
-
Central spring constant in (mg/s^2)/m of depth
var nu
-
Central viscosity constant in (mg/s)/m of depth
var packing_fraction
-
Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area
var r_max
-
Largest sphere radius
var record_type
-
Helper variable to distinguish between internal spheres and boundary replicates for the accounting of internal and external forces
var spheres_boundary
-
List of the spheres on the boundary
Methods
def central_viscous_force(self)
-
Let all spheres calculate the central elastic force acting on from their known neighbors.
This includes forces acting from the replicated boundary spheres.
This method is defined in class
CanvasPointsBasicElasticity
Expand source code
def central_viscous_force(self): """Let all spheres calculate the central elastic force acting on from their known neighbors. This includes forces acting from the replicated boundary spheres. \n This method is defined in class `particleShear.CanvasPointsBasicElasticity`""" self.record_type = "interal" super(Ensemble, self).central_viscous_force() self.record_type = "external" for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].central_viscous_force(self.spheres_boundary[sphereIndexBoundary], self.nu)
def elastic_force(self)
-
Let all spheres calculate the central elastic force acting on them
This includes forces acting from the replicated boundary spheres.
This method is defined in class
Ensemble
Expand source code
def elastic_force(self): """Let all spheres calculate the central elastic force acting on them This includes forces acting from the replicated boundary spheres. This method is defined in class `particleShear.Ensemble`""" self.record_type = "interal" super(Ensemble,self).elastic_force() self.record_type = "external" for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].elastic_force(self.spheres_boundary[sphereIndexBoundary], self.k)
def mechanical_simulation_step_calculate_forces(self)
-
Calculate the forces acting on the spheres
This method is defined in class
Ensemble
Expand source code
def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.Ensemble`""" self.reset_force_register() self.periodic_boundary_extension() self.test_neighbor_relation() self.elastic_force_from_neighbors() self.central_viscous_force_from_neighbors()
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
Ensemble
Expand source code
def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.Ensemble` """ return "Non-frictional spheres, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def periodic_boundary_extension(self)
-
Create periodic boundary extension.
The function replicates spheres near the boundary to obtain virtual copies displaced by one positive or negative of the simulation area, in both x and y direction and also in corners by displacement in both x and y direction.
This method is defined in class
Ensemble
Expand source code
def periodic_boundary_extension(self): """Create periodic boundary extension. The function replicates spheres near the boundary to obtain virtual copies displaced by one positive or negative of the simulation area, in both x and y direction and also in corners by displacement in both x and y direction. This method is defined in class `particleShear.Ensemble`""" if self.doDrawing: for theSphere in self.spheres_boundary: theSphere.deleteDrawing() max_index=0 for theSphere in self.sphereList: if max_index<theSphere.myindex: max_index=theSphere.myindex self.spheres_boundary = [] index=max_index+1 for theSphere in self.sphereList: pos = theSphere.coordinates() left_edge = pos[0] - pos[2] <= self.r_max right_edge = pos[0] + pos[2] >= self.size_x - self.r_max upper_edge = pos[1] - pos[2] <= self.r_max lower_edge = pos[1] + pos[2] >= self.size_y - self.r_max if left_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1], 2 * pos[2], theSphere.m,index,self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1] + self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] + self.size_x, pos[1] - self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if right_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1], 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1] + self.size_y, 2 * pos[2], theSphere.m,index,self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0] - self.size_x, pos[1] - self.size_y, 2 * pos[2], theSphere.m, index,self.theCanvas, self.doDrawing)) index=index+1 if upper_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0], pos[1] + self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1 if lower_edge: self.spheres_boundary.append(SphereFriction( "green", pos[0], pos[1] - self.size_y, 2 * pos[2], theSphere.m,index, self.theCanvas, self.doDrawing)) index=index+1
def random_radius_bimodal(self, r)
-
Choose randomly one of the two bimodal radius values.
Method defined in
Ensemble
Expand source code
def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.Ensemble`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
def record_individual_internal_force(self, target, source, force_vector)
-
Record internal force
Internal forces are the forces that act between spheres within the interior of the simulation. That is, they are able to move, and are not replicates to emulate periodic or other boundaries. In this class, the distinction between internal and external forces is made by using the instance variable
Ensemble.record_type
which switches between internal and external depending on whether forces originating from internal spheres or boundary replicates are considered.Method defined in
Ensemble
Expand source code
def record_individual_internal_force(self,target,source,force_vector): """Record internal force Internal forces are the forces that act between spheres within the interior of the simulation. That is, they are able to move, and are not replicates to emulate periodic or other boundaries. In this class, the distinction between internal and external forces is made by using the instance variable `particleShear.Ensemble.record_type` which switches between internal and external depending on whether forces originating from internal spheres or boundary replicates are considered.\n\n Method defined in `particleShear.Ensemble`""" if not self.canMove(target): return if self.record_type=="internal": super(Ensemble,self).record_individual_internal_force(target,source,force_vector) else: # running virtual boundary replicates, this is also external here self.record_external_force(target, force_vector)
def test_neighbor_relation(self)
-
Instruct every sphere to test establish its geometric (and possibly permanent) neighbors.
This function calls
CircleMassNeighbors.test_neighbor_relation()
on each member of theCanvasPoints.sphereList
. It does so not only on the actuale sphere list, but also on the replicates used to emulate the boundary environment in this class.This method is defined in class
Ensemble
Expand source code
def test_neighbor_relation(self): """Instruct every sphere to test establish its geometric (and possibly permanent) neighbors. This function calls `particleShear.CircleMassNeighbors.test_neighbor_relation` on each member of the `particleShear.CanvasPoints.sphereList`. It does so not only on the actuale sphere list, but also on the replicates used to emulate the boundary environment in this class.\n This method is defined in class `particleShear.Ensemble`""" super(Ensemble,self).test_neighbor_relation() for sphereIndex in range(len(self.sphereList)): for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].test_neighbor_relation(self.spheres_boundary[sphereIndexBoundary])
class EnsembleCompactParticles (size_x, size_y, N, packing_fraction=0.8, theCanvas=0, doDrawing=False, k=1, nu=0.01, m=1, bimodal_factor=1.4, k_t=1, nu_t=0.01, mu=0.1, dt=1, dt_max=10, theTkSimulation=False, avoid_horizontal_angle_degree=0)
-
Ensemble of
SphereLinkable
objects (referred to as spheres), with possible crosslinking into distinct, compact particlesThis class maintains a list of objects of type
SphereLinkable
and adds specific functions to the generation of the particlesInitialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mudt
Time step for a single simulation stepdt_max
Maximal time step used during pre-equilibration, can be larger thandt
theTkSimulation
Tk master object associated with theCanvasPoints.theCanvas
used to trigger automatic update of the canvas in a simulation settingExpand source code
class EnsembleCompactParticles (CanvasPointsLinkable): """Ensemble of `particleShear.SphereLinkable` objects (referred to as spheres), with possible crosslinking into distinct, compact particles This class maintains a list of objects of type `particleShear.SphereLinkable` and adds specific functions to the generation of the particles""" def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=FALSE, doDrawing=False, k=1, nu=0.01,m=1, bimodal_factor=1.4, k_t=1,nu_t=0.01,mu=0.1,dt=1,dt_max=10,theTkSimulation=False,avoid_horizontal_angle_degree=0): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the mass per unit of depth in mg/m of depth\n `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution\n `k_t` Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces\n `nu_t` Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones\n `mu` Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu\n `dt` Time step for a single simulation step `dt_max` Maximal time step used during pre-equilibration, can be larger than `dt`\n `theTkSimulation` Tk master object associated with the `particleShear.CanvasPoints.theCanvas` used to trigger automatic update of the canvas in a simulation setting """ self.bimodal_upper = math.sqrt(bimodal_factor) self.bimodal_lower = math.sqrt(1 / bimodal_factor) # The idea here is to be able to indicate an interval of angles near horizontal to be avoided in order not to generate # "pre-made" shear planes that would not be there in a larger ensemble # 0 or negative values indicate no restriction self.avoid_horizontal_angle_degree=avoid_horizontal_angle_degree self.avoid_height_spanning_particles=False # not an argument, just to register what has been done self.doCutByTriangulation = False self.removed_fraction=0 # For storing information about random link removal self.edge_fuzziness=0 # For storing information about making more fuzzy edges super(EnsembleCompactParticles, self).__init__( size_x, size_y, theCanvas=theCanvas, doDrawing=doDrawing, k=k, nu=nu,m=m,k_t=k_t,nu_t=nu_t,mu=mu) self.N=N self.packing_fraction=packing_fraction self.dt=dt self.dt_max=dt_max self.theTkSimulation=theTkSimulation self.model=False self.sphereList = [] self.doDrawing = doDrawing r=self.size_y/2 if N > 0: r = r_estimate(self.size_x, self.size_y, packing_fraction, N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower) self.r_max = 0 for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereLinkable("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i, theCanvas, False, self, self.size_x, self.size_y)) self.setShearInSpheres() self.setShearRateInSpheres() self.set_graphical_output_configuration( self.graphical_output_configuration) # To propagate it also to the spheres self.initiateGraphics() # Particles are identified later on, once the spheres are redistributed and permanent links are formed self.particles = [] def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions, organized in fixed particles\n" \ "\tSpheres with, bimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def random_radius_bimodal(self,r): return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1)) def free_pre_equilibration(self,N=25): print("Unconstrained pre-equilibration ... N=",N,"dt =",self.dt,"max dt = ",self.dt_max) self.applyingShear = False dt=self.dt_max N = N cool_factor=0.5 old_mu=self.mu self.mu=0 print("Removing friction for initial equilibration") while dt>self.dt: self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=cool_factor) dt=dt/2 cool_factor=(1+cool_factor)/2 self.correct_linear_drift() dt = self.dt self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1) self.mu = old_mu if self.mu>0: print("Initiate friction, mu=",self.mu) self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1) print("Done") def angle_OK(self,angle): if self.avoid_horizontal_angle_degree<=0: return True avoid_horizontal_angle = self.avoid_horizontal_angle_degree/180*math.pi # The angle that we are looking at is the angle between the norm vector and the x-axis # A first forbidden range is: # 90+/-avoid_horizontal_angle_degree (math.pi/2-avoid_horizontal_angle to math.pi/2+avoid_horizontal_angle) if angle>=math.pi/2-avoid_horizontal_angle and angle<=math.pi/2+avoid_horizontal_angle: return False # A second forbidden range is: # 270+/-avoid_horizontal_angle_degree (3*math.pi/2-avoid_horizontal_angle to 3*math.pi/2+avoid_horizontal_angle) if angle>=3*math.pi/2-avoid_horizontal_angle and angle<=3*math.pi/2+avoid_horizontal_angle: return False return True def maximal_particle_height(self): self.particles=[] self.identify_particles() max_height=0 for theParticle in self.particles: if len(theParticle)>0: min_y = theParticle[0].y-theParticle[0].r max_y = theParticle[0].y+theParticle[0].r for theSphere in theParticle: if theSphere.y-theSphere.r<min_y: min_y=theSphere.y-theSphere.r if theSphere.y+theSphere.r>max_y: max_y=theSphere.y+theSphere.r if max_y - min_y > max_height: max_height = max_y - min_y return max_height def join_isolated_spheres(self,physical_neighbors_only=True): for theSphere in self.sphereList: if len(theSphere.permanentlyConnectedSpheres(physical_neighbors_only=physical_neighbors_only))==0: non_permanent_neighbors=[] for theNeighbor in theSphere.neighbors: if not (theNeighbor.interface_type=="permanent"): non_permanent_neighbors.append(theNeighbor.theSphere) if len(non_permanent_neighbors)>0: theSphere.establish_permanent_link( non_permanent_neighbors[random.randint(0,len(non_permanent_neighbors)-1)], do_backlink=True) def cutByTriangulationPoints(self, triangulation_points, edge_fuzziness=0): # First, for each sphere, identify the neareast of the triangulation points nearest_point=[] self.edge_fuzziness=edge_fuzziness print("cutByTriangulationPoints, max r=",self.r_max,"edge fuzziness = ",self.edge_fuzziness) for theSphere in self.sphereList: min_dist=0 min_index = -1 started=False for triangulation_index in range(len(triangulation_points)): # Minimal distance to triangulation points, with possibility to randomness to generate fuzzier edges current_d=theSphere.d(triangulation_points[triangulation_index])+edge_fuzziness*random.random()*self.r_max if (not started) or (current_d<min_dist): min_index=triangulation_index min_dist=current_d started=True nearest_point.append(min_index) for theIndex in range(len(self.sphereList)): theSphere = self.sphereList[theIndex] for theNeighbor in theSphere.neighbors: if (theNeighbor.interface_type == "permanent"): theOtherIndex = self.sphereList.index(theNeighbor.theSphere) if not (nearest_point[theIndex]==nearest_point[theOtherIndex]): theSphere.cut_permanent_link(theNeighbor.theSphere) def cutByTriangulation(self, n_points=5,edge_fuzziness=0): thePoints=[] for ind in range(n_points): thePoints.append(PointLeesEdwards(random.random()*self.size_x,random.random()*self.size_y, size_x=self.size_x,size_y=self.size_y)) self.cutByTriangulationPoints(thePoints,edge_fuzziness=edge_fuzziness) self.doCutByTriangulation = True def cutRandomLine(self): if self.avoid_horizontal_angle_degree<=0: super(EnsembleCompactParticles,self).cutRandomLine() return point = [random.randint(0, self.size_x), random.randint(0, self.size_y)] angle = random.random() * 2 * math.pi while not self.angle_OK(angle): print("Rejecting angle",angle/math.pi*180) angle = random.random() * 2 * math.pi self.cutLine(point, angle) self.doCutByTriangulation = False # Functions relatiting to identifying coherent particles def findParticle(self,theSphere): for theParticle in self.particles: if theParticle.count(theSphere): return theParticle return False # Checks whether a given pair of spheres is connected permanently. # Four return values are possible: "direct", "indirect", "no_connection", or False if there was some error def permanentConnectionBetweenSpheres(self,theSphere1, theSphere2,physical_neighbors_only=True): if not self.sphereList.count(theSphere1): print("permanentConnectionBetweenSpheres: Sphere not found") return False if not self.sphereList.count(theSphere2): print("permanentConnectionBetweenSpheres: Sphere not found") return False neighbors1 = theSphere1.permanentlyConnectedSpheres(physical_neighbors_only) if(neighbors1.count(theSphere2)): return "direct" neighbors2 = theSphere2.permanentlyConnectedSpheres(physical_neighbors_only) if (neighbors2.count(theSphere1)): return "direct" self.identify_particles(physical_neighbors_only) particle1=self.findParticle( theSphere1) if not particle1: print("permanentConnectionBetweenSpheres: No particle found for given sphere") return False particle2 = self.findParticle( theSphere2) if not particle2: print("permanentConnectionBetweenSpheres: No particle found for given sphere") return False if(particle1==particle2): return "indirect" return "no_connection" def n_permanent_connections(self,physical_neighbors_only=True): n=0 for theSphere in self.sphereList: n=n+len(theSphere.permanentlyConnectedSpheres(physical_neighbors_only)) return (n/2) def remove_non_essential_links(self, max_fraction_to_remove,physical_neighbors_only=True, max_trials=1000 ): self.identify_particles(physical_neighbors_only) failed_trials=0 succesful_trials=0 n_links = self.n_permanent_connections(physical_neighbors_only) print("Removing connections (non-essential): Target removal fraction ",max_fraction_to_remove," total initial links ",n_links) while failed_trials<max_trials and succesful_trials<max_fraction_to_remove*n_links: failed_trials=failed_trials+1 sphereIndex=random.randint(0,len(self.sphereList)-1) selectedSphere = self.sphereList[sphereIndex] neighbors = selectedSphere.permanentlyConnectedSpheres(physical_neighbors_only) if len(neighbors)>0: neighborIndex = random.randint(0,len(neighbors)-1) theNeighbor = neighbors[neighborIndex] # Try to cut the connection selectedSphere.cut_permanent_link(theNeighbor,do_backlink=True) self.identify_particles(physical_neighbors_only) connectivity=self.permanentConnectionBetweenSpheres(selectedSphere, theNeighbor,physical_neighbors_only) if connectivity=="indirect": succesful_trials=succesful_trials+1 failed_trials=0 else: selectedSphere.establish_permanent_link(theNeighbor,do_backlink=True) self.removed_fraction=succesful_trials/n_links print("Removing connections (non-essential): Actual removal fraction = ",self.removed_fraction) def identify_particles(self,physical_neighbors_only=True): self.particles=[] for theSphere in self.sphereList: if not self.findParticle(theSphere): self.addParticleFromSphere(theSphere,physical_neighbors_only) # The idea here is that we loop through all the spheres of the particle by keeping an overall non-listed array up to date def addParticleFromSphere(self,theSphere,physical_neighbors_only=True): # Start the particles newParticle = [theSphere] # Add to the particles list self.particles.append(newParticle) # Find the neighbors of the first sphere neighbors=theSphere.permanentlyConnectedSpheres(physical_neighbors_only) # initiate the not-yet-listed array non_listed=[] # First round of neighbors: if not in the particle, list uniquely in the non-listed array (so check before adding # the spheres that they are not already listed, this is important as several paths can lead to the same sphere for theNeighbor in neighbors: if not newParticle.count(theNeighbor): if not non_listed.count(theNeighbor): non_listed.append(theNeighbor) # While we still have non-listed spheres in the non-listed array, go ahead while len(non_listed)>0: # First, add the currently non-listed spheres to the particle for sphereToAdd in non_listed: newParticle.append(sphereToAdd) # start a new screen for the neighbors specifically of the just-added spheres (for older spheres, all the # neighbors should already be included) neighbors = [] for theSphere in non_listed: # get the neighbors of the sphere localNeighbors = theSphere.permanentlyConnectedSpheres() for l in localNeighbors: # check whether the neighbors are listed in the particles if not newParticle.count(l): # if not already listed, add to the listed if not neighbors.count(l): neighbors.append(l) # the new non-particle neighbors become the new-non listed spheres non_listed = neighbors
Ancestors
- particleShearLinkableObjects.CanvasPointsLinkable.CanvasPointsLinkable
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Subclasses
- particleShearLinkableObjects.EnsembleCompactParticlesAdjustableInterfaceStrength.EnsembleCompactParticlesAdjustableInterfaceStrength
Methods
def addParticleFromSphere(self, theSphere, physical_neighbors_only=True)
-
Expand source code
def addParticleFromSphere(self,theSphere,physical_neighbors_only=True): # Start the particles newParticle = [theSphere] # Add to the particles list self.particles.append(newParticle) # Find the neighbors of the first sphere neighbors=theSphere.permanentlyConnectedSpheres(physical_neighbors_only) # initiate the not-yet-listed array non_listed=[] # First round of neighbors: if not in the particle, list uniquely in the non-listed array (so check before adding # the spheres that they are not already listed, this is important as several paths can lead to the same sphere for theNeighbor in neighbors: if not newParticle.count(theNeighbor): if not non_listed.count(theNeighbor): non_listed.append(theNeighbor) # While we still have non-listed spheres in the non-listed array, go ahead while len(non_listed)>0: # First, add the currently non-listed spheres to the particle for sphereToAdd in non_listed: newParticle.append(sphereToAdd) # start a new screen for the neighbors specifically of the just-added spheres (for older spheres, all the # neighbors should already be included) neighbors = [] for theSphere in non_listed: # get the neighbors of the sphere localNeighbors = theSphere.permanentlyConnectedSpheres() for l in localNeighbors: # check whether the neighbors are listed in the particles if not newParticle.count(l): # if not already listed, add to the listed if not neighbors.count(l): neighbors.append(l) # the new non-particle neighbors become the new-non listed spheres non_listed = neighbors
def angle_OK(self, angle)
-
Expand source code
def angle_OK(self,angle): if self.avoid_horizontal_angle_degree<=0: return True avoid_horizontal_angle = self.avoid_horizontal_angle_degree/180*math.pi # The angle that we are looking at is the angle between the norm vector and the x-axis # A first forbidden range is: # 90+/-avoid_horizontal_angle_degree (math.pi/2-avoid_horizontal_angle to math.pi/2+avoid_horizontal_angle) if angle>=math.pi/2-avoid_horizontal_angle and angle<=math.pi/2+avoid_horizontal_angle: return False # A second forbidden range is: # 270+/-avoid_horizontal_angle_degree (3*math.pi/2-avoid_horizontal_angle to 3*math.pi/2+avoid_horizontal_angle) if angle>=3*math.pi/2-avoid_horizontal_angle and angle<=3*math.pi/2+avoid_horizontal_angle: return False return True
def cutByTriangulation(self, n_points=5, edge_fuzziness=0)
-
Expand source code
def cutByTriangulation(self, n_points=5,edge_fuzziness=0): thePoints=[] for ind in range(n_points): thePoints.append(PointLeesEdwards(random.random()*self.size_x,random.random()*self.size_y, size_x=self.size_x,size_y=self.size_y)) self.cutByTriangulationPoints(thePoints,edge_fuzziness=edge_fuzziness) self.doCutByTriangulation = True
def cutByTriangulationPoints(self, triangulation_points, edge_fuzziness=0)
-
Expand source code
def cutByTriangulationPoints(self, triangulation_points, edge_fuzziness=0): # First, for each sphere, identify the neareast of the triangulation points nearest_point=[] self.edge_fuzziness=edge_fuzziness print("cutByTriangulationPoints, max r=",self.r_max,"edge fuzziness = ",self.edge_fuzziness) for theSphere in self.sphereList: min_dist=0 min_index = -1 started=False for triangulation_index in range(len(triangulation_points)): # Minimal distance to triangulation points, with possibility to randomness to generate fuzzier edges current_d=theSphere.d(triangulation_points[triangulation_index])+edge_fuzziness*random.random()*self.r_max if (not started) or (current_d<min_dist): min_index=triangulation_index min_dist=current_d started=True nearest_point.append(min_index) for theIndex in range(len(self.sphereList)): theSphere = self.sphereList[theIndex] for theNeighbor in theSphere.neighbors: if (theNeighbor.interface_type == "permanent"): theOtherIndex = self.sphereList.index(theNeighbor.theSphere) if not (nearest_point[theIndex]==nearest_point[theOtherIndex]): theSphere.cut_permanent_link(theNeighbor.theSphere)
def cutRandomLine(self)
-
Remove permanent links across a random cut line
Method defined in
CanvasPointsLinkable
Expand source code
def cutRandomLine(self): if self.avoid_horizontal_angle_degree<=0: super(EnsembleCompactParticles,self).cutRandomLine() return point = [random.randint(0, self.size_x), random.randint(0, self.size_y)] angle = random.random() * 2 * math.pi while not self.angle_OK(angle): print("Rejecting angle",angle/math.pi*180) angle = random.random() * 2 * math.pi self.cutLine(point, angle) self.doCutByTriangulation = False
def findParticle(self, theSphere)
-
Expand source code
def findParticle(self,theSphere): for theParticle in self.particles: if theParticle.count(theSphere): return theParticle return False
def free_pre_equilibration(self, N=25)
-
Expand source code
def free_pre_equilibration(self,N=25): print("Unconstrained pre-equilibration ... N=",N,"dt =",self.dt,"max dt = ",self.dt_max) self.applyingShear = False dt=self.dt_max N = N cool_factor=0.5 old_mu=self.mu self.mu=0 print("Removing friction for initial equilibration") while dt>self.dt: self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=cool_factor) dt=dt/2 cool_factor=(1+cool_factor)/2 self.correct_linear_drift() dt = self.dt self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1) self.mu = old_mu if self.mu>0: print("Initiate friction, mu=",self.mu) self.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1) print("Done")
def identify_particles(self, physical_neighbors_only=True)
-
Expand source code
def identify_particles(self,physical_neighbors_only=True): self.particles=[] for theSphere in self.sphereList: if not self.findParticle(theSphere): self.addParticleFromSphere(theSphere,physical_neighbors_only)
def join_isolated_spheres(self, physical_neighbors_only=True)
-
Expand source code
def join_isolated_spheres(self,physical_neighbors_only=True): for theSphere in self.sphereList: if len(theSphere.permanentlyConnectedSpheres(physical_neighbors_only=physical_neighbors_only))==0: non_permanent_neighbors=[] for theNeighbor in theSphere.neighbors: if not (theNeighbor.interface_type=="permanent"): non_permanent_neighbors.append(theNeighbor.theSphere) if len(non_permanent_neighbors)>0: theSphere.establish_permanent_link( non_permanent_neighbors[random.randint(0,len(non_permanent_neighbors)-1)], do_backlink=True)
def maximal_particle_height(self)
-
Expand source code
def maximal_particle_height(self): self.particles=[] self.identify_particles() max_height=0 for theParticle in self.particles: if len(theParticle)>0: min_y = theParticle[0].y-theParticle[0].r max_y = theParticle[0].y+theParticle[0].r for theSphere in theParticle: if theSphere.y-theSphere.r<min_y: min_y=theSphere.y-theSphere.r if theSphere.y+theSphere.r>max_y: max_y=theSphere.y+theSphere.r if max_y - min_y > max_height: max_height = max_y - min_y return max_height
def n_permanent_connections(self, physical_neighbors_only=True)
-
Expand source code
def n_permanent_connections(self,physical_neighbors_only=True): n=0 for theSphere in self.sphereList: n=n+len(theSphere.permanentlyConnectedSpheres(physical_neighbors_only)) return (n/2)
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
CanvasPointsLinkable
Expand source code
def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions, organized in fixed particles\n" \ "\tSpheres with, bimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def permanentConnectionBetweenSpheres(self, theSphere1, theSphere2, physical_neighbors_only=True)
-
Expand source code
def permanentConnectionBetweenSpheres(self,theSphere1, theSphere2,physical_neighbors_only=True): if not self.sphereList.count(theSphere1): print("permanentConnectionBetweenSpheres: Sphere not found") return False if not self.sphereList.count(theSphere2): print("permanentConnectionBetweenSpheres: Sphere not found") return False neighbors1 = theSphere1.permanentlyConnectedSpheres(physical_neighbors_only) if(neighbors1.count(theSphere2)): return "direct" neighbors2 = theSphere2.permanentlyConnectedSpheres(physical_neighbors_only) if (neighbors2.count(theSphere1)): return "direct" self.identify_particles(physical_neighbors_only) particle1=self.findParticle( theSphere1) if not particle1: print("permanentConnectionBetweenSpheres: No particle found for given sphere") return False particle2 = self.findParticle( theSphere2) if not particle2: print("permanentConnectionBetweenSpheres: No particle found for given sphere") return False if(particle1==particle2): return "indirect" return "no_connection"
def random_radius_bimodal(self, r)
-
Expand source code
def random_radius_bimodal(self,r): return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
def remove_non_essential_links(self, max_fraction_to_remove, physical_neighbors_only=True, max_trials=1000)
-
Expand source code
def remove_non_essential_links(self, max_fraction_to_remove,physical_neighbors_only=True, max_trials=1000 ): self.identify_particles(physical_neighbors_only) failed_trials=0 succesful_trials=0 n_links = self.n_permanent_connections(physical_neighbors_only) print("Removing connections (non-essential): Target removal fraction ",max_fraction_to_remove," total initial links ",n_links) while failed_trials<max_trials and succesful_trials<max_fraction_to_remove*n_links: failed_trials=failed_trials+1 sphereIndex=random.randint(0,len(self.sphereList)-1) selectedSphere = self.sphereList[sphereIndex] neighbors = selectedSphere.permanentlyConnectedSpheres(physical_neighbors_only) if len(neighbors)>0: neighborIndex = random.randint(0,len(neighbors)-1) theNeighbor = neighbors[neighborIndex] # Try to cut the connection selectedSphere.cut_permanent_link(theNeighbor,do_backlink=True) self.identify_particles(physical_neighbors_only) connectivity=self.permanentConnectionBetweenSpheres(selectedSphere, theNeighbor,physical_neighbors_only) if connectivity=="indirect": succesful_trials=succesful_trials+1 failed_trials=0 else: selectedSphere.establish_permanent_link(theNeighbor,do_backlink=True) self.removed_fraction=succesful_trials/n_links print("Removing connections (non-essential): Actual removal fraction = ",self.removed_fraction)
class EnsembleCompactParticlesAdjustableInterfaceStrength (size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01, m=1, bimodal_factor=1.4, k_t=1, nu_t=0.01, mu=0.1, dt=1, dt_max=10, theTkSimulation=False, avoid_horizontal_angle_degree=0, permanent_ratio_central=1, permanent_ratio_tangential=1, keep_viscosity_coefficients_constant=True)
-
Ensemble of
SphereLinkable
objects (referred to as spheres), with possible crosslinking into distinct, compact particlesThis class maintains a list of objects of type
SphereLinkable
and adds specific functions to the generation of the particlesInitialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mudt
Time step for a single simulation stepdt_max
Maximal time step used during pre-equilibration, can be larger thandt
theTkSimulation
Tk master object associated with theCanvasPoints.theCanvas
used to trigger automatic update of the canvas in a simulation settingExpand source code
class EnsembleCompactParticlesAdjustableInterfaceStrength(EnsembleCompactParticles): def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01,m=1, bimodal_factor=1.4, k_t=1,nu_t=0.01,mu=0.1,dt=1,dt_max=10,theTkSimulation=False,avoid_horizontal_angle_degree=0, permanent_ratio_central=1, permanent_ratio_tangential=1, keep_viscosity_coefficients_constant=True): # The idea here is that we can specifically increase the central or tangential forces for the crosslinked particles # as compared to the normal frictional contact self.permanent_ratio_central=permanent_ratio_central self.permanent_ratio_tangential=permanent_ratio_tangential self.keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant self.cut_top_bottom=False super(EnsembleCompactParticlesAdjustableInterfaceStrength, self).__init__( size_x, size_y,N=0,packing_fraction=packing_fraction, theCanvas=theCanvas, doDrawing=False, k=k, nu=nu,m=m,bimodal_factor=bimodal_factor, k_t=k_t,nu_t=nu_t,mu=mu,dt=dt, dt_max=dt_max,theTkSimulation=theTkSimulation,avoid_horizontal_angle_degree=avoid_horizontal_angle_degree) self.N = N self.sphereList = [] self.doDrawing = doDrawing r=self.size_y/2 if N > 0: r = r_estimate(self.size_x, self.size_y, packing_fraction, N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower) self.r_max = 0 for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereLinkableAdjustableInterfaceStrength("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i, theCanvas, False, self, self.size_x, self.size_y, permanent_ratio_central=self.permanent_ratio_central, permanent_ratio_tangential=self.permanent_ratio_tangential, keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant )) self.setShearInSpheres() self.setShearRateInSpheres() self.set_graphical_output_configuration( self.graphical_output_configuration) # To propagate it also to the spheres self.initiateGraphics() def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions, organized in fixed particles, adjustment of" \ "strength between permament and temporary interfaces\n" \ "\tSpheres with, bimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
Ancestors
- particleShearLinkableObjects.EnsembleCompactParticles.EnsembleCompactParticles
- particleShearLinkableObjects.CanvasPointsLinkable.CanvasPointsLinkable
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Methods
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
CanvasPointsLinkable
Expand source code
def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions, organized in fixed particles, adjustment of" \ "strength between permament and temporary interfaces\n" \ "\tSpheres with, bimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
class EnsembleFriction (size_x, size_y, N, packing_fraction=0.8, theCanvas=0, doDrawing=0, k=1, nu=0.01, m=1, bimodal_factor=1.4, k_t=1, nu_t=0.01, mu=0.1)
-
Ensemble of particles of type
SphereFriction
, with regular boundary conditionsThis class creates and maintains a list of objects of type
SphereFriction
. These objects are generically referred to as spheres and are stored inCanvasPoints.sphereList
.The ensemble class does not implement proper Lees-Edwards boundaries, but emulates neighboring spheres by creating replicates of the spheres near the boundares displaced by one positive or negative multiple of the xy dimensions of the simulation area.
Class defined in subpackage particleShearObjects
Initialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the average mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*muExpand source code
class EnsembleFriction(Ensemble): """Ensemble of particles of type `particleShear.SphereFriction`, with regular boundary conditions This class creates and maintains a list of objects of type `particleShear.SphereFriction`. These objects are generically referred to as spheres and are stored in `particleShear.CanvasPoints.sphereList`.\n The ensemble class does not implement proper Lees-Edwards boundaries, but emulates neighboring spheres by creating replicates of the spheres near the boundares displaced by one positive or negative multiple of the xy dimensions of the simulation area.\n Class defined in subpackage particleShearObjects""" def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=FALSE, doDrawing=FALSE, k=1, nu=0.01,m=1, bimodal_factor=1.4, k_t=1,nu_t=0.01,mu=0.1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the average mass per unit of depth in mg/m of depth\n `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution\n `k_t` Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces\n `nu_t` Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones\n `mu` Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu """ super(EnsembleFriction, self).__init__(size_x, size_y, N, packing_fraction, theCanvas, doDrawing, k, nu,m, bimodal_factor=bimodal_factor) self.N = N """Number of spheres""" self.packing_fraction = packing_fraction """Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area""" self.k_t=k_t """Transversal spring constant in (mg/s^2)/m of depth""" self.nu_t=nu_t """Transversal viscosity constant in (mg/s)/m of depth""" self.mu=mu """Friction coefficient""" def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleFriction` """ return "Frictional spheres, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def tangential_force(self): """Let all spheres calculate the total tangental force acting on them. This will check for contact and so is more time consuming than `particleShear.EnsembleFriction.tangential_force_from_neighbors`. For this function to work, the spheres must be of type `particleShear.SphereFriction`or a subclass thereof, or otherwise possess a method `tangential_force`\n This method is defined in class `particleShear.EnsembleFriction`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].tangential_force(self.sphereList[sphereIndex2], self.nu_t,self.mu,self.k,self.k_t) for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].tangential_force(self.spheres_boundary[sphereIndexBoundary], self.nu_t,self.mu,self.k,self.k_t) def tangential_force_from_neighbors(self): """Let all spheres calculate the total tangential force acting on from their known neighbors. This is quicker than `particleShear.EnsembleFriction.tangential_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`. For this function to work, the spheres must be of type `particleShear.SphereFriction`or a subclass thereof, or otherwise possess a method `tangential_force_from_neighbors`.\n This method is defined in class `particleShear.EnsembleFriction`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].tangential_force_from_neighbors(self.nu_t,self.mu,self.k,self.k_t) def do_rotational_acceleration(self, dt): """Have the spheres calculate their rotational acceleration from the tangential forces. For this function to work, the spheres must be of type `particleShear.SphereFriction` or subclass thereof, or alternatively, possess a method `do_rotational_acceleration`\n This method is defined in class `particleShear.EnsembleFriction`""" for theSphere in self.sphereList: theSphere.do_rotational_acceleration(dt) def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.EnsembleFriction`""" super(EnsembleFriction,self).mechanical_simulation_step_calculate_forces() self.tangential_force_from_neighbors() def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): """Calculate the accelerations resulting from the forces This method is defined in class `particleShear.EnsembleFriction`""" self.do_linear_acceleration(dt) self.do_rotational_acceleration(dt) self.cool(cool_factor)
Ancestors
- particleShearObjects.Ensemble.Ensemble
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Instance variables
var N
-
Number of spheres
var k_t
-
Transversal spring constant in (mg/s^2)/m of depth
var mu
-
Friction coefficient
var nu_t
-
Transversal viscosity constant in (mg/s)/m of depth
var packing_fraction
-
Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area
Methods
def do_rotational_acceleration(self, dt)
-
Have the spheres calculate their rotational acceleration from the tangential forces.
For this function to work, the spheres must be of type
SphereFriction
or subclass thereof, or alternatively, possess a methoddo_rotational_acceleration
This method is defined in class
EnsembleFriction
Expand source code
def do_rotational_acceleration(self, dt): """Have the spheres calculate their rotational acceleration from the tangential forces. For this function to work, the spheres must be of type `particleShear.SphereFriction` or subclass thereof, or alternatively, possess a method `do_rotational_acceleration`\n This method is defined in class `particleShear.EnsembleFriction`""" for theSphere in self.sphereList: theSphere.do_rotational_acceleration(dt)
def mechanical_simulation_step_calculate_acceleration(self, cool_factor=0.97, dt=1)
-
Calculate the accelerations resulting from the forces
This method is defined in class
EnsembleFriction
Expand source code
def mechanical_simulation_step_calculate_acceleration(self,cool_factor=0.97,dt=1): """Calculate the accelerations resulting from the forces This method is defined in class `particleShear.EnsembleFriction`""" self.do_linear_acceleration(dt) self.do_rotational_acceleration(dt) self.cool(cool_factor)
def mechanical_simulation_step_calculate_forces(self)
-
Calculate the forces acting on the spheres
This method is defined in class
EnsembleFriction
Expand source code
def mechanical_simulation_step_calculate_forces(self): """ Calculate the forces acting on the spheres This method is defined in class `particleShear.EnsembleFriction`""" super(EnsembleFriction,self).mechanical_simulation_step_calculate_forces() self.tangential_force_from_neighbors()
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
EnsembleFriction
Expand source code
def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleFriction` """ return "Frictional spheres, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def tangential_force(self)
-
Let all spheres calculate the total tangental force acting on them.
This will check for contact and so is more time consuming than
EnsembleFriction.tangential_force_from_neighbors()
. For this function to work, the spheres must be of typeSphereFriction
or a subclass thereof, or otherwise possess a methodtangential_force
This method is defined in class
EnsembleFriction
Expand source code
def tangential_force(self): """Let all spheres calculate the total tangental force acting on them. This will check for contact and so is more time consuming than `particleShear.EnsembleFriction.tangential_force_from_neighbors`. For this function to work, the spheres must be of type `particleShear.SphereFriction`or a subclass thereof, or otherwise possess a method `tangential_force`\n This method is defined in class `particleShear.EnsembleFriction`""" for sphereIndex in range(len(self.sphereList)): for sphereIndex2 in range(len(self.sphereList)): if sphereIndex != sphereIndex2: self.sphereList[sphereIndex].tangential_force(self.sphereList[sphereIndex2], self.nu_t,self.mu,self.k,self.k_t) for sphereIndexBoundary in range(len(self.spheres_boundary)): self.sphereList[sphereIndex].tangential_force(self.spheres_boundary[sphereIndexBoundary], self.nu_t,self.mu,self.k,self.k_t)
def tangential_force_from_neighbors(self)
-
Let all spheres calculate the total tangential force acting on from their known neighbors.
This is quicker than
EnsembleFriction.tangential_force()
but necessitates the neighbor relations to be already established viaCanvasPointsNeighbors.test_neighbor_relation()
. For this function to work, the spheres must be of typeSphereFriction
or a subclass thereof, or otherwise possess a methodtangential_force_from_neighbors
.This method is defined in class
EnsembleFriction
Expand source code
def tangential_force_from_neighbors(self): """Let all spheres calculate the total tangential force acting on from their known neighbors. This is quicker than `particleShear.EnsembleFriction.tangential_force` but necessitates the neighbor relations to be already established via `particleShear.CanvasPointsNeighbors.test_neighbor_relation`. For this function to work, the spheres must be of type `particleShear.SphereFriction`or a subclass thereof, or otherwise possess a method `tangential_force_from_neighbors`.\n This method is defined in class `particleShear.EnsembleFriction`""" for sphereIndex in range(len(self.sphereList)): self.sphereList[sphereIndex].tangential_force_from_neighbors(self.nu_t,self.mu,self.k,self.k_t)
class EnsembleFrictionLeesEdwards (size_x, size_y, N, packing_fraction=0.8, theCanvas=0, doDrawing=0, k=1, nu=0.01, m=1, bimodal_factor=1.4, k_t=1, nu_t=0.01, mu=0.1)
-
Ensemble of particles of type
SphereFrictionLeesEdwards
, with Lees-Edwards boundary conditionsThis class creates and maintains a list of objects of type
SphereFrictionLeesEdwards
. These objects are generically referred to as spheres and are stored inCanvasPoints.sphereList
.This class uses Lees-Edwards boundary conditions
Class defined in subpackage particleShearObjects
Initialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the average mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*muExpand source code
class EnsembleFrictionLeesEdwards(CanvasPointsFrictionElasticityLeesEdwards): """Ensemble of particles of type `particleShear.SphereFrictionLeesEdwards`, with Lees-Edwards boundary conditions This class creates and maintains a list of objects of type `particleShear.SphereFrictionLeesEdwards`. These objects are generically referred to as spheres and are stored in `particleShear.CanvasPoints.sphereList`.\n This class uses Lees-Edwards boundary conditions\n Class defined in subpackage particleShearObjects""" def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=FALSE, doDrawing=FALSE, k=1, nu=0.01,m=1, bimodal_factor=1.4, k_t=1,nu_t=0.01,mu=0.1): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the average mass per unit of depth in mg/m of depth\n `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution\n `k_t` Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces\n `nu_t` Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones\n `mu` Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu """ super(EnsembleFrictionLeesEdwards, self).__init__( size_x, size_y, theCanvas, False, k, nu, m, k_t=k_t,nu_t=nu_t,mu=mu) self.bimodal_upper = math.sqrt(bimodal_factor) """Upper multiplication factor for the bimodal sphere size distribution""" self.bimodal_lower = math.sqrt(1 / bimodal_factor) """Lower multiplication factor for the bimodal sphere size distribution""" self.N = N """Number of spheres""" self.packing_fraction = packing_fraction """Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area""" self.doDrawing = doDrawing """Boolean: Should we draw the ensemble during simulation""" self.sphereList = [] """List of the spheres""" r=self.size_y/2 # Default value if no spheres are specified if N > 0: r = r_estimate(self.size_x, self.size_y, packing_fraction, N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower) self.r_max = 0 """Largest sphere radius""" for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereFrictionLeesEdwards("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i, theCanvas, False,self, self.size_x, self.size_y)) self.setShearInSpheres() self.setShearRateInSpheres() self.initiateGraphics() def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleFrictionLeesEdwards` """ return "Frictional spheres under Lees-Edwards boundary conditions\n" \ "\tbimodal size distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.EnsembleFrictionLeesEdwards`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
Ancestors
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Instance variables
var N
-
Number of spheres
var bimodal_lower
-
Lower multiplication factor for the bimodal sphere size distribution
var bimodal_upper
-
Upper multiplication factor for the bimodal sphere size distribution
var doDrawing
-
Boolean: Should we draw the ensemble during simulation
var packing_fraction
-
Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area
var r_max
-
Largest sphere radius
var sphereList
-
List of the spheres
Methods
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
EnsembleFrictionLeesEdwards
Expand source code
def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleFrictionLeesEdwards` """ return "Frictional spheres under Lees-Edwards boundary conditions\n" \ "\tbimodal size distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def random_radius_bimodal(self, r)
-
Choose randomly one of the two bimodal radius values.
Method defined in
EnsembleFrictionLeesEdwards
Expand source code
def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.EnsembleFrictionLeesEdwards`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
class EnsembleLeesEdwards (size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01, m=1, bimodal_factor=1.4)
-
Ensemble of particles of type
SphereLeesEdwards
, with Lees-Edwards boundary conditionsThis class creates and maintains a list of objects of type
SphereLeesEdwards
. These objects are generically referred to as spheres and are stored inCanvasPoints.sphereList
.This class uses Lees-Edwards boundary conditions
Class defined in subpackage particleShearObjects
Initialize self
- parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationN
The number of spheres to placepacking_fraction
The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)theCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the average mass per unit of depth in mg/m of depthbimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionExpand source code
class EnsembleLeesEdwards(CanvasPointsBasicElasticityLeesEdwards): """Ensemble of particles of type `particleShear.SphereLeesEdwards`, with Lees-Edwards boundary conditions This class creates and maintains a list of objects of type `particleShear.SphereLeesEdwards`. These objects are generically referred to as spheres and are stored in `particleShear.CanvasPoints.sphereList`.\n This class uses Lees-Edwards boundary conditions\n Class defined in subpackage particleShearObjects""" def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=False, doDrawing=False, k=1, nu=0.01,m=1, bimodal_factor=1.4): """Initialize self - **parameters**\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) `theCanvas` possibility to transmit a `tkinter` `Canvas` object for graphical output\n `doDrawing` Flag to indicate whether graphical output should be produced or not\n `k` Central spring constant in (mg/s^2)/m of depth; so F=-k*x*L with L the depth of the stack (the simulation is 2D) and x the compression\n `nu` Central viscosity force constant in (mg/s)/m of depth. This is to have F=nu*v*L, L again the depth\n `m` is the average mass per unit of depth in mg/m of depth\n `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution """ super(EnsembleLeesEdwards,self).__init__(size_x, size_y, theCanvas=theCanvas, doDrawing=False, k=k, nu=nu,m=m) self.bimodal_upper = math.sqrt(bimodal_factor) """Upper multiplication factor for the bimodal sphere size distribution""" self.bimodal_lower = math.sqrt(1 / bimodal_factor) """Lower multiplication factor for the bimodal sphere size distribution""" self.N = N """Number of spheres""" self.packing_fraction = packing_fraction """Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area""" self.doDrawing=doDrawing """Boolean: Should we draw the ensemble during simulation""" self.sphereList = [] """List of the spheres""" r=self.size_y/2 # default value if N>0: r = r_estimate(self.size_x, self.size_y, packing_fraction, N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower) self.r_max = 0 """Largest sphere radius""" for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereLeesEdwards("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i, theCanvas, False,self,self.size_x,self.size_y)) self.setShearInSpheres() self.initiateGraphics() def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleLeesEdwards` """ return "Non-frictional spheres with Lees-Edwards boundary conditions, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.EnsembleLeesEdwards`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
Ancestors
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Instance variables
var N
-
Number of spheres
var bimodal_lower
-
Lower multiplication factor for the bimodal sphere size distribution
var bimodal_upper
-
Upper multiplication factor for the bimodal sphere size distribution
var doDrawing
-
Boolean: Should we draw the ensemble during simulation
var packing_fraction
-
Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area
var r_max
-
Largest sphere radius
var sphereList
-
List of the spheres
Methods
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
EnsembleLeesEdwards
Expand source code
def particle_info(self): """Provide short description of particle type of inclusion into output file Method defined in `particleShear.EnsembleLeesEdwards` """ return "Non-frictional spheres with Lees-Edwards boundary conditions, \n\tbimodal distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def random_radius_bimodal(self, r)
-
Choose randomly one of the two bimodal radius values.
Method defined in
EnsembleLeesEdwards
Expand source code
def random_radius_bimodal(self,r): """Choose randomly one of the two bimodal radius values. Method defined in `particleShear.EnsembleLeesEdwards`""" return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
class EnsembleLinkable (size_x, size_y, N, packing_fraction=0.8, theCanvas=0, doDrawing=0, k=1, nu=0.01, m=1, bimodal_factor=1.4, k_t=1, nu_t=0.01, mu=0.1)
-
Canvas for placing
SphereLinkable
objects (referred to as spheres). This class maintains a list of objects of typeSphereLinkable
and adds elementary functions related to crosslinkingInitialize self
-
parameters
size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationtheCanvas
possibility to transmit atkinter
Canvas
object for graphical outputdoDrawing
Flag to indicate whether graphical output should be produced or notk
Central spring constant in (mg/s^2)/m of depth; so F=-kxL with L the depth of the stack (the simulation is 2D) and x the compressionnu
Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depthm
is the mass per unit of depth in mg/m of depthk_t
Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfacesnu_t
Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping onesmu
Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu
Expand source code
class EnsembleLinkable(CanvasPointsLinkable): def __init__(self, size_x, size_y, N, packing_fraction=0.8, theCanvas=FALSE, doDrawing=FALSE, k=1, nu=0.01,m=1, bimodal_factor=1.4, k_t=1,nu_t=0.01,mu=0.1): super(EnsembleLinkable, self).__init__( size_x, size_y, theCanvas, False, k, nu, m,k_t=k_t,nu_t=nu_t,mu=mu) self.bimodal_upper = math.sqrt(bimodal_factor) self.bimodal_lower = math.sqrt(1 / bimodal_factor) self.N = N self.packing_fraction = packing_fraction self.sphereList = [] self.doDrawing = doDrawing r=self.size_y/2 if N > 0: r = r_estimate(self.size_x, self.size_y, packing_fraction, N, bimodal_upper=self.bimodal_upper, bimodal_lower=self.bimodal_lower) self.r_max = 0 for i in range(N): r_sphere = self.random_radius_bimodal(r) if r_sphere > self.r_max: self.r_max = r_sphere self.sphereList.append(SphereLinkable("grey", random.randrange(0, size_x), random.randrange(0, size_y), r_sphere * 2, adjust_m(m, r_sphere, r), i, theCanvas, False,self, self.size_x, self.size_y)) self.setShearInSpheres() self.setShearRateInSpheres() self.set_graphical_output_configuration( self.graphical_output_configuration) # To propagate it also to the spheres self.initiateGraphics() def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions with possible permanent links between neighbors\n" \ "\tbimodal size distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg" def random_radius_bimodal(self,r): return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
Ancestors
- particleShearLinkableObjects.CanvasPointsLinkable.CanvasPointsLinkable
- particleShearBase.CanvasPointsFrictionElasticityLeesEdwards.CanvasPointsFrictionElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticityLeesEdwards.CanvasPointsBasicElasticityLeesEdwards
- particleShearBase.CanvasPointsBasicElasticity.CanvasPointsBasicElasticity
- particleShearBase.CanvasPointsShear.CanvasPointsShear
- particleShearBase.CanvasPointsNeighbors.CanvasPointsNeighbors
- particleShearBase.CanvasPointsMass.CanvasPointsMass
- particleShearBase.CanvasPoints.CanvasPoints
Methods
def particle_info(self)
-
Provide short description of particle type of inclusion into output file
Method defined in
CanvasPointsLinkable
Expand source code
def particle_info(self): return "Frictional spheres under Lees-Edwards boundary conditions with possible permanent links between neighbors\n" \ "\tbimodal size distribution, equal probability, \n\tr1="+\ str(self.r_max*0.8/1.2)+"r2="+str(self.r_max)+"\n\tidentical mass="+str(self.sphereList[0].m)+"mg"
def random_radius_bimodal(self, r)
-
Expand source code
def random_radius_bimodal(self,r): return r*(self.bimodal_lower+(self.bimodal_upper-self.bimodal_lower)*random.randint(0, 1))
-
class EvaluationHandler
-
Expand source code
class EvaluationHandler(): def __init__(self): self.strain = [] self.strain_rate = [] self.force = [] self.t = [] self.shear_stress=[] self.Gprime=0 self.viscosity=0 self.xg=[0,0] # Center of gravity, in microns self.total_mass = 0 # Total mass present, in mg self.v_mean=[0,0] # Mean speed, in micrometers/s self.angular_momentum_around_origin=0 self.theEvaluator=False # Stress tensors to be stored self.stress_tensor_LW = [] self.stress_tensor_linear_acceleration_otsuki = [] self.stress_tensor_with_external_forces = [] self.stress_tensor_linear_acceleration = [] self.stress_tensor_unbalanced_forces = [] self.stress_tensor_unbalanced_torque = [] self.stress_tensor_internal_torque = [] self.stress_tensor_spin_kinetic_energy = [] self.overall_stress_tensor = [] self.overall_stress_tensor2 = [] def record(self, theEnsemble,theEvaluator=False): self.t.append(theEnsemble.t) if (not theEvaluator) and (not self.theEvaluator): self.theEvaluator=StressTensorEvaluation(theEnsemble.size_x,theEnsemble.size_y) if theEvaluator: self.theEvaluator=theEvaluator # Check whether the paired register is really paired sum_x = 0 sum_y = 0 total = 0 for thePair in theEnsemble.force_register.pair_register: sum_x = sum_x + thePair[2][0] sum_y = sum_y + thePair[2][1] total = total + abs(thePair[2][0]+thePair[2][1]) if total > 0: if not (abs(sum_x)/total<1e-14 and abs(sum_y)/total < 1e-14): print ("Stress tensor evaluation: Error in force register, paired forces do not match:",sum_x,sum_y) # Get the center of gravity: xg_x = 0 xg_y = 0 vm_x = 0 vm_y = 0 self.total_mass=0 self.angular_momentum_around_origin=0 for theSphere in theEnsemble.sphereList: self.total_mass=self.total_mass+theSphere.m xg_x = xg_x + theSphere.x*theSphere.m xg_y = xg_y + theSphere.y*theSphere.m vm_x = vm_x + theSphere.xspeed*theSphere.m vm_y = vm_y + theSphere.yspeed * theSphere.m self.angular_momentum_around_origin=self.angular_momentum_around_origin+\ theSphere.m*theSphere.x*theSphere.yspeed-\ theSphere.m*theSphere.y*theSphere.xspeed+\ theSphere.omega*theSphere.inertia self.xg=[xg_x/self.total_mass,xg_y/self.total_mass] self.v_mean=[vm_x/self.total_mass,vm_y/self.total_mass] theEvaluator.evaluate_stress_tensors(theEnsemble.force_register,theEnsemble.movableSphereList(),theEnsemble.shear_rate) #print("EvaluationHandler: overall stress tensor ",theEvaluator.overall_stress_tensor) self.strain.append(theEnsemble.shear) self.strain_rate.append(theEnsemble.shear_rate) self.shear_stress.append(theEvaluator.evaluate_quasistatic_shear_stress()) # This is overall shear stress self.force.append(theEvaluator.evaluate_externally_applied_shear_stress()) # This is shear stress as theoretically defined by average surface force on # arbitrary sections. # Stress tensors to be stored self.stress_tensor_LW.append(theEvaluator.stress_tensor_LW) self.stress_tensor_linear_acceleration_otsuki.append(theEvaluator.stress_tensor_linear_acceleration_otsuki) self.stress_tensor_with_external_forces.append(theEvaluator.stress_tensor_with_external_forces) self.stress_tensor_linear_acceleration.append(theEvaluator.stress_tensor_linear_acceleration) self.stress_tensor_unbalanced_forces.append(theEvaluator.stress_tensor_unbalanced_forces) self.stress_tensor_unbalanced_torque.append(theEvaluator.stress_tensor_unbalanced_torque) self.stress_tensor_internal_torque.append(theEvaluator.stress_tensor_internal_torque) self.stress_tensor_spin_kinetic_energy.append(theEvaluator.stress_tensor_spin_kinetic_energy) self.overall_stress_tensor.append(theEvaluator.overall_stress_tensor) sum_strain=0 sum_strain_rate=0 self.Gprime=0 self.viscosity=0 for index in range(len(self.t)): sum_strain=sum_strain+self.strain[index]*self.strain[index] sum_strain_rate=sum_strain_rate+self.strain_rate[index]*self.strain_rate[index] self.Gprime=self.Gprime-self.strain[index]*self.shear_stress[index] self.viscosity=self.viscosity-self.strain_rate[index]*self.shear_stress[index] if sum_strain > 0: self.Gprime=self.Gprime/sum_strain if sum_strain_rate > 0: self.viscosity=self.viscosity/sum_strain_rate
Subclasses
- particleShearSimulation.EvaluationHandlerPlotter.EvaluationHandlerPlotter
Methods
def record(self, theEnsemble, theEvaluator=False)
-
Expand source code
def record(self, theEnsemble,theEvaluator=False): self.t.append(theEnsemble.t) if (not theEvaluator) and (not self.theEvaluator): self.theEvaluator=StressTensorEvaluation(theEnsemble.size_x,theEnsemble.size_y) if theEvaluator: self.theEvaluator=theEvaluator # Check whether the paired register is really paired sum_x = 0 sum_y = 0 total = 0 for thePair in theEnsemble.force_register.pair_register: sum_x = sum_x + thePair[2][0] sum_y = sum_y + thePair[2][1] total = total + abs(thePair[2][0]+thePair[2][1]) if total > 0: if not (abs(sum_x)/total<1e-14 and abs(sum_y)/total < 1e-14): print ("Stress tensor evaluation: Error in force register, paired forces do not match:",sum_x,sum_y) # Get the center of gravity: xg_x = 0 xg_y = 0 vm_x = 0 vm_y = 0 self.total_mass=0 self.angular_momentum_around_origin=0 for theSphere in theEnsemble.sphereList: self.total_mass=self.total_mass+theSphere.m xg_x = xg_x + theSphere.x*theSphere.m xg_y = xg_y + theSphere.y*theSphere.m vm_x = vm_x + theSphere.xspeed*theSphere.m vm_y = vm_y + theSphere.yspeed * theSphere.m self.angular_momentum_around_origin=self.angular_momentum_around_origin+\ theSphere.m*theSphere.x*theSphere.yspeed-\ theSphere.m*theSphere.y*theSphere.xspeed+\ theSphere.omega*theSphere.inertia self.xg=[xg_x/self.total_mass,xg_y/self.total_mass] self.v_mean=[vm_x/self.total_mass,vm_y/self.total_mass] theEvaluator.evaluate_stress_tensors(theEnsemble.force_register,theEnsemble.movableSphereList(),theEnsemble.shear_rate) #print("EvaluationHandler: overall stress tensor ",theEvaluator.overall_stress_tensor) self.strain.append(theEnsemble.shear) self.strain_rate.append(theEnsemble.shear_rate) self.shear_stress.append(theEvaluator.evaluate_quasistatic_shear_stress()) # This is overall shear stress self.force.append(theEvaluator.evaluate_externally_applied_shear_stress()) # This is shear stress as theoretically defined by average surface force on # arbitrary sections. # Stress tensors to be stored self.stress_tensor_LW.append(theEvaluator.stress_tensor_LW) self.stress_tensor_linear_acceleration_otsuki.append(theEvaluator.stress_tensor_linear_acceleration_otsuki) self.stress_tensor_with_external_forces.append(theEvaluator.stress_tensor_with_external_forces) self.stress_tensor_linear_acceleration.append(theEvaluator.stress_tensor_linear_acceleration) self.stress_tensor_unbalanced_forces.append(theEvaluator.stress_tensor_unbalanced_forces) self.stress_tensor_unbalanced_torque.append(theEvaluator.stress_tensor_unbalanced_torque) self.stress_tensor_internal_torque.append(theEvaluator.stress_tensor_internal_torque) self.stress_tensor_spin_kinetic_energy.append(theEvaluator.stress_tensor_spin_kinetic_energy) self.overall_stress_tensor.append(theEvaluator.overall_stress_tensor) sum_strain=0 sum_strain_rate=0 self.Gprime=0 self.viscosity=0 for index in range(len(self.t)): sum_strain=sum_strain+self.strain[index]*self.strain[index] sum_strain_rate=sum_strain_rate+self.strain_rate[index]*self.strain_rate[index] self.Gprime=self.Gprime-self.strain[index]*self.shear_stress[index] self.viscosity=self.viscosity-self.strain_rate[index]*self.shear_stress[index] if sum_strain > 0: self.Gprime=self.Gprime/sum_strain if sum_strain_rate > 0: self.viscosity=self.viscosity/sum_strain_rate
class EvaluationHandlerPlotter (theTk, theCanvas, x_scale=1, y_scale_strain=1, y_scale_force=1, r=10, offset_x=0, offset_y=0, plotStress=True)
-
Expand source code
class EvaluationHandlerPlotter(EvaluationHandler): def __init__(self, theTk, theCanvas, x_scale=1, y_scale_strain=1, y_scale_force=1, r=10, offset_x=0, offset_y=0, plotStress=True): self.plotStress=plotStress super(EvaluationHandlerPlotter, self).__init__() self.theTk = theTk self.theCanvas = theCanvas self.x_scale = x_scale self.y_scale_strain = y_scale_strain self.y_scale_force = y_scale_force self.r = r self.offset_x = offset_x self.offset_y = offset_y def record(self, theEnsemble,theEvaluator=False): super(EvaluationHandlerPlotter, self).record(theEnsemble,theEvaluator) if self.plotStress: t = self.t[len(self.t) - 1] strain = self.strain[len(self.strain) - 1] force = self.force[len(self.force) - 1] shear_stress = self.shear_stress[len(self.shear_stress) - 1] self.plot(t, strain, force - self.force[0], shear_stress - self.shear_stress[0]) def plot(self, t, strain, force,shear_stress): # First, x coordinates (common) x1 = t * self.x_scale - self.r + self.offset_x x2 = x1 + 2 * self.r # Then, y for volume shear stress y1 = -shear_stress * self.y_scale_force - self.r + self.offset_y #Here, the minus sign is just because of the inverted # coordinate system of the canvas graphics (tkinter) y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="red",outline="red") # Then, y for force y1 = -force * self.y_scale_force - self.r + self.offset_y # Here, the minus sign is just because of the inverted # coordinate system of the canvas graphics (tkinter) y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="blue", outline="blue") # Then, y for strain y1 = -strain * self.y_scale_strain - self.r + self.offset_y y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="green",outline="green") self.theTk.update()
Ancestors
- particleShearSimulation.EvaluationHandler.EvaluationHandler
Methods
def plot(self, t, strain, force, shear_stress)
-
Expand source code
def plot(self, t, strain, force,shear_stress): # First, x coordinates (common) x1 = t * self.x_scale - self.r + self.offset_x x2 = x1 + 2 * self.r # Then, y for volume shear stress y1 = -shear_stress * self.y_scale_force - self.r + self.offset_y #Here, the minus sign is just because of the inverted # coordinate system of the canvas graphics (tkinter) y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="red",outline="red") # Then, y for force y1 = -force * self.y_scale_force - self.r + self.offset_y # Here, the minus sign is just because of the inverted # coordinate system of the canvas graphics (tkinter) y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="blue", outline="blue") # Then, y for strain y1 = -strain * self.y_scale_strain - self.r + self.offset_y y2 = y1 + 2 * self.r self.theCanvas.create_oval(x1, y1, x2, y2, fill="green",outline="green") self.theTk.update()
def record(self, theEnsemble, theEvaluator=False)
-
Expand source code
def record(self, theEnsemble,theEvaluator=False): super(EvaluationHandlerPlotter, self).record(theEnsemble,theEvaluator) if self.plotStress: t = self.t[len(self.t) - 1] strain = self.strain[len(self.strain) - 1] force = self.force[len(self.force) - 1] shear_stress = self.shear_stress[len(self.shear_stress) - 1] self.plot(t, strain, force - self.force[0], shear_stress - self.shear_stress[0])
class Force_register
-
Provide accounting of the forces acting on the particles.
Detailed accounting of the forces acting on the particles allows to calculate the relevant average stress tensors at each simulation time point by means of the
StressTensorEvaluation
class.Sub-package particleShearBase
Initialize the force register Provide default empty intance variables
Expand source code
class Force_register(): """Provide accounting of the forces acting on the particles. Detailed accounting of the forces acting on the particles allows to calculate the relevant average stress tensors at each simulation time point by means of the `particleShear.StressTensorEvaluation` class.\n Sub-package particleShearBase""" def __init__(self): """Initialize the force register Provide default empty intance variables""" self.pair_register=[] """ Hold the elementary internal pairwise forces""" self.internal_moment_register = [] """ Hold the fully internally generated torques""" self.total_particle_force_register=[] """ Hold the single total forces acting single particles""" self.external_force_register=[] """Hold single forces acting from the outside (across the canvas boudnary) on the particles""" self.external_moment_register = [] """Hold single torques acting from the outside (across the canvas boudnary) on the particles""" self.unbalanced_moment_register = [] """Hold the net torque moments causing rotational acceleration""" def reset_force_register(self): """Reset the force register to its original state This method is defined in class `particleShear.Force_register`""" self.pair_register = [] self.total_particle_force_register = [] # self.external_force_register = [] self.unbalanced_moment_register=[] self.internal_moment_register=[] self.external_moment_register = [] def record_individual_internal_force(self,target,source,force_vector): """Record an individual internal force. The force magnitude and direction is described by the force_vector. The target describes the sphere on which the force acts (it is applied to the center); the source describes the sphere from which the force originates. Due to Newton's law of action an reaction, there should always be a second entry in the `particleShear.Force_register.pair_register` with opposing force vector, and exchanged source and target. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.\n This method is defined in class `particleShear.Force_register`""" self.pair_register.append([target,source,force_vector]) def record_total_particle_force(self,target,force_vector): """Record a net total particle force As there should be only one net particle force for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created Method defined in class `particleShear.Force_register`""" # First, look whether there is already an entry found_index = -1 for ind in range(len(self.total_particle_force_register)): if self.total_particle_force_register[ind][0]==target: found_index=ind # If there is an entry, update it if(found_index>=0): self.total_particle_force_register[found_index][1]=force_vector else: # else add one self.total_particle_force_register.append([target,force_vector]) def record_external_force(self, target, force_vector): """ Add an external force External forces are coming from either outside the area under examination or from that is not free to move (see `particleShear.CanvasPointsShear.canMove`)\n Method defined in class `particleShear.Force_register`""" self.external_force_register.append ([target, force_vector]) def record_external_torque(self, target, moment): """ Add an external torque External torques are coming from either outside the area under examination or from that is not free to move (see `particleShear.CanvasPointsShear.canMove`)\n Method defined in class `particleShear.Force_register`""" self.external_moment_register.append ([target, moment]) def record_individual_internal_torque(self,target,source,moment): """Record an individual internal moment. The torque is described by the moment argument. The target describes the sphere on which the torque acts (it is applied homogeneously to cause correct acceleration); the source describes the sphere which by frictional interaction has helped generate the moment (typically, in `particleShear.CircleFrictionElasticity.distribute_tangential_couple`). Since frictional interaction tends to generate torque on both partners, there is generally a second entry with target and source inversed. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.\n This method is defined in class `particleShear.Force_register`""" self.internal_moment_register.append([target,source,moment]) def record_unbalanced_moment(self,target,moment): """ Add net torque on a sphere As there should be only one net particle torque for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created Method defined in class `particleShear.Force_register`""" found_index = -1 for ind in range(len(self.unbalanced_moment_register)): if self.unbalanced_moment_register[ind][0] == target: found_index = ind # If there is an entry, update it if (found_index >= 0): self.unbalanced_moment_register[found_index][1] = moment else: # else add one self.unbalanced_moment_register.append([target, moment])
Instance variables
var external_force_register
-
Hold single forces acting from the outside (across the canvas boudnary) on the particles
var external_moment_register
-
Hold single torques acting from the outside (across the canvas boudnary) on the particles
var internal_moment_register
-
Hold the fully internally generated torques
var pair_register
-
Hold the elementary internal pairwise forces
var total_particle_force_register
-
Hold the single total forces acting single particles
var unbalanced_moment_register
-
Hold the net torque moments causing rotational acceleration
Methods
def record_external_force(self, target, force_vector)
-
Add an external force
External forces are coming from either outside the area under examination or from that is not free to move (see
CanvasPointsShear.canMove()
)Method defined in class
Force_register
Expand source code
def record_external_force(self, target, force_vector): """ Add an external force External forces are coming from either outside the area under examination or from that is not free to move (see `particleShear.CanvasPointsShear.canMove`)\n Method defined in class `particleShear.Force_register`""" self.external_force_register.append ([target, force_vector])
def record_external_torque(self, target, moment)
-
Add an external torque
External torques are coming from either outside the area under examination or from that is not free to move (see
CanvasPointsShear.canMove()
)Method defined in class
Force_register
Expand source code
def record_external_torque(self, target, moment): """ Add an external torque External torques are coming from either outside the area under examination or from that is not free to move (see `particleShear.CanvasPointsShear.canMove`)\n Method defined in class `particleShear.Force_register`""" self.external_moment_register.append ([target, moment])
def record_individual_internal_force(self, target, source, force_vector)
-
Record an individual internal force.
The force magnitude and direction is described by the force_vector. The target describes the sphere on which the force acts (it is applied to the center); the source describes the sphere from which the force originates. Due to Newton's law of action an reaction, there should always be a second entry in the
Force_register.pair_register
with opposing force vector, and exchanged source and target. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.This method is defined in class
Force_register
Expand source code
def record_individual_internal_force(self,target,source,force_vector): """Record an individual internal force. The force magnitude and direction is described by the force_vector. The target describes the sphere on which the force acts (it is applied to the center); the source describes the sphere from which the force originates. Due to Newton's law of action an reaction, there should always be a second entry in the `particleShear.Force_register.pair_register` with opposing force vector, and exchanged source and target. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.\n This method is defined in class `particleShear.Force_register`""" self.pair_register.append([target,source,force_vector])
def record_individual_internal_torque(self, target, source, moment)
-
Record an individual internal moment.
The torque is described by the moment argument. The target describes the sphere on which the torque acts (it is applied homogeneously to cause correct acceleration); the source describes the sphere which by frictional interaction has helped generate the moment (typically, in
CircleFrictionElasticity.distribute_tangential_couple()
). Since frictional interaction tends to generate torque on both partners, there is generally a second entry with target and source inversed. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.This method is defined in class
Force_register
Expand source code
def record_individual_internal_torque(self,target,source,moment): """Record an individual internal moment. The torque is described by the moment argument. The target describes the sphere on which the torque acts (it is applied homogeneously to cause correct acceleration); the source describes the sphere which by frictional interaction has helped generate the moment (typically, in `particleShear.CircleFrictionElasticity.distribute_tangential_couple`). Since frictional interaction tends to generate torque on both partners, there is generally a second entry with target and source inversed. However, this function does not check for the presence of such a homologous entry, this needs to be done in the code invoking this method.\n This method is defined in class `particleShear.Force_register`""" self.internal_moment_register.append([target,source,moment])
def record_total_particle_force(self, target, force_vector)
-
Record a net total particle force
As there should be only one net particle force for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created
Method defined in class
Force_register
Expand source code
def record_total_particle_force(self,target,force_vector): """Record a net total particle force As there should be only one net particle force for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created Method defined in class `particleShear.Force_register`""" # First, look whether there is already an entry found_index = -1 for ind in range(len(self.total_particle_force_register)): if self.total_particle_force_register[ind][0]==target: found_index=ind # If there is an entry, update it if(found_index>=0): self.total_particle_force_register[found_index][1]=force_vector else: # else add one self.total_particle_force_register.append([target,force_vector])
def record_unbalanced_moment(self, target, moment)
-
Add net torque on a sphere
As there should be only one net particle torque for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created
Method defined in class
Force_register
Expand source code
def record_unbalanced_moment(self,target,moment): """ Add net torque on a sphere As there should be only one net particle torque for each particle, the method first looks for an entry corresponding to target. If such an entry is found, the force is updated, otherwise a new entry is created Method defined in class `particleShear.Force_register`""" found_index = -1 for ind in range(len(self.unbalanced_moment_register)): if self.unbalanced_moment_register[ind][0] == target: found_index = ind # If there is an entry, update it if (found_index >= 0): self.unbalanced_moment_register[found_index][1] = moment else: # else add one self.unbalanced_moment_register.append([target, moment])
def reset_force_register(self)
-
Reset the force register to its original state
This method is defined in class
Force_register
Expand source code
def reset_force_register(self): """Reset the force register to its original state This method is defined in class `particleShear.Force_register`""" self.pair_register = [] self.total_particle_force_register = [] # self.external_force_register = [] self.unbalanced_moment_register=[] self.internal_moment_register=[] self.external_moment_register = []
class Graphical_output_configuration (draw_spheres_as='spheres', color_spheres_volume='grey', color_spheres_boundary='green', draw_rotation_line=False, color_rotation_line='black', draw_active_interface=True, color_interface_locked='red', color_interface_slip='green', draw_permanent_connections=True, color_permanent_connections='blue', draw_permanent_interfaces=False, draw_volume_forces=False, color_volume_forces='darkorchid', draw_boundary_forces=False, color_boundary_forces='darkorchid', force_scale=1000, draw_speeds=False, color_speeds='black')
-
Provide configuration options for graphical output
Graphical_output_configuration
objects are used to configure graphical representation of the spheres (classCircle
and derived).Sub-package particleShearBase
Initialization
Expand source code
class Graphical_output_configuration: """Provide configuration options for graphical output `particleShear.Graphical_output_configuration` objects are used to configure graphical representation of the spheres (class `particleShear.Circle` and derived).\n Sub-package particleShearBase """ def __init__(self, draw_spheres_as="spheres",color_spheres_volume="grey",color_spheres_boundary="green", draw_rotation_line=False, color_rotation_line="black", draw_active_interface=True,color_interface_locked="red",color_interface_slip="green", draw_permanent_connections=True,color_permanent_connections="blue", draw_permanent_interfaces=False, draw_volume_forces=False,color_volume_forces="darkorchid", draw_boundary_forces=False,color_boundary_forces="darkorchid", force_scale=1000, draw_speeds=False,color_speeds="black"): """Initialization""" self.draw_spheres_as = draw_spheres_as """ Display of the spheres. Can be "spheres", "dots" or "none", setting used by the spheres (Class `particleShear.Circle` and derived)""" self.color_spheres_volume = color_spheres_volume # Can be any valid python color, used by the spheres """ Drawing color of the spheres. Can be any valid python color, used by the spheres (Class `particleShear.Circle` and derived)""" self.color_spheres_boundary = color_spheres_boundary # Can be any valid python color, used by the spheres """ Drawing color of the spheres on boundary. Not currently implemented. Can be any valid python color, to be used by the spheres (Class `particleShear.Circle` and derived)""" self.draw_rotation_line = draw_rotation_line """ Boolean, indicates whehter the spheres to display rotation status (`particleShear.SphereFriction` and derived) """ self.color_rotation_line = color_rotation_line # Can be any valid python color, used by the spheres """ Python color, indicates color for the rotation line (`particleShear.SphereFriction` and derived) """ self.draw_active_interface = draw_active_interface """ Boolean, indicates whether currently active `particleShear.neighbor_relation`s should be shown graphically. Used by the spheres (`particleShear.CircleMassNeighbors` and derived)""" self.color_interface_locked = color_interface_locked """ Can be any valid python color, used by the spheres (`particleShear.CircleMassNeighbors` and derived). This indicates the color for representation of the locked `particleShear.neighbor_relation`s""" self.color_interface_slip = color_interface_slip """ Can be any valid python color, used by the spheres (`particleShear.CircleMassNeighbors` and derived). This indicates the color for representation of the locked `particleShear.neighbor_relation`s""" self.draw_permanent_connections = draw_permanent_connections """Boolean, indicates to the spherees (`particleShear.SphereLinkable` and derived) whether permanent connections should be drawn""" self.draw_permanent_interfaces=draw_permanent_interfaces """Boolean, indicates to the spherees (`particleShear.SphereLinkable` and derived) whether the interfaces active for the permanent connections should be drawn""" self.color_permanent_connections = color_permanent_connections """Valid Python color for the permanent connections, drawn from center to center """ self.draw_volume_forces = draw_volume_forces """Boolean, used by `particleShear.StressTensorEvaluation` to show final forces on the non-constrained, totally interior spheres""" self.color_volume_forces = color_volume_forces """Any valid python color, used by `particleShear.StressTensorEvaluation` to show final forces on the non-constrained, interior spheres""" self.draw_boundary_forces = draw_boundary_forces """ Boolean, used by `particleShear.StressTensorEvaluation` to show final forces applied on the non-constrained spheres partially in contact with the constrained boundary spheres""" self.color_boundary_forces = color_boundary_forces """ Python color, used by `particleShear.StressTensorEvaluation` to show final forces applied on the non-constrained spheres partially in contact with the constrained boundary spheres""" self.force_scale = force_scale """ To graphically scale the forces to have reasonable length """ self.draw_speeds = draw_speeds """Boolean, indicates whether spheres should draw their speed as vectors""" self.color_speeds = color_speeds """Python color, used by the spheres for the color of the speed vector"""
Instance variables
var color_boundary_forces
-
Python color, used by
StressTensorEvaluation
to show final forces applied on the non-constrained spheres partially in contact with the constrained boundary spheres var color_interface_locked
-
Can be any valid python color, used by the spheres (
CircleMassNeighbors
and derived). This indicates the color for representation of the lockedneighbor_relation
s var color_interface_slip
-
Can be any valid python color, used by the spheres (
CircleMassNeighbors
and derived). This indicates the color for representation of the lockedneighbor_relation
s var color_permanent_connections
-
Valid Python color for the permanent connections, drawn from center to center
var color_rotation_line
-
Python color, indicates color for the rotation line (
SphereFriction
and derived) var color_speeds
-
Python color, used by the spheres for the color of the speed vector
var color_spheres_boundary
-
Drawing color of the spheres on boundary. Not currently implemented. Can be any valid python color, to be used by the spheres (Class
Circle
and derived) var color_spheres_volume
-
Drawing color of the spheres. Can be any valid python color, used by the spheres (Class
Circle
and derived) var color_volume_forces
-
Any valid python color, used by
StressTensorEvaluation
to show final forces on the non-constrained, interior spheres var draw_active_interface
-
Boolean, indicates whether currently active
neighbor_relation
s should be shown graphically. Used by the spheres (CircleMassNeighbors
and derived) var draw_boundary_forces
-
Boolean, used by
StressTensorEvaluation
to show final forces applied on the non-constrained spheres partially in contact with the constrained boundary spheres var draw_permanent_connections
-
Boolean, indicates to the spherees (
SphereLinkable
and derived) whether permanent connections should be drawn var draw_permanent_interfaces
-
Boolean, indicates to the spherees (
SphereLinkable
and derived) whether the interfaces active for the permanent connections should be drawn var draw_rotation_line
-
Boolean, indicates whehter the spheres to display rotation status (
SphereFriction
and derived) var draw_speeds
-
Boolean, indicates whether spheres should draw their speed as vectors
var draw_spheres_as
-
Display of the spheres. Can be "spheres", "dots" or "none", setting used by the spheres (Class
Circle
and derived) var draw_volume_forces
-
Boolean, used by
StressTensorEvaluation
to show final forces on the non-constrained, totally interior spheres var force_scale
-
To graphically scale the forces to have reasonable length
class OscillatoryShearExperiment (theEnsemble, dt, f, amplitude, output_file, dt_max=0, TkSimulation=False, force_scale=1, theTkOutput=False, imageOutputFolder=False, imageBaseFileName=False, imageFileType='jpg', plotStress=True, saveStressTensorData=True)
-
Expand source code
class OscillatoryShearExperiment(): # The plotStress variable serves as a switch to enable/disable output plotting, while leaving everything else the same def __init__(self, theEnsemble,dt,f,amplitude,output_file,dt_max=0,TkSimulation=False,force_scale=1,theTkOutput=False, imageOutputFolder=False,imageBaseFileName=False,imageFileType = "jpg",plotStress=True, saveStressTensorData=True): self.plotStress=plotStress print("OscillatoryShearExperiment: Plot stress graph:",self.plotStress) self.theEnsemble=theEnsemble self.amplitude=amplitude self.dt = dt self.f = f self.theTk = theTkOutput # The idea here is to be able to handle an external Tk so that the rest of the program can do something with the # graphical output self.external_output_Tk=False if self.theTk: self.external_output_Tk = True self.width=1000 self.height=500 self.outputCanvas = False self.output_file = False self.dt_max = 10*dt self.theTkSimulation=TkSimulation if dt_max>dt: self.dt_max = dt_max self.plotter = False self.theEvaluator = False self.baseline_pre_periods = 0 # Acquisition of stress data with 0 # excitation amplitude to have a baseline before oscillatory shear self.baseline_post_periods = 0 # Acquisition of stress data with 0 # excitation amplitude to have a baseline before oscillatory shear self.periods=2 self.force_scale=force_scale self.output_file=output_file self.final_cool_factor=1 self.do_preequilibration_with_fixed_boundaries=True self.imageOutputFolder=imageOutputFolder self.imageBaseFileName = imageBaseFileName self.imageFileType = imageFileType # can be either jpg or ps self.saveStressTensorData=saveStressTensorData def free_pre_equilibration(self): print("Free pre-equilibration, dt_max",self.dt_max, "dt = ",self.dt) self.theEnsemble.applyingShear = False dt=self.dt_max N = 10 cool_factor=0.5 if self.theEnsemble.mu > 0: N=int(20 * (2 - math.log10(self.theEnsemble.mu))) while dt>self.dt: print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=cool_factor) dt=dt/2 cool_factor=(1+cool_factor)/2 self.theEnsemble.correct_linear_drift() dt = self.dt print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1) def pre_equilibrate(self, N=25,cool_factor=0.99): print("Pre-equilibration under experimental conditions, Steps per dt:",N," max dt",self.dt_max," final dt",self.dt) self.theEnsemble.applyingShear = True dt = self.dt_max cf=0.5 while dt > self.dt: print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=int(N/2), cool_factor=cf) dt = dt / 2 cf = (1 + cf) / 2 dt = self.dt print(" Setting final dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N*2, cool_factor=0.98) for i in range(N): self.theEnsemble.shear_rate = 0 self.theEnsemble.mechanical_simulation_step(cool_factor, self.dt) self.theEnsemble.reset_force() if self.theEnsemble.doDrawing: self.theTkSimulation.update() def oscillatory_shear_experiment(self, cool_factor=1,N_pre_equilibration_fixed_boundaries=25): self.theEvaluator = StressTensorEvaluation(self.theEnsemble.size_x, self.theEnsemble.size_y, self.theEnsemble.theCanvas) self.final_cool_factor = cool_factor self.write_experiment_description_information_to_file() if(self.do_preequilibration_with_fixed_boundaries): self.pre_equilibrate(cool_factor=cool_factor,N=N_pre_equilibration_fixed_boundaries) self.theEnsemble.t=0 self.theEnsemble.applyingShear = True if self.plotStress and not self.theTk: self.theTk=Tk() if self.plotStress: self.outputCanvas = Canvas(self.theTk, width=self.width, height=self.height) self.outputCanvas.pack() self.theEnsemble.t=0 Time_required = (self.periods+self.baseline_pre_periods+self.baseline_post_periods) / self.f x_scale = self.width/Time_required self.plotter=EvaluationHandlerPlotter(self.theTk, self.outputCanvas, x_scale=x_scale, y_scale_force=self.force_scale, y_scale_strain=30 / self.amplitude, r=2, offset_x=0, offset_y=self.height / 2,plotStress=self.plotStress) N=int(Time_required/self.dt) print("Start application shear protocol, total N=",N," steps") # plan to output 200 images in total image_N=1 if N>200: image_N=int(N/200) self.theEnsemble.t=0 imageCounter=image_N if self.imageOutputFolder: print("Saving image every ",image_N," steps") for i in range(N): if i>=(self.baseline_pre_periods/self.f/self.dt) and i<=((self.periods+self.baseline_pre_periods)/self.f/self.dt): self.theEnsemble.setShearRate( self.amplitude * self.out_of_phase_signal(self.theEnsemble.t)* self.f * 2 * math.pi) else: self.theEnsemble.setShearRate(0) # Calculate forces, this registers individual forces in the force register self.theEnsemble.mechanical_simulation_step_calculate_forces() # report net particles forces and moments in the force register self.theEnsemble.record_total_particle_forces() self.plotter.record(self.theEnsemble,self.theEvaluator) # Do linear and rotational acceleration self.theEnsemble.mechanical_simulation_step_calculate_acceleration(cool_factor=cool_factor, dt=self.dt) # Do linear and rotation movememnt self.theEnsemble.mechanical_simulation_step_calculate_movement(dt=self.dt) # reset forces for next round self.theEnsemble.reset_force() if self.theEnsemble.doDrawing: self.theTkSimulation.update() if imageCounter>=image_N: self.save_canvas_image(i) imageCounter=0 imageCounter=imageCounter+1 print("overall", self.plotter.overall_stress_tensor[len(self.plotter.shear_stress) - 1]) print("overall", self.plotter.overall_stress_tensor[0]) G=self.write_output_information_to_file() if not self.external_output_Tk: if self.theTk: self.theTk.destroy() self.theTk = False self.outputCanvas = False return(G) def save_canvas_image(self,index): if not self.imageOutputFolder or not self.theEnsemble.doDrawing or not self.theEnsemble.theCanvas: return filename=self.imageOutputFolder+"/"+self.imageBaseFileName if index < 1e6: filename=filename+"0" if index < 1e5: filename=filename+"0" if index < 1e4: filename=filename+"0" if index < 1e3: filename=filename+"0" if index < 1e2: filename = filename + "0" if index < 1e1: filename = filename + "0" file = filename + str(index) if self.imageFileType=="jpg": file=file+ ".jpg" else: file=file+".ps" if self.imageFileType=="jpg": ps=self.theEnsemble.theCanvas.postscript(colormode="color") img = Image.open(io.BytesIO(ps.encode('utf-8'))) img.save(file) else: self.theEnsemble.theCanvas.postscript(file=file,colormode="color") def in_phase_signal(self,t): return math.sin((t * self.f - self.baseline_pre_periods) * math.pi * 2) def out_of_phase_signal(self,t): return math.cos((t * self.f - self.baseline_pre_periods) * math.pi * 2) # Returns a vector of four elements: the demodulated stress (in phase), the demodulated stress (90 degress), # G' and G'' def demodulation(self,t,shear_stress): t = t in_phase = [] out_of_phase = [] sum_in_phase = 0 sum_out_of_phase = 0 stress_in_phase = 0 stress_out_of_phase = 0 stress_in_phase_linear_drift_corrected = 0 stress_out_of_phase_linear_drift_corrected = 0 # If we have more than one period available, do not use the first one for evaluation as there are transitory # phenomena at the beginning equilibration_periods = 0 if self.periods >= 2: equilibration_periods = 1 lastIndex = len(shear_stress) - 1 lastStressForDelta = shear_stress[lastIndex] firstStressForDelta = shear_stress[0] firstTForDelta = t[0] lastTForDelta = t[lastIndex] if self.baseline_pre_periods > 0: sum_stress = 0 sum_t = 0 N = 0 for i in range(len(t)): if t[i] < (self.baseline_pre_periods / self.f) and t[i] >= (2 * self.baseline_pre_periods / self.f / 3): N = N + 1 sum_stress = sum_stress + shear_stress[i] sum_t = sum_t + t[i] firstStressForDelta = sum_stress / N firstTForDelta = sum_t / N if self.baseline_post_periods > 0: sum_stress = 0 sum_t = 0 N = 0 for i in range(len(t)): if t[i] > (self.baseline_pre_periods + self.periods) / self.f and t[i] <= \ (self.baseline_pre_periods + self.periods + self.baseline_post_periods / 3) / self.f: N = N + 1 sum_stress = sum_stress + shear_stress[i] sum_t = sum_t + t[i] lastStressForDelta = sum_stress / N lastTForDelta = sum_t / N linear_drift_coefficient = (lastStressForDelta - firstStressForDelta) / (firstTForDelta - lastTForDelta) for i in range(len(t)): ip = 0 op = 0 if t[i] >= ((self.baseline_pre_periods + equilibration_periods) / self.f) and t[i] <= ( (self.baseline_pre_periods + self.periods) / self.f): ip = self.in_phase_signal(t[i]) op = self.out_of_phase_signal(t[i]) in_phase.append(ip) out_of_phase.append(op) sum_in_phase = sum_in_phase + ip * ip sum_out_of_phase = sum_out_of_phase + op * op stress_linear_drift_corrected = shear_stress[i] - ( t[i] - self.baseline_pre_periods / self.f) * linear_drift_coefficient stress_in_phase = stress_in_phase + shear_stress[i] * ip stress_out_of_phase = stress_out_of_phase + shear_stress[i] * op stress_in_phase_linear_drift_corrected = stress_in_phase_linear_drift_corrected + stress_linear_drift_corrected * ip stress_out_of_phase_linear_drift_corrected = stress_out_of_phase_linear_drift_corrected + stress_linear_drift_corrected * op return [stress_in_phase_linear_drift_corrected / sum_in_phase, stress_out_of_phase_linear_drift_corrected / sum_out_of_phase, stress_in_phase_linear_drift_corrected / sum_in_phase / self.amplitude, stress_out_of_phase_linear_drift_corrected / sum_out_of_phase / self.amplitude] def evaluateStressAndG(self,useExternalForce=False): if not useExternalForce: return self.demodulation(self.plotter.t,self.plotter.shear_stress) else: return self.demodulation(self.plotter.t, self.plotter.force) def write_experiment_description_information_to_file(self): if not self.output_file: print("Not saving experiment description to file ( no data file configured )") return file=self.output_file print("Saving simulation description to output file") file.write("Oscillatory shear experiment\n\n") if hasattr(self.theEnsemble,"model"): file.write("Model parameters\n") file.write("Width of simulation area size_x =") file.write(str(self.theEnsemble.model.size_x)) file.write("micrometers\n") file.write("Height of simulation area size_y =") file.write(str(self.theEnsemble.model.size_y)) file.write("micrometers\n") file.write("Nominal packing fraction = ") file.write(str(self.theEnsemble.model.packing_fraction)) file.write("\n") file.write("Young modulus spheres = ") file.write(str(self.theEnsemble.model.Young_modulus_spheres)) file.write("Pa\n") file.write("Relative density spheres = ") file.write(str(self.theEnsemble.model.density)) file.write("kg/m^3\n") file.write("Characteristic time constant = ") file.write(str(self.theEnsemble.model.time_constant)) file.write("s\n") file.write("Characteristic spring constant = ") file.write(str(self.theEnsemble.model.k)) file.write("mg/s^2\n") file.write("Characteristic viscosity = ") file.write(str(self.theEnsemble.model.k*self.theEnsemble.model.time_constant)) file.write("mg/s\n\n") file.write("Ensemble parameters\n") file.write("Particle mass m = ") file.write(str(self.theEnsemble.m)) file.write("mg\n") file.write("Central spring constant k = ") file.write(str(self.theEnsemble.k)) file.write("mg*s^(-2)\n") file.write("Central viscosity constant nu = ") file.write(str(self.theEnsemble.nu)) file.write("mg*s^(-1)\n") file.write("Lateral spring constant k_t = ") file.write(str(self.theEnsemble.k_t)) file.write("mg*s^(-2)\n") file.write("Lateral viscosity constant nu_t = ") file.write(str(self.theEnsemble.nu_t)) file.write("mg*s^(-1)\n") file.write("Friction coefficient mu = ") file.write(str(self.theEnsemble.mu)) file.write("\n") file.write("Sample width = ") file.write(str(self.theEnsemble.size_x)) file.write("micrometers\n") file.write("Sample height = Gap height = ") file.write(str(self.theEnsemble.size_y)) file.write("micrometers\n") file.write("Particle number = ") file.write(str(self.theEnsemble.N)) file.write("\n") file.write("Particle information\n\t") file.write(str(self.theEnsemble.particle_info())) file.write("\n") if hasattr(self.theEnsemble,"k_corner_torque"): file.write("Angular torque spring constant, per m of depth: k_corner_torque = ") file.write(str(self.theEnsemble.k_corner_torque)) file.write("mg/m*micrometers^2/s^-2\n") if hasattr(self.theEnsemble, "nu_corner_torque"): file.write("Angular torque viscosity constant, per m of depth: nu_corner_torque = ") file.write(str(self.theEnsemble.nu_corner_torque)) file.write("mg/m*micrometers^2/s^-1\n") if hasattr(self.theEnsemble,"central_repulsion_coefficient"): file.write("Nonlinear enhancement for repulsion near sphere center = ") file.write(str(self.theEnsemble.central_repulsion_coefficient)) file.write("[-], 0=no enhancement, 1=maximum enhancement; see CircleBasicElasticity.get_elastic_force\n") if hasattr(self.theEnsemble,"permanent_ratio_central"): file.write("Adjustment coefficient for central force in permanent links = ") file.write(str(self.theEnsemble.permanent_ratio_central)) file.write("[-], 0=no central force, 1=as in free interfaces; >1 re-enforcement of permanent over transient links\n") file.write("\tk_permanent=") file.write(str(self.theEnsemble.k * self.theEnsemble.permanent_ratio_central)) file.write("mg*s^(-2)\n") if hasattr(self.theEnsemble,"permanent_ratio_tangential"): file.write("Adjustment coefficient for tangential force in permanent links = ") file.write(str(self.theEnsemble.permanent_ratio_tangential)) file.write("[-], 0=no tangential force, 1=as in free interfaces; >1 re-enforcement of permanent over transient links\n") file.write("\tk_t_permanent=") file.write(str(self.theEnsemble.k_t * self.theEnsemble.permanent_ratio_tangential)) file.write("mg*s^(-2)\n") if hasattr(self.theEnsemble,"keep_viscosity_coefficients_constant") and hasattr(self.theEnsemble,"permanent_ratio_tangential") \ and hasattr(self.theEnsemble,"permanent_ratio_central"): if self.theEnsemble.keep_viscosity_coefficients_constant: file.write("Viscosity adjustment for permanent links:\n" "\tNo adjustment: Impose constant viscosity regardless of the permanent link adjustment ") file.write("\n\tnu_permanent="+str(self.theEnsemble.nu)) file.write("\n\tnu_t_permanent=" + str(self.theEnsemble.nu_t)) else: file.write("Viscosity adjustment for permanent links:\n" "\tAdjustment: Adjust viscosity (central and tangential) by same factor "+ "as the respective spring constant (k and k_t)") file.write("\n\tnu_permanent=" + str(self.theEnsemble.nu*self.theEnsemble.permanent_ratio_central)) file.write("\n\tnu_t_permanent=" + str(self.theEnsemble.nu_t*self.theEnsemble.permanent_ratio_tangential)) if not hasattr(self.theEnsemble,"avoid_height_spanning_particles"): file.write("\navoid_height_spanning_particles: Full height spanning particles allowed") else: if not self.theEnsemble.avoid_height_spanning_particles: file.write("\navoid_height_spanning_particles: Full height spanning particles allowed") else: file.write("\navoid_height_spanning_particles: Ensembles with full height spanning particles discarded") if hasattr(self.theEnsemble, "removed_fraction"): file.write("\nActual fraction of randomly removed non-essential permanent bonds = ") file.write(str(self.theEnsemble.removed_fraction)) file.write( "[-]\n") if hasattr(self.theEnsemble, "edge_fuzziness"): file.write("\nEdge fuzziness for creating interlocking = ") file.write(str(self.theEnsemble.edge_fuzziness)) file.write( "[-]\n") file.write("\n\nRheology experiment information\n") file.write("Simulated time = ") file.write(str(self.periods / self.f)) file.write("s\n") file.write("Time step = ") file.write(str(self.dt)) file.write("s\n") file.write("Frequency = ") file.write(str(self.f)) file.write("Hz\n") file.write("Strain amplitude = ") file.write(str(self.amplitude*100)) file.write("%\n") file.write("Damping factor = ") file.write(str(self.final_cool_factor)) file.write(" [1 = no damping]\n") file.write("Pre-measurement periods = ") file.write(str(self.baseline_pre_periods)) file.write(" [-]\n") file.write("Oscillatory shear periods = ") file.write(str(self.periods)) file.write(" [-]\n") file.write("Post-measurement periods = ") file.write(str(self.baseline_pre_periods)) file.write(" [-]\n") if hasattr(self.theEnsemble,"shear_rate_to_rotation_rate_coefficient"): file.write("Sllod equations: Anticipated adjustment of rotation for shear, with coefficient") file.write(str(self.theEnsemble.shear_rate_to_rotation_rate_coefficient)) file.write(" [-]\n") def write_output_information_to_file(self): G=self.evaluateStressAndG(useExternalForce=False) G_external=self.evaluateStressAndG(useExternalForce=True) print("G'=") print(str(G[2])) print("Pa\n") print("G''=") print(str(G[3])) print("Pa\n") print("G'(by surface force)=") print(str(G_external[2])) print("Pa\n") print("G''(by surface force)=") print(str(G_external[3])) print("Pa\n") if not self.output_file: print("Not saving output to file (no data file configured)") return [G_external[2],G_external[3]] print("Saving simulation data to output file") file=self.output_file file.write("\n\nOutput data\nSummary data\n") file.write("In phase stress = ") file.write(str(G[0])) file.write("Pa\n") file.write("Out of phase stress = ") file.write(str(G[1])) file.write("Pa\n") file.write("Stress = ") file.write(str(math.sqrt(G[0]*G[0]+G[1]*G[1]))) file.write("Pa\n") file.write("G'=") file.write(str(G[2])) file.write("Pa\n") file.write("G''=") file.write(str(G[3])) file.write("Pa\n") file.write("G'(by surface force)=") file.write(str(G_external[2])) file.write("Pa\n") file.write("G''(by surface force)=") file.write(str(G_external[3])) file.write("Pa\n") file.write("\n\nDetailed stress tensor data\n") if(not self.saveStressTensorData): print("Saving summary data only to file") file.write("\tNot saving detailed stress tensor data. If you wish to save this data, pass \n"+ "\t saveStressTensorData=True") else: print("Saving stress tensor data to file") file.write("index\tt\tstrain\tstrain_rate\tstress_measured_at_surface\tshear_stress_internal_stress-tensor") file.write("\tstress_tensor_Love_Weber_00") file.write("\tstress_tensor_Love_Weber_01") file.write("\tstress_tensor_Love_Weber_10") file.write("\tstress_tensor_Love_Weber_11") file.write("\tstress_tensor_peculiar_acceleration_otsuki_00") file.write("\tstress_tensor_peculiar_acceleration_otsuki_01") file.write("\tstress_tensor_peculiar_acceleration_otsuki_10") file.write("\tstress_tensor_peculiar_acceleration_otsuki_11") file.write("\tstress_tensor_from_external_forces_00") file.write("\tstress_tensor_from_external_forces_01") file.write("\tstress_tensor_from_external_forces_10") file.write("\tstress_tensor_from_external_forces_11") file.write("\tstress_tensor_linear_acceleration_00") file.write("\tstress_tensor_linear_acceleration_01") file.write("\tstress_tensor_linear_acceleration_10") file.write("\tstress_tensor_linear_acceleration_11") file.write("\tstress_tensor_unbalanced_linear_forces_00") file.write("\tstress_tensor_unbalanced_linear_forces_01") file.write("\tstress_tensor_unbalanced_linear_forces_10") file.write("\tstress_tensor_unbalanced_linear_forces_11") file.write("\tstress_tensor_tangential_torque_00") file.write("\tstress_tensor_tangential_torque_01") file.write("\tstress_tensor_tangential_torque_10") file.write("\tstress_tensor_tangential_torque_11") file.write("\tstress_tensor_internal_tangential_torque_00") file.write("\tstress_tensor_internal_tangential_torque_01") file.write("\tstress_tensor_internal_tangential_torque_10") file.write("\tstress_tensor_internal_tangential_torque_11") file.write("\tstress_tensor_centrifugal_00") file.write("\tstress_tensor_centrifugal_01") file.write("\tstress_tensor_centrifugal_10") file.write("\tstress_tensor_centrifugal_11") file.write("\tstress_tensor_overall_00") file.write("\tstress_tensor_overall_01") file.write("\tstress_tensor_overall_10") file.write("\tstress_tensor_overall_11") p = self.plotter for ip in range(len(p.t)): file.write("\n" + str(ip)) file.write("\t") file.write(str(p.t[ip])) file.write("\t") file.write(str(p.strain[ip])) file.write("\t") file.write(str(p.strain_rate[ip])) file.write("\t") file.write(str(p.force[ip])) file.write("\t") file.write(str(p.shear_stress[ip])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_LW[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_linear_acceleration_otsuki[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_with_external_forces[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_linear_acceleration[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_unbalanced_forces[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_unbalanced_torque[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_internal_torque[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_spin_kinetic_energy[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.overall_stress_tensor[ip][i][j])) return [G_external[2],G_external[3]]
Methods
def demodulation(self, t, shear_stress)
-
Expand source code
def demodulation(self,t,shear_stress): t = t in_phase = [] out_of_phase = [] sum_in_phase = 0 sum_out_of_phase = 0 stress_in_phase = 0 stress_out_of_phase = 0 stress_in_phase_linear_drift_corrected = 0 stress_out_of_phase_linear_drift_corrected = 0 # If we have more than one period available, do not use the first one for evaluation as there are transitory # phenomena at the beginning equilibration_periods = 0 if self.periods >= 2: equilibration_periods = 1 lastIndex = len(shear_stress) - 1 lastStressForDelta = shear_stress[lastIndex] firstStressForDelta = shear_stress[0] firstTForDelta = t[0] lastTForDelta = t[lastIndex] if self.baseline_pre_periods > 0: sum_stress = 0 sum_t = 0 N = 0 for i in range(len(t)): if t[i] < (self.baseline_pre_periods / self.f) and t[i] >= (2 * self.baseline_pre_periods / self.f / 3): N = N + 1 sum_stress = sum_stress + shear_stress[i] sum_t = sum_t + t[i] firstStressForDelta = sum_stress / N firstTForDelta = sum_t / N if self.baseline_post_periods > 0: sum_stress = 0 sum_t = 0 N = 0 for i in range(len(t)): if t[i] > (self.baseline_pre_periods + self.periods) / self.f and t[i] <= \ (self.baseline_pre_periods + self.periods + self.baseline_post_periods / 3) / self.f: N = N + 1 sum_stress = sum_stress + shear_stress[i] sum_t = sum_t + t[i] lastStressForDelta = sum_stress / N lastTForDelta = sum_t / N linear_drift_coefficient = (lastStressForDelta - firstStressForDelta) / (firstTForDelta - lastTForDelta) for i in range(len(t)): ip = 0 op = 0 if t[i] >= ((self.baseline_pre_periods + equilibration_periods) / self.f) and t[i] <= ( (self.baseline_pre_periods + self.periods) / self.f): ip = self.in_phase_signal(t[i]) op = self.out_of_phase_signal(t[i]) in_phase.append(ip) out_of_phase.append(op) sum_in_phase = sum_in_phase + ip * ip sum_out_of_phase = sum_out_of_phase + op * op stress_linear_drift_corrected = shear_stress[i] - ( t[i] - self.baseline_pre_periods / self.f) * linear_drift_coefficient stress_in_phase = stress_in_phase + shear_stress[i] * ip stress_out_of_phase = stress_out_of_phase + shear_stress[i] * op stress_in_phase_linear_drift_corrected = stress_in_phase_linear_drift_corrected + stress_linear_drift_corrected * ip stress_out_of_phase_linear_drift_corrected = stress_out_of_phase_linear_drift_corrected + stress_linear_drift_corrected * op return [stress_in_phase_linear_drift_corrected / sum_in_phase, stress_out_of_phase_linear_drift_corrected / sum_out_of_phase, stress_in_phase_linear_drift_corrected / sum_in_phase / self.amplitude, stress_out_of_phase_linear_drift_corrected / sum_out_of_phase / self.amplitude]
def evaluateStressAndG(self, useExternalForce=False)
-
Expand source code
def evaluateStressAndG(self,useExternalForce=False): if not useExternalForce: return self.demodulation(self.plotter.t,self.plotter.shear_stress) else: return self.demodulation(self.plotter.t, self.plotter.force)
def free_pre_equilibration(self)
-
Expand source code
def free_pre_equilibration(self): print("Free pre-equilibration, dt_max",self.dt_max, "dt = ",self.dt) self.theEnsemble.applyingShear = False dt=self.dt_max N = 10 cool_factor=0.5 if self.theEnsemble.mu > 0: N=int(20 * (2 - math.log10(self.theEnsemble.mu))) while dt>self.dt: print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=cool_factor) dt=dt/2 cool_factor=(1+cool_factor)/2 self.theEnsemble.correct_linear_drift() dt = self.dt print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N, cool_factor=1)
def in_phase_signal(self, t)
-
Expand source code
def in_phase_signal(self,t): return math.sin((t * self.f - self.baseline_pre_periods) * math.pi * 2)
def oscillatory_shear_experiment(self, cool_factor=1, N_pre_equilibration_fixed_boundaries=25)
-
Expand source code
def oscillatory_shear_experiment(self, cool_factor=1,N_pre_equilibration_fixed_boundaries=25): self.theEvaluator = StressTensorEvaluation(self.theEnsemble.size_x, self.theEnsemble.size_y, self.theEnsemble.theCanvas) self.final_cool_factor = cool_factor self.write_experiment_description_information_to_file() if(self.do_preequilibration_with_fixed_boundaries): self.pre_equilibrate(cool_factor=cool_factor,N=N_pre_equilibration_fixed_boundaries) self.theEnsemble.t=0 self.theEnsemble.applyingShear = True if self.plotStress and not self.theTk: self.theTk=Tk() if self.plotStress: self.outputCanvas = Canvas(self.theTk, width=self.width, height=self.height) self.outputCanvas.pack() self.theEnsemble.t=0 Time_required = (self.periods+self.baseline_pre_periods+self.baseline_post_periods) / self.f x_scale = self.width/Time_required self.plotter=EvaluationHandlerPlotter(self.theTk, self.outputCanvas, x_scale=x_scale, y_scale_force=self.force_scale, y_scale_strain=30 / self.amplitude, r=2, offset_x=0, offset_y=self.height / 2,plotStress=self.plotStress) N=int(Time_required/self.dt) print("Start application shear protocol, total N=",N," steps") # plan to output 200 images in total image_N=1 if N>200: image_N=int(N/200) self.theEnsemble.t=0 imageCounter=image_N if self.imageOutputFolder: print("Saving image every ",image_N," steps") for i in range(N): if i>=(self.baseline_pre_periods/self.f/self.dt) and i<=((self.periods+self.baseline_pre_periods)/self.f/self.dt): self.theEnsemble.setShearRate( self.amplitude * self.out_of_phase_signal(self.theEnsemble.t)* self.f * 2 * math.pi) else: self.theEnsemble.setShearRate(0) # Calculate forces, this registers individual forces in the force register self.theEnsemble.mechanical_simulation_step_calculate_forces() # report net particles forces and moments in the force register self.theEnsemble.record_total_particle_forces() self.plotter.record(self.theEnsemble,self.theEvaluator) # Do linear and rotational acceleration self.theEnsemble.mechanical_simulation_step_calculate_acceleration(cool_factor=cool_factor, dt=self.dt) # Do linear and rotation movememnt self.theEnsemble.mechanical_simulation_step_calculate_movement(dt=self.dt) # reset forces for next round self.theEnsemble.reset_force() if self.theEnsemble.doDrawing: self.theTkSimulation.update() if imageCounter>=image_N: self.save_canvas_image(i) imageCounter=0 imageCounter=imageCounter+1 print("overall", self.plotter.overall_stress_tensor[len(self.plotter.shear_stress) - 1]) print("overall", self.plotter.overall_stress_tensor[0]) G=self.write_output_information_to_file() if not self.external_output_Tk: if self.theTk: self.theTk.destroy() self.theTk = False self.outputCanvas = False return(G)
def out_of_phase_signal(self, t)
-
Expand source code
def out_of_phase_signal(self,t): return math.cos((t * self.f - self.baseline_pre_periods) * math.pi * 2)
def pre_equilibrate(self, N=25, cool_factor=0.99)
-
Expand source code
def pre_equilibrate(self, N=25,cool_factor=0.99): print("Pre-equilibration under experimental conditions, Steps per dt:",N," max dt",self.dt_max," final dt",self.dt) self.theEnsemble.applyingShear = True dt = self.dt_max cf=0.5 while dt > self.dt: print("dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=int(N/2), cool_factor=cf) dt = dt / 2 cf = (1 + cf) / 2 dt = self.dt print(" Setting final dt=", dt) self.theEnsemble.mechanical_relaxation(dt=dt, theTk=self.theTkSimulation, N=N*2, cool_factor=0.98) for i in range(N): self.theEnsemble.shear_rate = 0 self.theEnsemble.mechanical_simulation_step(cool_factor, self.dt) self.theEnsemble.reset_force() if self.theEnsemble.doDrawing: self.theTkSimulation.update()
def save_canvas_image(self, index)
-
Expand source code
def save_canvas_image(self,index): if not self.imageOutputFolder or not self.theEnsemble.doDrawing or not self.theEnsemble.theCanvas: return filename=self.imageOutputFolder+"/"+self.imageBaseFileName if index < 1e6: filename=filename+"0" if index < 1e5: filename=filename+"0" if index < 1e4: filename=filename+"0" if index < 1e3: filename=filename+"0" if index < 1e2: filename = filename + "0" if index < 1e1: filename = filename + "0" file = filename + str(index) if self.imageFileType=="jpg": file=file+ ".jpg" else: file=file+".ps" if self.imageFileType=="jpg": ps=self.theEnsemble.theCanvas.postscript(colormode="color") img = Image.open(io.BytesIO(ps.encode('utf-8'))) img.save(file) else: self.theEnsemble.theCanvas.postscript(file=file,colormode="color")
def write_experiment_description_information_to_file(self)
-
Expand source code
def write_experiment_description_information_to_file(self): if not self.output_file: print("Not saving experiment description to file ( no data file configured )") return file=self.output_file print("Saving simulation description to output file") file.write("Oscillatory shear experiment\n\n") if hasattr(self.theEnsemble,"model"): file.write("Model parameters\n") file.write("Width of simulation area size_x =") file.write(str(self.theEnsemble.model.size_x)) file.write("micrometers\n") file.write("Height of simulation area size_y =") file.write(str(self.theEnsemble.model.size_y)) file.write("micrometers\n") file.write("Nominal packing fraction = ") file.write(str(self.theEnsemble.model.packing_fraction)) file.write("\n") file.write("Young modulus spheres = ") file.write(str(self.theEnsemble.model.Young_modulus_spheres)) file.write("Pa\n") file.write("Relative density spheres = ") file.write(str(self.theEnsemble.model.density)) file.write("kg/m^3\n") file.write("Characteristic time constant = ") file.write(str(self.theEnsemble.model.time_constant)) file.write("s\n") file.write("Characteristic spring constant = ") file.write(str(self.theEnsemble.model.k)) file.write("mg/s^2\n") file.write("Characteristic viscosity = ") file.write(str(self.theEnsemble.model.k*self.theEnsemble.model.time_constant)) file.write("mg/s\n\n") file.write("Ensemble parameters\n") file.write("Particle mass m = ") file.write(str(self.theEnsemble.m)) file.write("mg\n") file.write("Central spring constant k = ") file.write(str(self.theEnsemble.k)) file.write("mg*s^(-2)\n") file.write("Central viscosity constant nu = ") file.write(str(self.theEnsemble.nu)) file.write("mg*s^(-1)\n") file.write("Lateral spring constant k_t = ") file.write(str(self.theEnsemble.k_t)) file.write("mg*s^(-2)\n") file.write("Lateral viscosity constant nu_t = ") file.write(str(self.theEnsemble.nu_t)) file.write("mg*s^(-1)\n") file.write("Friction coefficient mu = ") file.write(str(self.theEnsemble.mu)) file.write("\n") file.write("Sample width = ") file.write(str(self.theEnsemble.size_x)) file.write("micrometers\n") file.write("Sample height = Gap height = ") file.write(str(self.theEnsemble.size_y)) file.write("micrometers\n") file.write("Particle number = ") file.write(str(self.theEnsemble.N)) file.write("\n") file.write("Particle information\n\t") file.write(str(self.theEnsemble.particle_info())) file.write("\n") if hasattr(self.theEnsemble,"k_corner_torque"): file.write("Angular torque spring constant, per m of depth: k_corner_torque = ") file.write(str(self.theEnsemble.k_corner_torque)) file.write("mg/m*micrometers^2/s^-2\n") if hasattr(self.theEnsemble, "nu_corner_torque"): file.write("Angular torque viscosity constant, per m of depth: nu_corner_torque = ") file.write(str(self.theEnsemble.nu_corner_torque)) file.write("mg/m*micrometers^2/s^-1\n") if hasattr(self.theEnsemble,"central_repulsion_coefficient"): file.write("Nonlinear enhancement for repulsion near sphere center = ") file.write(str(self.theEnsemble.central_repulsion_coefficient)) file.write("[-], 0=no enhancement, 1=maximum enhancement; see CircleBasicElasticity.get_elastic_force\n") if hasattr(self.theEnsemble,"permanent_ratio_central"): file.write("Adjustment coefficient for central force in permanent links = ") file.write(str(self.theEnsemble.permanent_ratio_central)) file.write("[-], 0=no central force, 1=as in free interfaces; >1 re-enforcement of permanent over transient links\n") file.write("\tk_permanent=") file.write(str(self.theEnsemble.k * self.theEnsemble.permanent_ratio_central)) file.write("mg*s^(-2)\n") if hasattr(self.theEnsemble,"permanent_ratio_tangential"): file.write("Adjustment coefficient for tangential force in permanent links = ") file.write(str(self.theEnsemble.permanent_ratio_tangential)) file.write("[-], 0=no tangential force, 1=as in free interfaces; >1 re-enforcement of permanent over transient links\n") file.write("\tk_t_permanent=") file.write(str(self.theEnsemble.k_t * self.theEnsemble.permanent_ratio_tangential)) file.write("mg*s^(-2)\n") if hasattr(self.theEnsemble,"keep_viscosity_coefficients_constant") and hasattr(self.theEnsemble,"permanent_ratio_tangential") \ and hasattr(self.theEnsemble,"permanent_ratio_central"): if self.theEnsemble.keep_viscosity_coefficients_constant: file.write("Viscosity adjustment for permanent links:\n" "\tNo adjustment: Impose constant viscosity regardless of the permanent link adjustment ") file.write("\n\tnu_permanent="+str(self.theEnsemble.nu)) file.write("\n\tnu_t_permanent=" + str(self.theEnsemble.nu_t)) else: file.write("Viscosity adjustment for permanent links:\n" "\tAdjustment: Adjust viscosity (central and tangential) by same factor "+ "as the respective spring constant (k and k_t)") file.write("\n\tnu_permanent=" + str(self.theEnsemble.nu*self.theEnsemble.permanent_ratio_central)) file.write("\n\tnu_t_permanent=" + str(self.theEnsemble.nu_t*self.theEnsemble.permanent_ratio_tangential)) if not hasattr(self.theEnsemble,"avoid_height_spanning_particles"): file.write("\navoid_height_spanning_particles: Full height spanning particles allowed") else: if not self.theEnsemble.avoid_height_spanning_particles: file.write("\navoid_height_spanning_particles: Full height spanning particles allowed") else: file.write("\navoid_height_spanning_particles: Ensembles with full height spanning particles discarded") if hasattr(self.theEnsemble, "removed_fraction"): file.write("\nActual fraction of randomly removed non-essential permanent bonds = ") file.write(str(self.theEnsemble.removed_fraction)) file.write( "[-]\n") if hasattr(self.theEnsemble, "edge_fuzziness"): file.write("\nEdge fuzziness for creating interlocking = ") file.write(str(self.theEnsemble.edge_fuzziness)) file.write( "[-]\n") file.write("\n\nRheology experiment information\n") file.write("Simulated time = ") file.write(str(self.periods / self.f)) file.write("s\n") file.write("Time step = ") file.write(str(self.dt)) file.write("s\n") file.write("Frequency = ") file.write(str(self.f)) file.write("Hz\n") file.write("Strain amplitude = ") file.write(str(self.amplitude*100)) file.write("%\n") file.write("Damping factor = ") file.write(str(self.final_cool_factor)) file.write(" [1 = no damping]\n") file.write("Pre-measurement periods = ") file.write(str(self.baseline_pre_periods)) file.write(" [-]\n") file.write("Oscillatory shear periods = ") file.write(str(self.periods)) file.write(" [-]\n") file.write("Post-measurement periods = ") file.write(str(self.baseline_pre_periods)) file.write(" [-]\n") if hasattr(self.theEnsemble,"shear_rate_to_rotation_rate_coefficient"): file.write("Sllod equations: Anticipated adjustment of rotation for shear, with coefficient") file.write(str(self.theEnsemble.shear_rate_to_rotation_rate_coefficient)) file.write(" [-]\n")
def write_output_information_to_file(self)
-
Expand source code
def write_output_information_to_file(self): G=self.evaluateStressAndG(useExternalForce=False) G_external=self.evaluateStressAndG(useExternalForce=True) print("G'=") print(str(G[2])) print("Pa\n") print("G''=") print(str(G[3])) print("Pa\n") print("G'(by surface force)=") print(str(G_external[2])) print("Pa\n") print("G''(by surface force)=") print(str(G_external[3])) print("Pa\n") if not self.output_file: print("Not saving output to file (no data file configured)") return [G_external[2],G_external[3]] print("Saving simulation data to output file") file=self.output_file file.write("\n\nOutput data\nSummary data\n") file.write("In phase stress = ") file.write(str(G[0])) file.write("Pa\n") file.write("Out of phase stress = ") file.write(str(G[1])) file.write("Pa\n") file.write("Stress = ") file.write(str(math.sqrt(G[0]*G[0]+G[1]*G[1]))) file.write("Pa\n") file.write("G'=") file.write(str(G[2])) file.write("Pa\n") file.write("G''=") file.write(str(G[3])) file.write("Pa\n") file.write("G'(by surface force)=") file.write(str(G_external[2])) file.write("Pa\n") file.write("G''(by surface force)=") file.write(str(G_external[3])) file.write("Pa\n") file.write("\n\nDetailed stress tensor data\n") if(not self.saveStressTensorData): print("Saving summary data only to file") file.write("\tNot saving detailed stress tensor data. If you wish to save this data, pass \n"+ "\t saveStressTensorData=True") else: print("Saving stress tensor data to file") file.write("index\tt\tstrain\tstrain_rate\tstress_measured_at_surface\tshear_stress_internal_stress-tensor") file.write("\tstress_tensor_Love_Weber_00") file.write("\tstress_tensor_Love_Weber_01") file.write("\tstress_tensor_Love_Weber_10") file.write("\tstress_tensor_Love_Weber_11") file.write("\tstress_tensor_peculiar_acceleration_otsuki_00") file.write("\tstress_tensor_peculiar_acceleration_otsuki_01") file.write("\tstress_tensor_peculiar_acceleration_otsuki_10") file.write("\tstress_tensor_peculiar_acceleration_otsuki_11") file.write("\tstress_tensor_from_external_forces_00") file.write("\tstress_tensor_from_external_forces_01") file.write("\tstress_tensor_from_external_forces_10") file.write("\tstress_tensor_from_external_forces_11") file.write("\tstress_tensor_linear_acceleration_00") file.write("\tstress_tensor_linear_acceleration_01") file.write("\tstress_tensor_linear_acceleration_10") file.write("\tstress_tensor_linear_acceleration_11") file.write("\tstress_tensor_unbalanced_linear_forces_00") file.write("\tstress_tensor_unbalanced_linear_forces_01") file.write("\tstress_tensor_unbalanced_linear_forces_10") file.write("\tstress_tensor_unbalanced_linear_forces_11") file.write("\tstress_tensor_tangential_torque_00") file.write("\tstress_tensor_tangential_torque_01") file.write("\tstress_tensor_tangential_torque_10") file.write("\tstress_tensor_tangential_torque_11") file.write("\tstress_tensor_internal_tangential_torque_00") file.write("\tstress_tensor_internal_tangential_torque_01") file.write("\tstress_tensor_internal_tangential_torque_10") file.write("\tstress_tensor_internal_tangential_torque_11") file.write("\tstress_tensor_centrifugal_00") file.write("\tstress_tensor_centrifugal_01") file.write("\tstress_tensor_centrifugal_10") file.write("\tstress_tensor_centrifugal_11") file.write("\tstress_tensor_overall_00") file.write("\tstress_tensor_overall_01") file.write("\tstress_tensor_overall_10") file.write("\tstress_tensor_overall_11") p = self.plotter for ip in range(len(p.t)): file.write("\n" + str(ip)) file.write("\t") file.write(str(p.t[ip])) file.write("\t") file.write(str(p.strain[ip])) file.write("\t") file.write(str(p.strain_rate[ip])) file.write("\t") file.write(str(p.force[ip])) file.write("\t") file.write(str(p.shear_stress[ip])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_LW[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_linear_acceleration_otsuki[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_with_external_forces[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_linear_acceleration[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_unbalanced_forces[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_unbalanced_torque[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_internal_torque[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.stress_tensor_spin_kinetic_energy[ip][i][j])) for i in range(2): for j in range(2): file.write("\t") file.write(str(p.overall_stress_tensor[ip][i][j])) return [G_external[2],G_external[3]]
class OscillatorySimulation (root_folder, amplitude=0.5, relative_y_scale_force=0.0005, relative_frequency=0.05, doDrawing=True, saveData=False)
-
Expand source code
class OscillatorySimulation(): def __init__(self,root_folder,amplitude=0.5,relative_y_scale_force=5e-4,relative_frequency=0.05,doDrawing=True, saveData=False): self.amplitude=amplitude self.doDrawing=doDrawing self.relative_y_scale_force=relative_y_scale_force self.relative_frequency=relative_frequency self.theTkSimulation=False self.f=-1 # Frequency, -1 before setting self.root_folder=root_folder self.saveData=saveData self.baseline_pre_periods = 0 # Acquisition of stress data with 0 # excitation amplitude to have a baseline before oscillatory shear self.baseline_post_periods = 0 # Acquisition of stress data with 0 # excitation amplitude to have a baseline before oscillatory shear self.imageOutputFolder = False self.imageBaseFileName = False self.saveOutputImages = False self.imageFileType="jpg" self.saveStressTensorData=True """Flag indicating whether we should save the detailed stress tensor data in the ASCII output files Relevant only if saveData==True""" self.function_call = "OscillatorySimulation(root_folder="+str(root_folder)+",amplitude="+str(amplitude)+\ ",relative_y_scale_force="+str(relative_y_scale_force)+",relative_frequency="+str(relative_frequency)+")" self.theExperiment = False # To hold the shear experiment done # Here the ensemble needs to be initiated, but this needs to be done in the subclasses def initEnsemble(self): if not self.theTkSimulation and self.doDrawing: self.theTkSimulation=Tk() def file_path(self): if not self.root_folder: return False fname = "oscillatory_simulation_" fnum=1 while Path(self.root_folder + "/" + fname + str(fnum) + ".txt").is_file(): fnum = fnum + 1 return self.root_folder + "/" + fname + str(fnum) + ".txt" def image_folder_path(self,fileName): folderName=fileName+"_images_folder" self.createFolder(folderName) return folderName def createFolder(self,directory): print("Creating image folder",directory) if not os.path.exists(directory): os.makedirs(directory) print("done") def write_information_on_simulation_to_file(self,theFile): if not self.saveData: return theFile.write("Basic oscillatory shear simulation") # To be implemented by subclasses, in some cases longer pre-equilibration is necessary (low friction coefficient, low amplitudes) # but this depends on the exact simulation to be run def adjustment_factor_for_bounded_preequilibration_steps(self): return 1 def runSimulation(self,cool_factor=1,periods=2,plotStress=True): self.initEnsemble() model = self.theEnsemble.model y_scale_force = self.relative_y_scale_force / model.k / self.amplitude final_dt = self.theEnsemble.dt #if self.amplitude<0.01: # final_dt=final_dt/(1-2*(math.log10(self.amplitude)+2)) # print("Small amplitude: Adjusted to dt=", final_dt) print("Setting relative frequency: "+str(self.relative_frequency)) self.f = self.relative_frequency / model.time_constant fileName=self.file_path() if self.saveOutputImages: self.imageOutputFolder = self.image_folder_path(fileName) self.imageBaseFileName = "image" theFile=False if self.saveData: theFile = open(fileName, "w") self.write_information_on_simulation_to_file(theFile) print("Creating oscillatory shear experiment, dt = ",final_dt," max dt = ",self.theEnsemble.dt_max) theExperiment=OscillatoryShearExperiment(self.theEnsemble,final_dt, self.f,self.amplitude,theFile,force_scale=y_scale_force, TkSimulation=self.theEnsemble.theTkSimulation,dt_max=self.theEnsemble.dt_max, imageOutputFolder=self.imageOutputFolder, imageBaseFileName=self.imageBaseFileName, imageFileType=self.imageFileType, plotStress=plotStress, saveStressTensorData=self.saveStressTensorData) theExperiment.periods=periods theExperiment.baseline_pre_periods=self.baseline_pre_periods theExperiment.baseline_post_periods=self.baseline_post_periods theExperiment.do_preequilibration_with_fixed_boundaries=True N_pre_equilibration_fixed_boundaries=25*self.adjustment_factor_for_bounded_preequilibration_steps() N_per_period = 1/self.f/final_dt cf_per_dt = math.pow(cool_factor,1/N_per_period) # The idea here is that we have always the same attenuation per period print("cool factor per dt=",cf_per_dt) G=theExperiment.oscillatory_shear_experiment( cool_factor=cf_per_dt,N_pre_equilibration_fixed_boundaries=N_pre_equilibration_fixed_boundaries) if self.saveData: theFile.close() if self.theEnsemble.theTkSimulation: self.theEnsemble.theTkSimulation.destroy() self.theTkSimulation=False self.theExperiment=theExperiment return(G)
Subclasses
- particleShearSimulation.OscillatorySimulationFragmentation.OscillatorySimulationFragmentation
Instance variables
var saveStressTensorData
-
Flag indicating whether we should save the detailed stress tensor data in the ASCII output files
Relevant only if saveData==True
Methods
def adjustment_factor_for_bounded_preequilibration_steps(self)
-
Expand source code
def adjustment_factor_for_bounded_preequilibration_steps(self): return 1
def createFolder(self, directory)
-
Expand source code
def createFolder(self,directory): print("Creating image folder",directory) if not os.path.exists(directory): os.makedirs(directory) print("done")
def file_path(self)
-
Expand source code
def file_path(self): if not self.root_folder: return False fname = "oscillatory_simulation_" fnum=1 while Path(self.root_folder + "/" + fname + str(fnum) + ".txt").is_file(): fnum = fnum + 1 return self.root_folder + "/" + fname + str(fnum) + ".txt"
def image_folder_path(self, fileName)
-
Expand source code
def image_folder_path(self,fileName): folderName=fileName+"_images_folder" self.createFolder(folderName) return folderName
def initEnsemble(self)
-
Expand source code
def initEnsemble(self): if not self.theTkSimulation and self.doDrawing: self.theTkSimulation=Tk()
def runSimulation(self, cool_factor=1, periods=2, plotStress=True)
-
Expand source code
def runSimulation(self,cool_factor=1,periods=2,plotStress=True): self.initEnsemble() model = self.theEnsemble.model y_scale_force = self.relative_y_scale_force / model.k / self.amplitude final_dt = self.theEnsemble.dt #if self.amplitude<0.01: # final_dt=final_dt/(1-2*(math.log10(self.amplitude)+2)) # print("Small amplitude: Adjusted to dt=", final_dt) print("Setting relative frequency: "+str(self.relative_frequency)) self.f = self.relative_frequency / model.time_constant fileName=self.file_path() if self.saveOutputImages: self.imageOutputFolder = self.image_folder_path(fileName) self.imageBaseFileName = "image" theFile=False if self.saveData: theFile = open(fileName, "w") self.write_information_on_simulation_to_file(theFile) print("Creating oscillatory shear experiment, dt = ",final_dt," max dt = ",self.theEnsemble.dt_max) theExperiment=OscillatoryShearExperiment(self.theEnsemble,final_dt, self.f,self.amplitude,theFile,force_scale=y_scale_force, TkSimulation=self.theEnsemble.theTkSimulation,dt_max=self.theEnsemble.dt_max, imageOutputFolder=self.imageOutputFolder, imageBaseFileName=self.imageBaseFileName, imageFileType=self.imageFileType, plotStress=plotStress, saveStressTensorData=self.saveStressTensorData) theExperiment.periods=periods theExperiment.baseline_pre_periods=self.baseline_pre_periods theExperiment.baseline_post_periods=self.baseline_post_periods theExperiment.do_preequilibration_with_fixed_boundaries=True N_pre_equilibration_fixed_boundaries=25*self.adjustment_factor_for_bounded_preequilibration_steps() N_per_period = 1/self.f/final_dt cf_per_dt = math.pow(cool_factor,1/N_per_period) # The idea here is that we have always the same attenuation per period print("cool factor per dt=",cf_per_dt) G=theExperiment.oscillatory_shear_experiment( cool_factor=cf_per_dt,N_pre_equilibration_fixed_boundaries=N_pre_equilibration_fixed_boundaries) if self.saveData: theFile.close() if self.theEnsemble.theTkSimulation: self.theEnsemble.theTkSimulation.destroy() self.theTkSimulation=False self.theExperiment=theExperiment return(G)
def write_information_on_simulation_to_file(self, theFile)
-
Expand source code
def write_information_on_simulation_to_file(self,theFile): if not self.saveData: return theFile.write("Basic oscillatory shear simulation")
class OscillatorySimulationFragmentation (root_folder, do_permanent_links=True, cut_lines=5, N=150, packing_fraction=2, amplitude=0.5, Young_modulus_spheres=1000, mu=0.1, relative_y_scale_force=0.0005, relative_frequency=0.05, doDrawing=True, saveData=False)
-
Expand source code
class OscillatorySimulationFragmentation(OscillatorySimulation): def __init__(self,root_folder,do_permanent_links=True,cut_lines=5,N=150,packing_fraction=2,amplitude=0.5, Young_modulus_spheres=1000,mu=0.1, relative_y_scale_force=5e-4,relative_frequency=0.05,doDrawing=True,saveData=False): self.packing_fraction = packing_fraction self.N = N super(OscillatorySimulationFragmentation,self).__init__( root_folder,amplitude=amplitude, relative_y_scale_force=relative_y_scale_force,relative_frequency=relative_frequency,doDrawing=doDrawing, saveData=saveData) self.do_permanent_links=do_permanent_links self.cut_lines=cut_lines self.Young_modulus_spheres=Young_modulus_spheres self.mu=mu self.theEnsemble=False self.model=False self.packing_fraction=packing_fraction self.function_call="OscillatorySimulationFragmentation(root_folder="+str(root_folder)+",do_permanent_links="+\ str(do_permanent_links)+",cut_lines="+str(cut_lines)+",N="+str(N)+\ "packing_fraction="+str(packing_fraction)+",amplitude="+str(amplitude)+\ ",Young_modulus_spheres="+str(Young_modulus_spheres)+",mu="+str(mu)+\ ",relative_y_scale_force="+str(relative_y_scale_force)+",relative_frequency="+str(relative_frequency)+\ ",doDrawing=" + str(doDrawing) + ")" def adjustment_factor_for_bounded_preequilibration_steps(self): if self.mu > 0 and self.mu<=1: return int(2 - math.log10(self.mu)) return 1 # Here the ensemble needs to be initiated, but this needs to be done in the subclasses def initEnsemble(self): if self.doDrawing and not self.theTkSimulation: self.theTkSimulation=Tk() def file_path(self): if not self.root_folder: return False fname = "dermal_filler" if self.do_permanent_links: if not (self.cut_lines > 0): fname = fname + "_bulk" else: fname = fname + "_uncrosslinked" fname = fname + str(self.mu) + "_f_" + str(round(self.f*10000)/10000) + "_strain_" +\ str(round(self.amplitude*100000)/100000) + "_nr_" fnum = 1 while Path(self.root_folder + "/" + fname + str(fnum) + ".txt").is_file(): fnum = fnum + 1 return self.root_folder + "/" + fname + str(fnum) + ".txt" def write_information_on_simulation_to_file(self,theFile): if not self.saveData: return if self.do_permanent_links: if self.cut_lines > 0: theFile.write("Dermal filler Ensemble: \n") theFile.write("\tCutting lines used to generate the particles" + str(self.cut_lines) + "\n") else: theFile.write("Dermal filler control ensemble: no cut lines \n") else: theFile.write("Dermal filler control ensemble: No permanent links\n") theFile.write("\tN=" + str(self.N) + " spheres, nominal packing density = " + str(self.packing_fraction) + "\n") theFile.write("\tYoung modulus of the individual spheres = " + str(self.Young_modulus_spheres) + " Pa \n") theFile.write("\tFriction coefficient = " + str(self.mu) + "\n\n") theFile.write("\n\nFunction call : \n" + self.function_call+"\n\n")
Ancestors
- particleShearSimulation.OscillatorySimulation.OscillatorySimulation
Subclasses
- particleShearSimulation.Simulation_dermal_filler_rheology.Simulation_dermal_filler_rheology
Methods
def adjustment_factor_for_bounded_preequilibration_steps(self)
-
Expand source code
def adjustment_factor_for_bounded_preequilibration_steps(self): if self.mu > 0 and self.mu<=1: return int(2 - math.log10(self.mu)) return 1
def file_path(self)
-
Expand source code
def file_path(self): if not self.root_folder: return False fname = "dermal_filler" if self.do_permanent_links: if not (self.cut_lines > 0): fname = fname + "_bulk" else: fname = fname + "_uncrosslinked" fname = fname + str(self.mu) + "_f_" + str(round(self.f*10000)/10000) + "_strain_" +\ str(round(self.amplitude*100000)/100000) + "_nr_" fnum = 1 while Path(self.root_folder + "/" + fname + str(fnum) + ".txt").is_file(): fnum = fnum + 1 return self.root_folder + "/" + fname + str(fnum) + ".txt"
def initEnsemble(self)
-
Expand source code
def initEnsemble(self): if self.doDrawing and not self.theTkSimulation: self.theTkSimulation=Tk()
def write_information_on_simulation_to_file(self, theFile)
-
Expand source code
def write_information_on_simulation_to_file(self,theFile): if not self.saveData: return if self.do_permanent_links: if self.cut_lines > 0: theFile.write("Dermal filler Ensemble: \n") theFile.write("\tCutting lines used to generate the particles" + str(self.cut_lines) + "\n") else: theFile.write("Dermal filler control ensemble: no cut lines \n") else: theFile.write("Dermal filler control ensemble: No permanent links\n") theFile.write("\tN=" + str(self.N) + " spheres, nominal packing density = " + str(self.packing_fraction) + "\n") theFile.write("\tYoung modulus of the individual spheres = " + str(self.Young_modulus_spheres) + " Pa \n") theFile.write("\tFriction coefficient = " + str(self.mu) + "\n\n") theFile.write("\n\nFunction call : \n" + self.function_call+"\n\n")
class PlateauConfiguration
-
Class to define alternative compressive force laws, not used by default
Expand source code
class PlateauConfiguration(): """ Class to define alternative compressive force laws, not used by default""" relative_plateau_begin = 0.3 relative_plateau_end = 0.8 relative_plateau_slope = 0.1
Class variables
var relative_plateau_begin
var relative_plateau_end
var relative_plateau_slope
class Point (x, y)
-
Define a moving point, with position (x and y) and speed (xpeed and yspeed)
Initialize self
- parameters
x
The x-position of the point (assumed micrometers for the simulation)y
The y-position of the point (assumed micrometers for the simulation)Expand source code
class Point: """Define a moving point, with position (x and y) and speed (xpeed and yspeed)""" def __init__(self, x, y): """Initialize self - **parameters**\n `x` The x-position of the point (assumed micrometers for the simulation)\n `y` The y-position of the point (assumed micrometers for the simulation)\n """ self.x = x """The x-position of the point (assumed micrometers for the simulation)""" self.y = y """The y-position of the point (assumed micrometers for the simulation)""" self.xspeed=0 """The horizontal speed of the point (assumed micrometers/s for the simulation)""" self.yspeed=0 """The vertical speed of the point (assumed micrometers/s for the simulation)""" def coordinates(self): """Return the current coordinates: vector of three elements, which are (x,y,radius=0) This method is defined in class `particleShearBase.Point`""" return [self.x, self.y, 0] def boundary_conditions(self, boundary_x, boundary_y): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n This method is defined in class `particleShearBase.Point`""" old_x = self.x old_y = self.y while self.x >= boundary_x: self.x = self.x - boundary_x while self.x < 0: self.x = self.x + boundary_x while self.y >= boundary_y: self.y = self.y - boundary_y while self.y < 0: self.y = self.y + boundary_y if self.doDrawing: self.theCanvas.move(self.shape, self.x - old_x, self.y - old_y) def d_euclidian(self,theSphere): """Calculate euclidian distance to another object of type `particleShearBase.Point`or derived The calculation is done via the Pythagorean formula: square root of the sum of the squares of the distances in x and y.\n This method is defined in class `particleShearBase.Point`""" pos = theSphere.coordinates() x = pos[0] y = pos[1] return math.sqrt((x - self.x) * (x - self.x) + (y - self.y) * (y - self.y)) def d(self,theSphere): """Calculate the distance to another sphere. For this class, uses `particleShearBase.Point.d_euclidian` but is overriden by specific subclasses.\n This method is defined in class `particleShearBase.Point`""" return self.d_euclidian(theSphere) def n(self, theSphere): """Return unit vector pointing towards another sphere For this `particleShearBase.Point`, returns vector in cartesian coordinates pointing towards the center of the sphere indicated by the parameter theSphere. This behaviour can be overriden directly or indirectly by overriding the definition of `particleShearBase.Point.d`\n This method is defined in class `particleShearBase.Point`""" d = self.d(theSphere) if d > 0: pos = theSphere.coordinates() x = pos[0] y = pos[1] return ([(x - self.x) / d, (y - self.y) / d]) if d == 0: angle = random.random()* 2 * math.pi return [math.cos(angle), math.sin(angle)] def relative_position(self,theSphere): """Return vector indicating the position of theSphere relative to myself For this `particleShearBase.Point`, returns vectorial difference between the position of theSphere and the current object, but this behaviour can be overriden directly, or also indirectly when either the definition of `particleShearBase.Point.d` or `particleShearBase.Point.n` is overriden.\n This method is defined in class `particleShearBase.Point`""" n_vector = self.n(theSphere) d = self.d(theSphere) return [n_vector[0]*d,n_vector[1]*d] def move(self, dt=1): """Move for time interval dt This method is defined in class `particleShearBase.Point`""" self.x = self.x + self.xspeed * dt self.y = self.y + self.yspeed * dt def cool(self, f=0.8): """Cool by decreasing speed This method is defined in class `particleShearBase.Point`""" self.xspeed = self.xspeed * f self.yspeed = self.yspeed * f def relative_speed(self, theSphere): """Relative speed of the two spheres evaluated by the difference of speed of their centers This method is defined in class `particleShearBase.Point`""" return [theSphere.xspeed-self.xspeed, theSphere.yspeed-self.yspeed]
Subclasses
- particleShearBase.PointLeesEdwards.PointLeesEdwards
Instance variables
var x
-
The x-position of the point (assumed micrometers for the simulation)
var xspeed
-
The horizontal speed of the point (assumed micrometers/s for the simulation)
var y
-
The y-position of the point (assumed micrometers for the simulation)
var yspeed
-
The vertical speed of the point (assumed micrometers/s for the simulation)
Methods
def boundary_conditions(self, boundary_x, boundary_y)
-
Ensure location of the point within boundary conditions
x should be >= 0 and < boundary_x
y should be >= 0 and < boundary_y
x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x
y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y
This method is defined in class
particleShearBase.Point
Expand source code
def boundary_conditions(self, boundary_x, boundary_y): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n This method is defined in class `particleShearBase.Point`""" old_x = self.x old_y = self.y while self.x >= boundary_x: self.x = self.x - boundary_x while self.x < 0: self.x = self.x + boundary_x while self.y >= boundary_y: self.y = self.y - boundary_y while self.y < 0: self.y = self.y + boundary_y if self.doDrawing: self.theCanvas.move(self.shape, self.x - old_x, self.y - old_y)
def cool(self, f=0.8)
-
Cool by decreasing speed
This method is defined in class
particleShearBase.Point
Expand source code
def cool(self, f=0.8): """Cool by decreasing speed This method is defined in class `particleShearBase.Point`""" self.xspeed = self.xspeed * f self.yspeed = self.yspeed * f
def coordinates(self)
-
Return the current coordinates: vector of three elements, which are (x,y,radius=0)
This method is defined in class
particleShearBase.Point
Expand source code
def coordinates(self): """Return the current coordinates: vector of three elements, which are (x,y,radius=0) This method is defined in class `particleShearBase.Point`""" return [self.x, self.y, 0]
def d(self, theSphere)
-
Calculate the distance to another sphere.
For this class, uses
particleShearBase.Point.d_euclidian
but is overriden by specific subclasses.This method is defined in class
particleShearBase.Point
Expand source code
def d(self,theSphere): """Calculate the distance to another sphere. For this class, uses `particleShearBase.Point.d_euclidian` but is overriden by specific subclasses.\n This method is defined in class `particleShearBase.Point`""" return self.d_euclidian(theSphere)
def d_euclidian(self, theSphere)
-
Calculate euclidian distance to another object of type
particleShearBase.Point
or derivedThe calculation is done via the Pythagorean formula: square root of the sum of the squares of the distances in x and y.
This method is defined in class
particleShearBase.Point
Expand source code
def d_euclidian(self,theSphere): """Calculate euclidian distance to another object of type `particleShearBase.Point`or derived The calculation is done via the Pythagorean formula: square root of the sum of the squares of the distances in x and y.\n This method is defined in class `particleShearBase.Point`""" pos = theSphere.coordinates() x = pos[0] y = pos[1] return math.sqrt((x - self.x) * (x - self.x) + (y - self.y) * (y - self.y))
def move(self, dt=1)
-
Move for time interval dt
This method is defined in class
particleShearBase.Point
Expand source code
def move(self, dt=1): """Move for time interval dt This method is defined in class `particleShearBase.Point`""" self.x = self.x + self.xspeed * dt self.y = self.y + self.yspeed * dt
def n(self, theSphere)
-
Return unit vector pointing towards another sphere
For this
particleShearBase.Point
, returns vector in cartesian coordinates pointing towards the center of the sphere indicated by the parameter theSphere. This behaviour can be overriden directly or indirectly by overriding the definition ofparticleShearBase.Point.d
This method is defined in class
particleShearBase.Point
Expand source code
def n(self, theSphere): """Return unit vector pointing towards another sphere For this `particleShearBase.Point`, returns vector in cartesian coordinates pointing towards the center of the sphere indicated by the parameter theSphere. This behaviour can be overriden directly or indirectly by overriding the definition of `particleShearBase.Point.d`\n This method is defined in class `particleShearBase.Point`""" d = self.d(theSphere) if d > 0: pos = theSphere.coordinates() x = pos[0] y = pos[1] return ([(x - self.x) / d, (y - self.y) / d]) if d == 0: angle = random.random()* 2 * math.pi return [math.cos(angle), math.sin(angle)]
def relative_position(self, theSphere)
-
Return vector indicating the position of theSphere relative to myself
For this
particleShearBase.Point
, returns vectorial difference between the position of theSphere and the current object, but this behaviour can be overriden directly, or also indirectly when either the definition ofparticleShearBase.Point.d
orparticleShearBase.Point.n
is overriden.This method is defined in class
particleShearBase.Point
Expand source code
def relative_position(self,theSphere): """Return vector indicating the position of theSphere relative to myself For this `particleShearBase.Point`, returns vectorial difference between the position of theSphere and the current object, but this behaviour can be overriden directly, or also indirectly when either the definition of `particleShearBase.Point.d` or `particleShearBase.Point.n` is overriden.\n This method is defined in class `particleShearBase.Point`""" n_vector = self.n(theSphere) d = self.d(theSphere) return [n_vector[0]*d,n_vector[1]*d]
def relative_speed(self, theSphere)
-
Relative speed of the two spheres evaluated by the difference of speed of their centers
This method is defined in class
particleShearBase.Point
Expand source code
def relative_speed(self, theSphere): """Relative speed of the two spheres evaluated by the difference of speed of their centers This method is defined in class `particleShearBase.Point`""" return [theSphere.xspeed-self.xspeed, theSphere.yspeed-self.yspeed]
class PointLeesEdwards (x, y, size_x=500, size_y=500, shear=0, shear_rate=0, use_lees_edwards=True)
-
Define a moving point, under Lees-Edwards boundary conditions.
Lees-Edwards boundary conditions are period boundary conditions where particles exiting on one face of the rectangular simulation area reappear on the opposing face; in addition, during such jumps, the x-position is adjusted to reflect applied shear (e.g. delta_x = delta_y * shear) and x-speed is adjusted to reflect applied shear rate (e.g. delta_v_x = delta_y * shear_rate). This is well illustrated on a number of websites or publications (for instance, Bertevas, Erwan & Fan, Xijun & I. Tanner, Roger. (2009). Simulation of the rheological properties of suspensions of oblate spheroidal particles in a Newtonian fluid. Rheologica Acta. 49. 53-73. 10.1007/s00397-009-0390-8. )
Oriented-object implementation of the Lees-Edwards boundary conditions requires that the object is aware of the pertinent global parameters of the simulation in addition to its own local properties: size of the simulation rectangle, current shear rate, current shear.
Initialize self
- parameters
x
The x-position of the point (assumed micrometers for the simulation)y
The y-position of the point (assumed micrometers for the simulation)size_x
Width of area to be used in pixels = micrometers for the simulationsize_y
Height of area to be used in pixels = micrometers for the simulationshear
shear (affecting the x-coordinate; expressed relative, so dimension-less)shear_rate
shear rate (affecting the x-movement; expressed relative, so units 1/s)Expand source code
class PointLeesEdwards(Point): """Define a moving point, under Lees-Edwards boundary conditions. Lees-Edwards boundary conditions are period boundary conditions where particles exiting on one face of the rectangular simulation area reappear on the opposing face; in addition, during such jumps, the x-position is adjusted to reflect applied shear (e.g. delta_x = delta_y * shear) and x-speed is adjusted to reflect applied shear rate (e.g. delta_v_x = delta_y * shear_rate). This is well illustrated on a number of websites or publications (for instance, Bertevas, Erwan & Fan, Xijun & I. Tanner, Roger. (2009). Simulation of the rheological properties of suspensions of oblate spheroidal particles in a Newtonian fluid. Rheologica Acta. 49. 53-73. 10.1007/s00397-009-0390-8. )\n\n Oriented-object implementation of the Lees-Edwards boundary conditions requires that the object is aware of the pertinent global parameters of the simulation in addition to its own local properties: size of the simulation rectangle, current shear rate, current shear.""" def __init__(self, x, y,size_x=500,size_y=500,shear=0,shear_rate=0,use_lees_edwards=True): """Initialize self - **parameters**\n `x` The x-position of the point (assumed micrometers for the simulation)\n `y` The y-position of the point (assumed micrometers for the simulation)\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `shear` shear (affecting the x-coordinate; expressed relative, so dimension-less)\n `shear_rate` shear rate (affecting the x-movement; expressed relative, so units 1/s)""" super(PointLeesEdwards,self).__init__(x,y) self.use_lees_edwards=use_lees_edwards """Flag to indicate whether or not we use Lees-Edwards boundary conditions""" self.size_x=size_x """Width of area to be used in pixels = micrometers for the simulation""" self.size_y=size_y """Height of area to be used in pixels = micrometers for the simulation""" self.shear=shear """Current applied shear (relative, so dimension-less)""" self.shear_rate=shear_rate """Current shear rate (relative, so 1/s)""" def boundary_conditions(self, boundary_x, boundary_y): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n In addition to the parent method defined in `particleShear.Point`, here the corrections associated with the Lees-Edwards boundary conditions are taken into account by using `particleShear.PointLeesEdwards.lee_edwards_boundary_conditions` if `particleShear.PointLeesEdwards.use_lees_edwards` is set to True\n This method is defined in class `particleShear.PointLeesEdwards`""" self.size_x=boundary_x self.size_y=boundary_y if not self.use_lees_edwards: super(PointLeesEdwards,self).boundary_conditions(boundary_x,boundary_y) else: self.lee_edwards_boundary_conditions() def lee_edwards_closest_to_zero(self,x,y): """Return the closest match to zero (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions This method is defined in class `particleShear.PointLeesEdwards` """ while y >= (self.size_y/2): y = y - self.size_y x = x - self.size_y * self.shear while y < (-self.size_y/2): y = y + self.size_y x = x + self.size_y * self.shear while x >= (self.size_x/2): x = x - self.size_x while x < (-self.size_x/2): x = x + self.size_x return [x, y] def lee_edwards_positive(self,x,y): """Return the smallest positive solution (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions This method is defined in class `particleShear.PointLeesEdwards` """ while y >= self.size_y: y = y - self.size_y x = x - self.size_y * self.shear while y < 0: y = y + self.size_y x = x + self.size_y * self.shear while x >= self.size_x: x = x - self.size_x while x < 0: x = x + self.size_x return [x, y] def lee_edwards_relative_speed(self,theSphere): """Return relative speed using neighbor convention under Lees-Edwards boundary conditions For this, the relative speed for the nearest representation under repetition and shear is calculated. Adjustement in the x-speed is made only for transformation by multiples of the simulation area height\n This method is defined in class `particleShear.PointLeesEdwards` """ vx_rel = theSphere.xspeed - self.xspeed vy_rel = theSphere.yspeed - self.yspeed y = theSphere.y - self.y while y >= (self.size_y / 2): y = y - self.size_y vx_rel = vx_rel - self.size_y * self.shear_rate while y < (-self.size_y / 2): y = y + self.size_y vx_rel = vx_rel + self.size_y * self.shear_rate return [vx_rel, vy_rel] def relative_speed(self,theSphere): """Relative speed of the two spheres evaluated by the difference of speed of their centers. If `particleShear.PointLeesEdwards.use_lees_edwards` is set to True, this method uses `particleShear.PointLeesEdwards.lee_edwards_relative_speed`; otherwise, it uses `particleShear.Point.relative_speed` defined in the parent class `particleShear.Point`\n This method is defined in class `particleShear.PointLeesEdwards` """ if self.use_lees_edwards: return self.lee_edwards_relative_speed(theSphere) else: return super(PointLeesEdwards,self).relative_speed(theSphere) def lee_edwards_boundary_conditions(self): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n In addition, adjusting y is coupled with a change in x and speed in x-direction to reflect shear, respectively shear rate, according to the Lees-Edwards boundary conditions.\n This method is defined in class `particleShear.PointLeesEdwards`.""" old_x = self.x old_y = self.y new_pos = self.lee_edwards_positive(self.x,self.y) # Adjust speed self.xspeed = self.xspeed + (new_pos[1]-self.y)*self.shear_rate self.x = new_pos[0] self.y = new_pos[1] if self.doDrawing: self.theCanvas.move(self.shape, self.x - old_x, self.y - old_y) def lee_edwards_relative_vector(self,theSphere): """Return a relative vector to the Sphere, using smallest x- and y- distances resulting under Lees-Edwards periodic boundary condition transforms. This is achieved by applying `particleShear.PointLeesEdwards.lee_edwards_closest_to_zero` to the actual delta x and delta y vector.\n This method is defined in class `particleShear.PointLeesEdwards`. """ pos = theSphere.coordinates() x = pos[0] y = pos[1] delta_x = x - self.x delta_y = y - self.y return self.lee_edwards_closest_to_zero(delta_x, delta_y) def d(self,theSphere): """Return the distance to another sphere under Lees-Edwards boundary conditions. This is the shortest distance among periodic repetitions, as evaluated as the absolute length of the delta vector returned by `particleShear.PointLeesEdwards.lee_edwards_relative_vector`. If `particleShear.PointLeesEdwards.use_lees_edwards` is False, the parent method `particleShear.Point.d` of class `particleShear.Point` is used instead.\n This method is defined in class `particleShear.PointLeesEdwards`.""" if not self.use_lees_edwards: return super(PointLeesEdwards,self).d(theSphere) newDelta = self.lee_edwards_relative_vector(theSphere) return math.sqrt(newDelta[0] * newDelta[0] + newDelta[1] * newDelta[1]) # To get a normal unit vector to another sphere def n(self, theSphere): """Return normal unit vector pointing towards another sphere, using Lees-Edward boundary conditions for determining the shortest connection among the periodic representations. Uses `particleShear.PointLeesEdwards.lee_edwards_relative_vector` to determine the shortest distance among the peridic representations under the Lees-Edwards boundary conditions. If `particleShear.PointLeesEdwards.use_lees_edwards` is False, the parent method `particleShear.Point.n` of class `particleShear.Point` is used instead.\n This method is defined in class `particleShear.PointLeesEdwards`. """ if not self.use_lees_edwards: return super(PointLeesEdwards,self).n(theSphere) d = self.d(theSphere) if d > 0: rel=self.lee_edwards_relative_vector(theSphere) return ([rel[0] / d, rel[1] / d]) if d == 0: angle = random.random()* 2 * math.pi return [math.cos(angle), math.sin(angle)] def transmit_lees_edwards_parameters(self,theSphere): """Transmit the parameters pertaining to the Lees-Edwards boundary conditions to another sphere (that is, object deriving from `particleShear.PointLeesEdwards`). If `particleShear.PointLeesEdwards.use_lees_edwards` is set to True, transmit the instance variables regarding canvas size (`particleShear.PointLeesEdwards.size_x`,`particleShear.PointLeesEdwards.size_y`), and shear regime (`particleShear.PointLeesEdwards.shear`,`particleShear.PointLeesEdwards.shear_rate`, `particleShear.PointLeesEdwards.use_lees_edwards`) to the given target sphere. Else, only set the `particleShear.PointLeesEdwards.use_lees_edwards` field in the target sphere to False\n This method is defined in class `particleShear.PointLeesEdwards`""" if self.use_lees_edwards: theSphere.size_x = self.size_x theSphere.size_y = self.size_y theSphere.shear_rate = self.shear_rate theSphere.shear = self.shear theSphere.use_lees_edwards = self.use_lees_edwards else: if hasattr(theSphere,"use_lees_edwards"): theSphere.use_lees_edwards=False return theSphere def cool(self, f=0.8): """Cool by decreasing local speed Cool relative to fine-grained local movement (x-speed of the fluid is expected to be (y-size_y/2)*shear_rate), decrease the delta to this expected speed rather than diminishing the absolute speed. This is used to stabilize the ensemble at the given shear; physically, this reflects friction with the pore or interstitial fluid.\n This method is defined in class `particleShear.PointLeesEdwards`""" x_speed_local = (self.y-self.size_y/2)*self.shear_rate #self.xspeed = x_speed_local+(self.xspeed-x_speed_local) * f self.xspeed = x_speed_local + (self.xspeed - x_speed_local) * f self.yspeed = self.yspeed * f
Ancestors
- particleShearBase.Point.Point
Subclasses
- particleShearBase.Circle.Circle
Instance variables
var shear
-
Current applied shear (relative, so dimension-less)
var shear_rate
-
Current shear rate (relative, so 1/s)
var size_x
-
Width of area to be used in pixels = micrometers for the simulation
var size_y
-
Height of area to be used in pixels = micrometers for the simulation
var use_lees_edwards
-
Flag to indicate whether or not we use Lees-Edwards boundary conditions
Methods
def boundary_conditions(self, boundary_x, boundary_y)
-
Ensure location of the point within boundary conditions
x should be >= 0 and < boundary_x
y should be >= 0 and < boundary_y
x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x
y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y
In addition to the parent method defined in
Point
, here the corrections associated with the Lees-Edwards boundary conditions are taken into account by usingPointLeesEdwards.lee_edwards_boundary_conditions()
ifPointLeesEdwards.use_lees_edwards
is set to TrueThis method is defined in class
PointLeesEdwards
Expand source code
def boundary_conditions(self, boundary_x, boundary_y): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n In addition to the parent method defined in `particleShear.Point`, here the corrections associated with the Lees-Edwards boundary conditions are taken into account by using `particleShear.PointLeesEdwards.lee_edwards_boundary_conditions` if `particleShear.PointLeesEdwards.use_lees_edwards` is set to True\n This method is defined in class `particleShear.PointLeesEdwards`""" self.size_x=boundary_x self.size_y=boundary_y if not self.use_lees_edwards: super(PointLeesEdwards,self).boundary_conditions(boundary_x,boundary_y) else: self.lee_edwards_boundary_conditions()
def cool(self, f=0.8)
-
Cool by decreasing local speed
Cool relative to fine-grained local movement (x-speed of the fluid is expected to be (y-size_y/2)*shear_rate), decrease the delta to this expected speed rather than diminishing the absolute speed. This is used to stabilize the ensemble at the given shear; physically, this reflects friction with the pore or interstitial fluid.
This method is defined in class
PointLeesEdwards
Expand source code
def cool(self, f=0.8): """Cool by decreasing local speed Cool relative to fine-grained local movement (x-speed of the fluid is expected to be (y-size_y/2)*shear_rate), decrease the delta to this expected speed rather than diminishing the absolute speed. This is used to stabilize the ensemble at the given shear; physically, this reflects friction with the pore or interstitial fluid.\n This method is defined in class `particleShear.PointLeesEdwards`""" x_speed_local = (self.y-self.size_y/2)*self.shear_rate #self.xspeed = x_speed_local+(self.xspeed-x_speed_local) * f self.xspeed = x_speed_local + (self.xspeed - x_speed_local) * f self.yspeed = self.yspeed * f
def d(self, theSphere)
-
Return the distance to another sphere under Lees-Edwards boundary conditions.
This is the shortest distance among periodic repetitions, as evaluated as the absolute length of the delta vector returned by
PointLeesEdwards.lee_edwards_relative_vector()
. IfPointLeesEdwards.use_lees_edwards
is False, the parent methodPoint.d()
of classPoint
is used instead.This method is defined in class
PointLeesEdwards
.Expand source code
def d(self,theSphere): """Return the distance to another sphere under Lees-Edwards boundary conditions. This is the shortest distance among periodic repetitions, as evaluated as the absolute length of the delta vector returned by `particleShear.PointLeesEdwards.lee_edwards_relative_vector`. If `particleShear.PointLeesEdwards.use_lees_edwards` is False, the parent method `particleShear.Point.d` of class `particleShear.Point` is used instead.\n This method is defined in class `particleShear.PointLeesEdwards`.""" if not self.use_lees_edwards: return super(PointLeesEdwards,self).d(theSphere) newDelta = self.lee_edwards_relative_vector(theSphere) return math.sqrt(newDelta[0] * newDelta[0] + newDelta[1] * newDelta[1])
def lee_edwards_boundary_conditions(self)
-
Ensure location of the point within boundary conditions
x should be >= 0 and < boundary_x
y should be >= 0 and < boundary_y
x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x
y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y
In addition, adjusting y is coupled with a change in x and speed in x-direction to reflect shear, respectively shear rate, according to the Lees-Edwards boundary conditions.
This method is defined in class
PointLeesEdwards
.Expand source code
def lee_edwards_boundary_conditions(self): """Ensure location of the point within boundary conditions x should be >= 0 and < boundary_x\n y should be >= 0 and < boundary_y\n x will be adusted to satisfy this via addition or subtraction of multiples of boundary_x\n y will be adusted to satisfy this via addition or subtraction of multiples of boundary_y\n In addition, adjusting y is coupled with a change in x and speed in x-direction to reflect shear, respectively shear rate, according to the Lees-Edwards boundary conditions.\n This method is defined in class `particleShear.PointLeesEdwards`.""" old_x = self.x old_y = self.y new_pos = self.lee_edwards_positive(self.x,self.y) # Adjust speed self.xspeed = self.xspeed + (new_pos[1]-self.y)*self.shear_rate self.x = new_pos[0] self.y = new_pos[1] if self.doDrawing: self.theCanvas.move(self.shape, self.x - old_x, self.y - old_y)
def lee_edwards_closest_to_zero(self, x, y)
-
Return the closest match to zero (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions
This method is defined in class
PointLeesEdwards
Expand source code
def lee_edwards_closest_to_zero(self,x,y): """Return the closest match to zero (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions This method is defined in class `particleShear.PointLeesEdwards` """ while y >= (self.size_y/2): y = y - self.size_y x = x - self.size_y * self.shear while y < (-self.size_y/2): y = y + self.size_y x = x + self.size_y * self.shear while x >= (self.size_x/2): x = x - self.size_x while x < (-self.size_x/2): x = x + self.size_x return [x, y]
def lee_edwards_positive(self, x, y)
-
Return the smallest positive solution (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions
This method is defined in class
PointLeesEdwards
Expand source code
def lee_edwards_positive(self,x,y): """Return the smallest positive solution (for x and y separately) under periodic transformations respecting the Lees-Edwards boudary conditions This method is defined in class `particleShear.PointLeesEdwards` """ while y >= self.size_y: y = y - self.size_y x = x - self.size_y * self.shear while y < 0: y = y + self.size_y x = x + self.size_y * self.shear while x >= self.size_x: x = x - self.size_x while x < 0: x = x + self.size_x return [x, y]
def lee_edwards_relative_speed(self, theSphere)
-
Return relative speed using neighbor convention under Lees-Edwards boundary conditions
For this, the relative speed for the nearest representation under repetition and shear is calculated. Adjustement in the x-speed is made only for transformation by multiples of the simulation area height
This method is defined in class
PointLeesEdwards
Expand source code
def lee_edwards_relative_speed(self,theSphere): """Return relative speed using neighbor convention under Lees-Edwards boundary conditions For this, the relative speed for the nearest representation under repetition and shear is calculated. Adjustement in the x-speed is made only for transformation by multiples of the simulation area height\n This method is defined in class `particleShear.PointLeesEdwards` """ vx_rel = theSphere.xspeed - self.xspeed vy_rel = theSphere.yspeed - self.yspeed y = theSphere.y - self.y while y >= (self.size_y / 2): y = y - self.size_y vx_rel = vx_rel - self.size_y * self.shear_rate while y < (-self.size_y / 2): y = y + self.size_y vx_rel = vx_rel + self.size_y * self.shear_rate return [vx_rel, vy_rel]
def lee_edwards_relative_vector(self, theSphere)
-
Return a relative vector to the Sphere, using smallest x- and y- distances resulting under Lees-Edwards periodic boundary condition transforms.
This is achieved by applying
PointLeesEdwards.lee_edwards_closest_to_zero()
to the actual delta x and delta y vector.This method is defined in class
PointLeesEdwards
.Expand source code
def lee_edwards_relative_vector(self,theSphere): """Return a relative vector to the Sphere, using smallest x- and y- distances resulting under Lees-Edwards periodic boundary condition transforms. This is achieved by applying `particleShear.PointLeesEdwards.lee_edwards_closest_to_zero` to the actual delta x and delta y vector.\n This method is defined in class `particleShear.PointLeesEdwards`. """ pos = theSphere.coordinates() x = pos[0] y = pos[1] delta_x = x - self.x delta_y = y - self.y return self.lee_edwards_closest_to_zero(delta_x, delta_y)
def n(self, theSphere)
-
Return normal unit vector pointing towards another sphere, using Lees-Edward boundary conditions for determining the shortest connection among the periodic representations.
Uses
PointLeesEdwards.lee_edwards_relative_vector()
to determine the shortest distance among the peridic representations under the Lees-Edwards boundary conditions. IfPointLeesEdwards.use_lees_edwards
is False, the parent methodPoint.n()
of classPoint
is used instead.This method is defined in class
PointLeesEdwards
.Expand source code
def n(self, theSphere): """Return normal unit vector pointing towards another sphere, using Lees-Edward boundary conditions for determining the shortest connection among the periodic representations. Uses `particleShear.PointLeesEdwards.lee_edwards_relative_vector` to determine the shortest distance among the peridic representations under the Lees-Edwards boundary conditions. If `particleShear.PointLeesEdwards.use_lees_edwards` is False, the parent method `particleShear.Point.n` of class `particleShear.Point` is used instead.\n This method is defined in class `particleShear.PointLeesEdwards`. """ if not self.use_lees_edwards: return super(PointLeesEdwards,self).n(theSphere) d = self.d(theSphere) if d > 0: rel=self.lee_edwards_relative_vector(theSphere) return ([rel[0] / d, rel[1] / d]) if d == 0: angle = random.random()* 2 * math.pi return [math.cos(angle), math.sin(angle)]
def relative_speed(self, theSphere)
-
Relative speed of the two spheres evaluated by the difference of speed of their centers.
If
PointLeesEdwards.use_lees_edwards
is set to True, this method usesPointLeesEdwards.lee_edwards_relative_speed()
; otherwise, it usesPoint.relative_speed()
defined in the parent classPoint
This method is defined in class
PointLeesEdwards
Expand source code
def relative_speed(self,theSphere): """Relative speed of the two spheres evaluated by the difference of speed of their centers. If `particleShear.PointLeesEdwards.use_lees_edwards` is set to True, this method uses `particleShear.PointLeesEdwards.lee_edwards_relative_speed`; otherwise, it uses `particleShear.Point.relative_speed` defined in the parent class `particleShear.Point`\n This method is defined in class `particleShear.PointLeesEdwards` """ if self.use_lees_edwards: return self.lee_edwards_relative_speed(theSphere) else: return super(PointLeesEdwards,self).relative_speed(theSphere)
def transmit_lees_edwards_parameters(self, theSphere)
-
Transmit the parameters pertaining to the Lees-Edwards boundary conditions to another sphere (that is, object deriving from
PointLeesEdwards
).If
PointLeesEdwards.use_lees_edwards
is set to True, transmit the instance variables regarding canvas size (PointLeesEdwards.size_x
,PointLeesEdwards.size_y
), and shear regime (PointLeesEdwards.shear
,PointLeesEdwards.shear_rate
,PointLeesEdwards.use_lees_edwards
) to the given target sphere. Else, only set thePointLeesEdwards.use_lees_edwards
field in the target sphere to FalseThis method is defined in class
PointLeesEdwards
Expand source code
def transmit_lees_edwards_parameters(self,theSphere): """Transmit the parameters pertaining to the Lees-Edwards boundary conditions to another sphere (that is, object deriving from `particleShear.PointLeesEdwards`). If `particleShear.PointLeesEdwards.use_lees_edwards` is set to True, transmit the instance variables regarding canvas size (`particleShear.PointLeesEdwards.size_x`,`particleShear.PointLeesEdwards.size_y`), and shear regime (`particleShear.PointLeesEdwards.shear`,`particleShear.PointLeesEdwards.shear_rate`, `particleShear.PointLeesEdwards.use_lees_edwards`) to the given target sphere. Else, only set the `particleShear.PointLeesEdwards.use_lees_edwards` field in the target sphere to False\n This method is defined in class `particleShear.PointLeesEdwards`""" if self.use_lees_edwards: theSphere.size_x = self.size_x theSphere.size_y = self.size_y theSphere.shear_rate = self.shear_rate theSphere.shear = self.shear theSphere.use_lees_edwards = self.use_lees_edwards else: if hasattr(theSphere,"use_lees_edwards"): theSphere.use_lees_edwards=False return theSphere
class Simulation_dermal_filler_rheology (root_folder, do_permanent_links=True, cut_lines=5, N=150, packing_fraction=2, bimodal_factor=1.4, amplitude=0.5, Young_modulus_spheres=1000, density=50, mu=0.1, relative_viscosity=0.01, relative_y_scale_force=0.0005, relative_frequency=0.05, relative_transversal_link_strength=1, central_repulsion_coefficient=1, avoid_horizontal_angle_degree=0, avoid_height_spanning_particles=False, doCutByTriangulation=True, doDrawing=True, saveData=False)
-
Expand source code
class Simulation_dermal_filler_rheology(OscillatorySimulationFragmentation): def __init__(self,root_folder,do_permanent_links=True,cut_lines=5,N=150,packing_fraction=2, bimodal_factor=1.4, amplitude=0.5, Young_modulus_spheres=1000, density=50, mu=0.1,relative_viscosity=0.01, relative_y_scale_force=5e-4,relative_frequency=0.05, relative_transversal_link_strength=1, central_repulsion_coefficient=1, avoid_horizontal_angle_degree=0,avoid_height_spanning_particles=False, doCutByTriangulation=True,doDrawing=True,saveData=False): self.avoid_horizontal_angle_degree=avoid_horizontal_angle_degree self.avoid_height_spanning_particles=avoid_height_spanning_particles self.doCutByTriangulation=doCutByTriangulation self.bimodal_factor=bimodal_factor self.central_repulsion_coefficient=central_repulsion_coefficient self.density=density super(Simulation_dermal_filler_rheology,self).__init__( root_folder,do_permanent_links=do_permanent_links,cut_lines=cut_lines, N=N,packing_fraction=packing_fraction,amplitude=amplitude, Young_modulus_spheres=Young_modulus_spheres,mu=mu, relative_y_scale_force=relative_y_scale_force,relative_frequency=relative_frequency, doDrawing=doDrawing,saveData=saveData) self.relative_viscosity=relative_viscosity self.relative_transversal_link_strength=relative_transversal_link_strength self.function_call = "Simulation_dermal_filler_rheology(root_folder=" + str(root_folder) + ",do_permanent_links=" + \ str(do_permanent_links) + ",cut_lines=" + str(cut_lines) + ",N=" + str(N) + \ ",packing_fraction=" + str(packing_fraction) + \ ",bimodal_factor="+str(bimodal_factor)+\ ",amplitude=" + str(amplitude) + \ ",Young_modulus_spheres=" + str(Young_modulus_spheres) + \ ",density=" + str(density) + ",mu=" + str(mu) + \ ",relative_viscosity="+str(relative_viscosity)+\ ",relative_y_scale_force=" + str(relative_y_scale_force) + ",relative_frequency=" + \ str(relative_frequency) +\ ",relative_transversal_link_strength="+str(relative_transversal_link_strength)+ \ ",central_repulsion_coefficient=" + str(central_repulsion_coefficient) + \ ",avoid_horizontal_angle_degree="+str(avoid_horizontal_angle_degree)+\ ",avoid_height_spanning_particles=" + str(self.avoid_height_spanning_particles) + \ ",doCutByTriangulation="+str(self.doCutByTriangulation) + \ ",doDrawing=" + str(doDrawing) + ")" def adjustment_factor_for_bounded_preequilibration_steps(self): f=1 if self.mu<=0.1: f=f*1.5 if self.mu<=0.01: f=f*2 if self.mu<=0.001: f=f*2 if self.amplitude<=0.01: f=f*(1-(math.log10(self.amplitude)+2)) if self.amplitude<=0.005: f=f*2 if self.relative_viscosity<=0.1: f=f*2 print("Adjustment factor of pre-equilibration time for low amplitude and friction:",f) return int(f) def initEnsemble(self,do_debug=False): super(Simulation_dermal_filler_rheology,self).initEnsemble() self.theEnsemble = EnsembleCompactParticlesFromModelParameters( cut_lines=self.cut_lines,N=self.N,packing_fraction=self.packing_fraction, Young_modulus_spheres=self.Young_modulus_spheres, density=self.density,bimodal_factor=self.bimodal_factor, do_permanent_links=self.do_permanent_links,mu=self.mu,theTk=self.theTkSimulation,do_pre_equilibration=True, relative_viscosity=self.relative_viscosity,central_repulsion_coefficient=self.central_repulsion_coefficient, anticipated_amplitude=self.amplitude,relative_transversal_link_strength=self.relative_transversal_link_strength, avoid_horizontal_angle_degree=self.avoid_horizontal_angle_degree, avoid_height_spanning_particles=self.avoid_height_spanning_particles, doCutByTriangulation=self.doCutByTriangulation,doDrawing=self.doDrawing, do_debug=do_debug) self.dt=self.theEnsemble.dt self.dt_max = self.theEnsemble.dt_max
Ancestors
- particleShearSimulation.OscillatorySimulationFragmentation.OscillatorySimulationFragmentation
- particleShearSimulation.OscillatorySimulation.OscillatorySimulation
Subclasses
- particleShearSimulation.Simulation_interlocking_rheology.Simulation_interlocking_rheology
Methods
def adjustment_factor_for_bounded_preequilibration_steps(self)
-
Expand source code
def adjustment_factor_for_bounded_preequilibration_steps(self): f=1 if self.mu<=0.1: f=f*1.5 if self.mu<=0.01: f=f*2 if self.mu<=0.001: f=f*2 if self.amplitude<=0.01: f=f*(1-(math.log10(self.amplitude)+2)) if self.amplitude<=0.005: f=f*2 if self.relative_viscosity<=0.1: f=f*2 print("Adjustment factor of pre-equilibration time for low amplitude and friction:",f) return int(f)
def initEnsemble(self, do_debug=False)
-
Expand source code
def initEnsemble(self,do_debug=False): super(Simulation_dermal_filler_rheology,self).initEnsemble() self.theEnsemble = EnsembleCompactParticlesFromModelParameters( cut_lines=self.cut_lines,N=self.N,packing_fraction=self.packing_fraction, Young_modulus_spheres=self.Young_modulus_spheres, density=self.density,bimodal_factor=self.bimodal_factor, do_permanent_links=self.do_permanent_links,mu=self.mu,theTk=self.theTkSimulation,do_pre_equilibration=True, relative_viscosity=self.relative_viscosity,central_repulsion_coefficient=self.central_repulsion_coefficient, anticipated_amplitude=self.amplitude,relative_transversal_link_strength=self.relative_transversal_link_strength, avoid_horizontal_angle_degree=self.avoid_horizontal_angle_degree, avoid_height_spanning_particles=self.avoid_height_spanning_particles, doCutByTriangulation=self.doCutByTriangulation,doDrawing=self.doDrawing, do_debug=do_debug) self.dt=self.theEnsemble.dt self.dt_max = self.theEnsemble.dt_max
class Simulation_interlocking_rheology (root_folder, do_permanent_links=True, cut_lines=5, N=150, packing_fraction=2, bimodal_factor=1.4, amplitude=0.5, Young_modulus_spheres=1000, density=50, mu=0.1, relative_viscosity=0.01, relative_y_scale_force=0.0005, relative_frequency=0.05, relative_transversal_link_strength=1, avoid_horizontal_angle_degree=15, avoid_height_spanning_particles=False, interface_reenforcement_central=1, interface_reenforcement_tangential=1, central_repulsion_coefficient=1, keep_viscosity_coefficients_constant=True, cut_top_bottom=True, doCutByTriangulation=True, remove_link_fraction=0, edge_fuzziness=0, doDrawing=True, saveData=False)
-
Expand source code
class Simulation_interlocking_rheology(Simulation_dermal_filler_rheology): def __init__(self,root_folder,do_permanent_links=True,cut_lines=5,N=150,packing_fraction=2, bimodal_factor=1.4, amplitude=0.5, Young_modulus_spheres=1000, density=50, mu=0.1,relative_viscosity=0.01, relative_y_scale_force=5e-4,relative_frequency=0.05, relative_transversal_link_strength=1, avoid_horizontal_angle_degree=15,avoid_height_spanning_particles=False, interface_reenforcement_central=1,interface_reenforcement_tangential=1, central_repulsion_coefficient=1, keep_viscosity_coefficients_constant=True,cut_top_bottom=True, doCutByTriangulation=True,remove_link_fraction=0,edge_fuzziness=0,doDrawing=True,saveData=False): self.avoid_horizontal_angle_degree=avoid_horizontal_angle_degree self.interface_reenforcement_central=interface_reenforcement_central self.interface_reenforcement_tangential=interface_reenforcement_tangential self.keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant self.cut_top_bottom=cut_top_bottom self.remove_link_fraction=remove_link_fraction self.edge_fuzziness=edge_fuzziness super(Simulation_interlocking_rheology,self).__init__( root_folder,do_permanent_links=do_permanent_links,cut_lines=cut_lines, N=N,packing_fraction=packing_fraction, bimodal_factor=bimodal_factor, amplitude=amplitude, Young_modulus_spheres=Young_modulus_spheres, density=density, mu=mu,relative_viscosity=relative_viscosity, relative_y_scale_force=relative_y_scale_force,relative_frequency=relative_frequency, relative_transversal_link_strength=relative_transversal_link_strength, central_repulsion_coefficient=central_repulsion_coefficient, avoid_horizontal_angle_degree=avoid_horizontal_angle_degree, avoid_height_spanning_particles=avoid_height_spanning_particles, doCutByTriangulation=doCutByTriangulation, doDrawing=doDrawing,saveData=saveData) self.function_call = "Simulation_interlocking_rheology(root_folder=" + str(root_folder) + ",do_permanent_links=" + \ str(do_permanent_links) + ",cut_lines=" + str(cut_lines) + ",N=" + str(N) + \ ",packing_fraction=" + str(packing_fraction) + \ ",bimodal_factor=" + str(bimodal_factor) + \ ",amplitude=" + str(amplitude) + \ ",Young_modulus_spheres=" + str(Young_modulus_spheres) + \ ",density=" + str(density) + ",mu=" + str(mu) + \ ",relative_viscosity="+str(relative_viscosity)+\ ",relative_y_scale_force=" + str(relative_y_scale_force) + ",relative_frequency=" + \ str(relative_frequency) +\ ",relative_transversal_link_strength="+str(relative_transversal_link_strength)+ \ ",central_repulsion_coefficient=" + str(central_repulsion_coefficient) + \ ",avoid_horizontal_angle_degree="+str(avoid_horizontal_angle_degree)+ \ ",avoid_height_spanning_particles=" + str(self.avoid_height_spanning_particles) + \ ",interface_reenforcement_central="+str(interface_reenforcement_central)+\ ",interface_reenforcement_tangential="+str(interface_reenforcement_tangential)+\ ",keep_viscosity_coefficients_constant="+str(keep_viscosity_coefficients_constant)+ \ ",cut_top_bottom=" + str(cut_top_bottom)+ \ ",doCutByTriangulation=" + str(self.doCutByTriangulation) + \ ",remove_link_fraction=" + str(self.remove_link_fraction) + \ ",edge_fuzziness=" + str(self.edge_fuzziness) + \ ",doDrawing=" + str(doDrawing)+")" def initEnsemble(self,do_debug=False): super(Simulation_dermal_filler_rheology,self).initEnsemble() print("Simulation_interlocking_rheology: initiating ensemble") self.theEnsemble = EnsembleCompactParticlesAdjustableInterfaceStrengthFromModelParameters( cut_lines=self.cut_lines,N=self.N,packing_fraction=self.packing_fraction, Young_modulus_spheres=self.Young_modulus_spheres,density=self.density, bimodal_factor=self.bimodal_factor, do_permanent_links=self.do_permanent_links,mu=self.mu,theTk=self.theTkSimulation,do_pre_equilibration=True, relative_viscosity=self.relative_viscosity,central_repulsion_coefficient=self.central_repulsion_coefficient, anticipated_amplitude=self.amplitude,relative_transversal_link_strength=self.relative_transversal_link_strength, avoid_horizontal_angle_degree=self.avoid_horizontal_angle_degree, avoid_height_spanning_particles=self.avoid_height_spanning_particles, interface_reenforcement_central=self.interface_reenforcement_central, interface_reenforcement_tangential=self.interface_reenforcement_tangential, keep_viscosity_coefficients_constant=self.keep_viscosity_coefficients_constant, cut_top_bottom=self.cut_top_bottom,doCutByTriangulation=self.doCutByTriangulation, remove_link_fraction=self.remove_link_fraction, edge_fuzziness=self.edge_fuzziness, doDrawing=self.doDrawing, do_debug=do_debug ) self.dt=self.theEnsemble.dt self.dt_max = self.theEnsemble.dt_max
Ancestors
- particleShearSimulation.Simulation_dermal_filler_rheology.Simulation_dermal_filler_rheology
- particleShearSimulation.OscillatorySimulationFragmentation.OscillatorySimulationFragmentation
- particleShearSimulation.OscillatorySimulation.OscillatorySimulation
Methods
def initEnsemble(self, do_debug=False)
-
Expand source code
def initEnsemble(self,do_debug=False): super(Simulation_dermal_filler_rheology,self).initEnsemble() print("Simulation_interlocking_rheology: initiating ensemble") self.theEnsemble = EnsembleCompactParticlesAdjustableInterfaceStrengthFromModelParameters( cut_lines=self.cut_lines,N=self.N,packing_fraction=self.packing_fraction, Young_modulus_spheres=self.Young_modulus_spheres,density=self.density, bimodal_factor=self.bimodal_factor, do_permanent_links=self.do_permanent_links,mu=self.mu,theTk=self.theTkSimulation,do_pre_equilibration=True, relative_viscosity=self.relative_viscosity,central_repulsion_coefficient=self.central_repulsion_coefficient, anticipated_amplitude=self.amplitude,relative_transversal_link_strength=self.relative_transversal_link_strength, avoid_horizontal_angle_degree=self.avoid_horizontal_angle_degree, avoid_height_spanning_particles=self.avoid_height_spanning_particles, interface_reenforcement_central=self.interface_reenforcement_central, interface_reenforcement_tangential=self.interface_reenforcement_tangential, keep_viscosity_coefficients_constant=self.keep_viscosity_coefficients_constant, cut_top_bottom=self.cut_top_bottom,doCutByTriangulation=self.doCutByTriangulation, remove_link_fraction=self.remove_link_fraction, edge_fuzziness=self.edge_fuzziness, doDrawing=self.doDrawing, do_debug=do_debug ) self.dt=self.theEnsemble.dt self.dt_max = self.theEnsemble.dt_max
class Sphere (color, x, y, diameter, m=1, my_index=1, theCanvas=False, doDrawing=False, force_register=<particleShearBase.Force_register.Force_register object>)
-
Objects of type
CircleBasicElasticity
, using standard rather than periodic Lees-Edwards boundary conditionsConstructor as
particleShearBase.CircleBasicElasticity.__init__
but with use_lees_edwards set to False and indexing element (my_index).Expand source code
class Sphere(CircleBasicElasticity): """Objects of type `particleShear.CircleBasicElasticity`, using standard rather than periodic Lees-Edwards boundary conditions""" def __init__(self, color, x, y, diameter, m=1,my_index=1,theCanvas=False, doDrawing=False,force_register=Force_register()): """Constructor as `particleShearBase.CircleBasicElasticity.__init__` but with use_lees_edwards set to False and indexing element (my_index).""" super(Sphere,self).__init__(color,x,y,diameter,m=m, theCanvas=theCanvas, doDrawing=doDrawing,force_register=force_register, use_lees_edwards=False) self.myindex=my_index # To give a unique index
Ancestors
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
class SphereFriction (color, x, y, diameter, m=1, my_index=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>)
-
Objects of type
CircleFrictionElasticity
, using standard rather than periodic Lees-Edwards boundary conditionsConstructor as
CircleFrictionElasticity
inCircleFrictionElasticity
but with and indexing element (my_index) and with the parameter use_lees_edwards set to FalseExpand source code
class SphereFriction(CircleFrictionElasticity): """Objects of type `particleShear.CircleFrictionElasticity`, using standard rather than periodic Lees-Edwards boundary conditions""" def __init__(self, color, x, y, diameter, m=1,my_index=1, theCanvas=FALSE, doDrawing=FALSE,force_register=Force_register()): """Constructor as `particleShear.CircleFrictionElasticity.__init__` in `particleShear.CircleFrictionElasticity` but with and indexing element (my_index) and with the parameter use_lees_edwards set to False""" super(SphereFriction, self).__init__(color, x, y, diameter, m, theCanvas, doDrawing, force_register, use_lees_edwards=False) self.myindex = my_index # To give a unique index
Ancestors
- particleShearBase.CircleFrictionElasticity.CircleFrictionElasticity
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
class SphereFrictionLeesEdwards (color, x, y, diameter, m=1, my_index=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>, size_x=500, size_y=500)
-
Objects of type
CircleFrictionElasticity
, using the periodic Lees-Edwards boundary conditionsConstructor as
CircleFrictionElasticity
inCircleFrictionElasticity
but with and indexing element (my_index) and with the parameter use_lees_edwards set to TrueExpand source code
class SphereFrictionLeesEdwards(CircleFrictionElasticity): """Objects of type `particleShear.CircleFrictionElasticity` , using the periodic Lees-Edwards boundary conditions""" def __init__(self, color, x, y, diameter, m=1,my_index=1, theCanvas=FALSE, doDrawing=FALSE, force_register=Force_register(), size_x=500,size_y=500): """Constructor as `particleShear.CircleFrictionElasticity.__init__` in `particleShear.CircleFrictionElasticity` but with and indexing element (my_index) and with the parameter use_lees_edwards set to True""" super(SphereFrictionLeesEdwards,self).__init__(color, x, y, diameter, m, theCanvas, doDrawing,force_register, use_lees_edwards=True) self.use_lees_edwards=True self.myindex = my_index # To give a unique index self.size_x=size_x self.size_y=size_y
Ancestors
- particleShearBase.CircleFrictionElasticity.CircleFrictionElasticity
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearLinkableObjects.SphereLinkable.SphereLinkable
class SphereLeesEdwards (color, x, y, diameter, m=1, myindex=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>, size_x=500, size_y=500)
-
Objects of type
CircleBasicElasticity
, using periodic Lees-Edwards boundary conditionsConstructor as
particleShearBase.CircleBasicElasticity.__init__
but with use_lees_edwards set to True and indexing element (my_index).Expand source code
class SphereLeesEdwards(CircleBasicElasticity): """Objects of type `particleShear.CircleBasicElasticity`, using periodic Lees-Edwards boundary conditions""" def __init__(self, color, x, y, diameter, m=1, myindex=1,theCanvas=FALSE, doDrawing=FALSE, force_register=Force_register(),size_x=500,size_y=500): """Constructor as `particleShearBase.CircleBasicElasticity.__init__` but with use_lees_edwards set to True and indexing element (my_index).""" super(SphereLeesEdwards, self).__init__(color, x, y, diameter, m, theCanvas, doDrawing,force_register, use_lees_edwards=True) self.size_x=size_x self.size_y=size_y self.myindex = my_index
Ancestors
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
class SphereLinkable (color, x, y, diameter, m=1, my_index=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>, size_x=500, size_y=500)
-
Objects of type
CircleFrictionElasticity
, using the periodic Lees-Edwards boundary conditionsConstructor as
CircleFrictionElasticity
inCircleFrictionElasticity
but with and indexing element (my_index) and with the parameter use_lees_edwards set to TrueExpand source code
class SphereLinkable(SphereFrictionLeesEdwards): call_back_elastic_force_law_tensile = elastic_force_law_tensile def __init__(self, color, x, y, diameter, m=1,my_index=1, theCanvas=FALSE, doDrawing=FALSE, force_register=Force_register(),size_x=500,size_y=500): super(SphereLinkable,self).__init__(color, x, y, diameter, m,my_index, theCanvas, doDrawing,force_register,size_x,size_y) def cut_permanent_link(self,theSphere,do_backlink=True): foundNeighborIndex = self.findNeighborIndex(theSphere) if foundNeighborIndex >= 0: theNeighbor = self.neighbors[foundNeighborIndex] if theNeighbor.interface_type=="permanent": theNeighbor.equilibrium_distance = 0 theNeighbor.friction_position = 0 theNeighbor.interface_type = "slip" # Link back from the neighbor if do_backlink: theNeighbor.theSphere.cut_permanent_link(self, do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing() def establish_permanent_link(self,theSphere,do_backlink=True): foundNeighborIndex = self.findNeighborIndex(theSphere) if foundNeighborIndex>=0: theNeighbor = self.neighbors[foundNeighborIndex] theNeighbor.equilibrium_distance=self.r+theNeighbor.theSphere.r theNeighbor.friction_position=0 theNeighbor.interface_type="permanent" # Link back from the neighbor if do_backlink: theNeighbor.theSphere.establish_permanent_link(self,do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing() else: info = neighbor_relation_linkable(0, "permanent", theSphere) info.equilibrium_distance=self.r+theSphere.r info.friction_position = 0 if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if do_backlink: theSphere.establish_permanent_link(self,do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing() def permanentlyConnectedSpheres(self,physical_neighbors_only=True): connected=[] for theNeighbor in self.neighbors: if theNeighbor.interface_type=="permanent" and not connected.count(theNeighbor.theSphere): if physical_neighbors_only: # Additional check for physical proximity if abs(theNeighbor.theSphere.x-self.x)<self.size_x*0.5 and abs(theNeighbor.theSphere.y-self.y)<self.size_y*0.5: connected.append(theNeighbor.theSphere) return connected def test_neighbor_relation(self, theSphere): d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] is_neighbor=(d < r + self.r) # Regardless of the distance, first check whether we find the sphere in the neighbors list found = FALSE foundNeighborIndex=-1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere.myindex == theNeighbor.myindex): foundNeighborIndex = theNeighborIndex found = TRUE if not is_neighbor: #If the sphere is not a neighbor (anymore) if found: #It was previously a neighbor, but no more, so delete it from the neighbors list if possible if not self.neighbors[foundNeighborIndex].interface_type=="permanent": if self.doDrawing: self.deleteNeighborDrawing() del self.neighbors[foundNeighborIndex] if self.doDrawing: self.initiateNeighborDrawing() if is_neighbor: if not found: # Not found previously, so add a new entry info = neighbor_relation_linkable(0,"stick",theSphere) if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if self.doDrawing: self.initiateNeighborDrawing() def deleteNeighborDrawing(self): super(SphereLinkable,self).deleteNeighborDrawing() for theNeighbor in self.neighbors: if theNeighbor.linking_line_index!=-1: if self.theCanvas: self.theCanvas.delete(theNeighbor.linking_line_index) theNeighbor.linking_line_index=-1 def initiateNeighborDrawing(self): super(SphereLinkable, self).initiateNeighborDrawing() for theNeighbor in self.neighbors: if theNeighbor.linking_line_index==-1: if theNeighbor.interface_type == "permanent": # To correctly draw under periodic boundary conditions we need to use the joining vector and distance # rather than raw coordinates dist=self.d(theNeighbor.theSphere) n_vector = self.n(theNeighbor.theSphere) if self.theCanvas: theNeighbor.linking_line_index = self.theCanvas.create_line( self.x, self.y, self.x + n_vector[0] * dist, self.y + n_vector[1] * dist, width=3, fill="blue" ) # Here, permanent and transient interfaces behave the same. This is not necessarily true for subclasses def tangential_force_viscous_permanent(self,theSphere,nu): return self.tangential_force_viscous(theSphere,nu) def tangential_force(self, theSphere, nu,mu,k,k_t): d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] foundNeighborIndex=self.findNeighborIndex(theSphere) found = foundNeighborIndex >= 0 isPermanent = False if found: # Check whether symmetric backlink exists backlink=self.neighbors[foundNeighborIndex].theSphere.findNeighborIndex(self) if(not backlink>=0): print("SphereLinkable: Asymmetric neighbor problem") if self.neighbors[foundNeighborIndex].interface_type == "permanent": isPermanent = True if d < r + self.r or isPermanent: if not found: print("tangential_force: Problem with neighbors, found touching sphere not in neighbors list") return if isPermanent: viscous_force=self.tangential_force_viscous_permanent(theSphere,nu) if abs(viscous_force - theSphere.tangential_force_viscous_permanent(self, nu)) > 1e-15: print("viscous force mis-match", viscous_force - theSphere.tangential_force_viscous_permanent(self, nu)) else: viscous_force=self.tangential_force_viscous(theSphere,nu) if abs(viscous_force - theSphere.tangential_force_viscous(self, nu)) > 1e-15: print("viscous force mis-match", viscous_force - theSphere.tangential_force_viscous(self, nu)) if isPermanent: elastic_force=self.tangential_force_elastic_permanent(theSphere,k_t) else: elastic_force=self.tangential_force_elastic(theSphere,k_t) total_adherence_force = viscous_force+elastic_force friction_force=self.get_elastic_force(theSphere, k)*mu sig=1 if(total_adherence_force<0): sig=-1 if(abs(total_adherence_force)<=abs(friction_force)): if not isPermanent: self.neighbors[foundNeighborIndex].interface_type="stick" theSphere.neighbors[backlink].interface_type = "stick" else: if not isPermanent: self.neighbors[foundNeighborIndex].interface_type = "slip" self.neighbors[foundNeighborIndex].friction_position=0 theSphere.neighbors[backlink].interface_type = "slip" theSphere.neighbors[backlink].friction_position = 0 if isPermanent: force=abs(total_adherence_force) else: force=min(abs(total_adherence_force),abs(friction_force)) force=force*sig # The idea here is that the same friction point will be visited again when the neighboring sphere is examined, # so we assign only half the force here # The distribution is a bit complicated because of the conservation of angular momentum, so we use a dedicated function self.distribute_tangential_couple(theSphere, force/2) def tangential_force_elastic_permanent(self, theSphere, k_t): found = False foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere and theNeighbor.interface_type=="permanent"): foundNeighborIndex = theNeighborIndex found = TRUE if not found: print("Problem with neighbors, found permanent touching sphere not in neighbors list") else: return k_t * self.neighbors[foundNeighborIndex].friction_position def elastic_force(self, theSphere, k): foundNeighborIndex = self.findNeighborIndex(theSphere) if(foundNeighborIndex>=0): if(self.neighbors[foundNeighborIndex].interface_type != "permanent"): super(SphereLinkable,self).elastic_force(theSphere,k) return # So, we have a permanent neighbor here with a known equilibrium position d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] force = self.get_elastic_force_permanent(theSphere,k) n_vector = self.n(theSphere) self.xforce = self.xforce + force * n_vector[0] self.yforce = self.yforce + force * n_vector[1] self.force_register.record_individual_internal_force(self,theSphere,[force * n_vector[0], force * n_vector[1]]) def get_elastic_force_permanent(self,theSphere,k=1): d = self.d(theSphere) foundNeighborIndex = self.findNeighborIndex(theSphere) if(foundNeighborIndex<0): print("Could not find supposedly permanent neighbor") return 0 equilibrium_distance = self.neighbors[foundNeighborIndex].equilibrium_distance if d>=equilibrium_distance: return SphereLinkable.call_back_elastic_force_law_tensile(d, equilibrium_distance, k) return CircleBasicElasticity.call_back_elastic_force_law(d, equilibrium_distance, k, self.central_repulsion_coefficient) def contactLineColor(self,interface_type): col="green" if(interface_type == "stick" or interface_type == "permanent"): col="red" return col
Ancestors
- particleShearObjects.SphereFrictionLeesEdwards.SphereFrictionLeesEdwards
- particleShearBase.CircleFrictionElasticity.CircleFrictionElasticity
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Subclasses
- particleShearLinkableObjects.SphereLinkableAdjustableInterfaceStrength.SphereLinkableAdjustableInterfaceStrength
Methods
def call_back_elastic_force_law_tensile(d, d0, k)
-
Expand source code
def elastic_force_law_tensile(d, d0, k): return -k * (d0 - d)
def contactLineColor(self, interface_type)
-
Find current contact line color
Color codes for the interface state; by default, red is locked, green is slipping. These settings can be changed in the
particleShear.CircleMassNeighbors.graphical_output_configuration
instance variableThis method is defined in class
CircleMassNeighbors
Expand source code
def contactLineColor(self,interface_type): col="green" if(interface_type == "stick" or interface_type == "permanent"): col="red" return col
def cut_permanent_link(self, theSphere, do_backlink=True)
-
Expand source code
def cut_permanent_link(self,theSphere,do_backlink=True): foundNeighborIndex = self.findNeighborIndex(theSphere) if foundNeighborIndex >= 0: theNeighbor = self.neighbors[foundNeighborIndex] if theNeighbor.interface_type=="permanent": theNeighbor.equilibrium_distance = 0 theNeighbor.friction_position = 0 theNeighbor.interface_type = "slip" # Link back from the neighbor if do_backlink: theNeighbor.theSphere.cut_permanent_link(self, do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing()
def deleteNeighborDrawing(self)
-
Deletes the drawing objects for the neighbor interfaces
There seems to be no good way to move a line, so deleting and drawing is done at every simulation step for the neighbor lines.
This method is defined in class
CircleMassNeighbors
Expand source code
def deleteNeighborDrawing(self): super(SphereLinkable,self).deleteNeighborDrawing() for theNeighbor in self.neighbors: if theNeighbor.linking_line_index!=-1: if self.theCanvas: self.theCanvas.delete(theNeighbor.linking_line_index) theNeighbor.linking_line_index=-1
def elastic_force(self, theSphere, k)
-
Calculate and apply elastic force excerted on this sphere by
theSphere
This method will check for contact and so is more time consuming than
CircleBasicElasticity.elastic_force_from_neighbors()
This method is defined in class
CircleBasicElasticity
Expand source code
def elastic_force(self, theSphere, k): foundNeighborIndex = self.findNeighborIndex(theSphere) if(foundNeighborIndex>=0): if(self.neighbors[foundNeighborIndex].interface_type != "permanent"): super(SphereLinkable,self).elastic_force(theSphere,k) return # So, we have a permanent neighbor here with a known equilibrium position d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] force = self.get_elastic_force_permanent(theSphere,k) n_vector = self.n(theSphere) self.xforce = self.xforce + force * n_vector[0] self.yforce = self.yforce + force * n_vector[1] self.force_register.record_individual_internal_force(self,theSphere,[force * n_vector[0], force * n_vector[1]])
def establish_permanent_link(self, theSphere, do_backlink=True)
-
Expand source code
def establish_permanent_link(self,theSphere,do_backlink=True): foundNeighborIndex = self.findNeighborIndex(theSphere) if foundNeighborIndex>=0: theNeighbor = self.neighbors[foundNeighborIndex] theNeighbor.equilibrium_distance=self.r+theNeighbor.theSphere.r theNeighbor.friction_position=0 theNeighbor.interface_type="permanent" # Link back from the neighbor if do_backlink: theNeighbor.theSphere.establish_permanent_link(self,do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing() else: info = neighbor_relation_linkable(0, "permanent", theSphere) info.equilibrium_distance=self.r+theSphere.r info.friction_position = 0 if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if do_backlink: theSphere.establish_permanent_link(self,do_backlink=False) if self.doDrawing: self.initiateNeighborDrawing()
def get_elastic_force_permanent(self, theSphere, k=1)
-
Expand source code
def get_elastic_force_permanent(self,theSphere,k=1): d = self.d(theSphere) foundNeighborIndex = self.findNeighborIndex(theSphere) if(foundNeighborIndex<0): print("Could not find supposedly permanent neighbor") return 0 equilibrium_distance = self.neighbors[foundNeighborIndex].equilibrium_distance if d>=equilibrium_distance: return SphereLinkable.call_back_elastic_force_law_tensile(d, equilibrium_distance, k) return CircleBasicElasticity.call_back_elastic_force_law(d, equilibrium_distance, k, self.central_repulsion_coefficient)
def initiateNeighborDrawing(self)
-
Generate the graphical representation of the neighboring lines
This method is defined in class
CircleMassNeighbors
Expand source code
def initiateNeighborDrawing(self): super(SphereLinkable, self).initiateNeighborDrawing() for theNeighbor in self.neighbors: if theNeighbor.linking_line_index==-1: if theNeighbor.interface_type == "permanent": # To correctly draw under periodic boundary conditions we need to use the joining vector and distance # rather than raw coordinates dist=self.d(theNeighbor.theSphere) n_vector = self.n(theNeighbor.theSphere) if self.theCanvas: theNeighbor.linking_line_index = self.theCanvas.create_line( self.x, self.y, self.x + n_vector[0] * dist, self.y + n_vector[1] * dist, width=3, fill="blue" )
def permanentlyConnectedSpheres(self, physical_neighbors_only=True)
-
Expand source code
def permanentlyConnectedSpheres(self,physical_neighbors_only=True): connected=[] for theNeighbor in self.neighbors: if theNeighbor.interface_type=="permanent" and not connected.count(theNeighbor.theSphere): if physical_neighbors_only: # Additional check for physical proximity if abs(theNeighbor.theSphere.x-self.x)<self.size_x*0.5 and abs(theNeighbor.theSphere.y-self.y)<self.size_y*0.5: connected.append(theNeighbor.theSphere) return connected
def tangential_force(self, theSphere, nu, mu, k, k_t)
-
Calculate and appropriately distribute the tangential force of
theSphere
on the current objectThe calculation of the tangential force is calculated in a manner to ensure a high level of conservation of angular and linear momentum, including possible rounding errors. For this, it is safest to distribute the forces in a manner that conserves angular momentum and linear momentum rather than anticipating that an opposing force will arise when the same interface is considered from the point of view of the partner sphere. It could for instance happen that the other sphere considers the same interface as slipping while here it is locked and so the magnitude of force would be different.
So when calculating the interface from the perspective of the current sphere, we count half the force but distribute it immediately and with conservation of both linear and angular momentum to the two involved spheres, by using the
CircleFrictionElasticity.distribute_tangential_couple()
. The same interface will be visited again from the perspective oftheSphere
, but even if the force magnitude is slightly different due to numerical or simulation imprecision, this will not generate a net linear or angular momentum.To further reduce chances of erroneous calculation, changes in slip / locked status of the interface are immediately transmitted to the partner interface under control of the partner sphere.
Compared to Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902, this is intended to be the same torque compensation mechanism (eq. 8 of Otsuki et al.) except for that the actual separation distance is used and so angular momentum is conserved exactly, not just approximately.
This method is defined in class
CircleFrictionElasticity
Expand source code
def tangential_force(self, theSphere, nu,mu,k,k_t): d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] foundNeighborIndex=self.findNeighborIndex(theSphere) found = foundNeighborIndex >= 0 isPermanent = False if found: # Check whether symmetric backlink exists backlink=self.neighbors[foundNeighborIndex].theSphere.findNeighborIndex(self) if(not backlink>=0): print("SphereLinkable: Asymmetric neighbor problem") if self.neighbors[foundNeighborIndex].interface_type == "permanent": isPermanent = True if d < r + self.r or isPermanent: if not found: print("tangential_force: Problem with neighbors, found touching sphere not in neighbors list") return if isPermanent: viscous_force=self.tangential_force_viscous_permanent(theSphere,nu) if abs(viscous_force - theSphere.tangential_force_viscous_permanent(self, nu)) > 1e-15: print("viscous force mis-match", viscous_force - theSphere.tangential_force_viscous_permanent(self, nu)) else: viscous_force=self.tangential_force_viscous(theSphere,nu) if abs(viscous_force - theSphere.tangential_force_viscous(self, nu)) > 1e-15: print("viscous force mis-match", viscous_force - theSphere.tangential_force_viscous(self, nu)) if isPermanent: elastic_force=self.tangential_force_elastic_permanent(theSphere,k_t) else: elastic_force=self.tangential_force_elastic(theSphere,k_t) total_adherence_force = viscous_force+elastic_force friction_force=self.get_elastic_force(theSphere, k)*mu sig=1 if(total_adherence_force<0): sig=-1 if(abs(total_adherence_force)<=abs(friction_force)): if not isPermanent: self.neighbors[foundNeighborIndex].interface_type="stick" theSphere.neighbors[backlink].interface_type = "stick" else: if not isPermanent: self.neighbors[foundNeighborIndex].interface_type = "slip" self.neighbors[foundNeighborIndex].friction_position=0 theSphere.neighbors[backlink].interface_type = "slip" theSphere.neighbors[backlink].friction_position = 0 if isPermanent: force=abs(total_adherence_force) else: force=min(abs(total_adherence_force),abs(friction_force)) force=force*sig # The idea here is that the same friction point will be visited again when the neighboring sphere is examined, # so we assign only half the force here # The distribution is a bit complicated because of the conservation of angular momentum, so we use a dedicated function self.distribute_tangential_couple(theSphere, force/2)
def tangential_force_elastic_permanent(self, theSphere, k_t)
-
Expand source code
def tangential_force_elastic_permanent(self, theSphere, k_t): found = False foundNeighborIndex = -1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere == theNeighbor.theSphere and theNeighbor.interface_type=="permanent"): foundNeighborIndex = theNeighborIndex found = TRUE if not found: print("Problem with neighbors, found permanent touching sphere not in neighbors list") else: return k_t * self.neighbors[foundNeighborIndex].friction_position
def tangential_force_viscous_permanent(self, theSphere, nu)
-
Expand source code
def tangential_force_viscous_permanent(self,theSphere,nu): return self.tangential_force_viscous(theSphere,nu)
def test_neighbor_relation(self, theSphere)
-
Test whether a given sphere is a neighbor
Tests whether or not the sphere is a geometrical neighbor. Depending on the result, update the list of
CircleMassNeighbors.neighbors
. If necessary, draw or delete the associated shapes.This method is defined in class
CircleMassNeighbors
Expand source code
def test_neighbor_relation(self, theSphere): d = self.d(theSphere) pos = theSphere.coordinates() r = pos[2] is_neighbor=(d < r + self.r) # Regardless of the distance, first check whether we find the sphere in the neighbors list found = FALSE foundNeighborIndex=-1 for theNeighborIndex in range(len(self.neighbors)): theNeighbor = self.neighbors[theNeighborIndex] if (theSphere.myindex == theNeighbor.myindex): foundNeighborIndex = theNeighborIndex found = TRUE if not is_neighbor: #If the sphere is not a neighbor (anymore) if found: #It was previously a neighbor, but no more, so delete it from the neighbors list if possible if not self.neighbors[foundNeighborIndex].interface_type=="permanent": if self.doDrawing: self.deleteNeighborDrawing() del self.neighbors[foundNeighborIndex] if self.doDrawing: self.initiateNeighborDrawing() if is_neighbor: if not found: # Not found previously, so add a new entry info = neighbor_relation_linkable(0,"stick",theSphere) if self.doDrawing: self.deleteNeighborDrawing() self.neighbors.append(info) if self.doDrawing: self.initiateNeighborDrawing()
class SphereLinkableAdjustableInterfaceStrength (color, x, y, diameter, m=1, my_index=1, theCanvas=0, doDrawing=0, force_register=<particleShearBase.Force_register.Force_register object>, size_x=500, size_y=500, permanent_ratio_central=1, permanent_ratio_tangential=1, keep_viscosity_coefficients_constant=True)
-
Objects of type
CircleFrictionElasticity
, using the periodic Lees-Edwards boundary conditionsConstructor as
CircleFrictionElasticity
inCircleFrictionElasticity
but with and indexing element (my_index) and with the parameter use_lees_edwards set to TrueExpand source code
class SphereLinkableAdjustableInterfaceStrength(SphereLinkable): def __init__(self, color, x, y, diameter, m=1,my_index=1, theCanvas=FALSE, doDrawing=FALSE, force_register=Force_register(),size_x=500,size_y=500,permanent_ratio_central=1,permanent_ratio_tangential=1, keep_viscosity_coefficients_constant=True): # The idea is here that we specifically modify the permanent links to have mechanical properties that can be different # from the purely frictional ones self.permanent_ratio_central=permanent_ratio_central self.permanent_ratio_tangential=permanent_ratio_tangential self.keep_viscosity_coefficients_constant=keep_viscosity_coefficients_constant super(SphereLinkableAdjustableInterfaceStrength,self).__init__( color, x, y, diameter, m,my_index, theCanvas, doDrawing,force_register,size_x,size_y) def tangential_force_elastic_permanent(self, theSphere, k_t): #print("tangential_force_elastic_permanent", k_t,self.permanent_ratio_tangential) return super(SphereLinkableAdjustableInterfaceStrength,self).\ tangential_force_elastic_permanent(theSphere, k_t)*self.permanent_ratio_tangential def tangential_force_viscous_permanent(self, theSphere, nu): if(self.keep_viscosity_coefficients_constant): return super(SphereLinkableAdjustableInterfaceStrength, self).tangential_force_viscous_permanent(theSphere, nu) #print("tangential_force_viscous_permanent",self.permanent_ratio_tangential) return super(SphereLinkableAdjustableInterfaceStrength, self). \ tangential_force_viscous_permanent(theSphere, nu) * self.permanent_ratio_tangential def get_elastic_force_permanent(self,theSphere,k=1): #print("get_elastic_force_permanent",k,self.permanent_ratio_central) return super(SphereLinkableAdjustableInterfaceStrength, self). \ get_elastic_force_permanent(theSphere, k)*self.permanent_ratio_central def get_central_viscous_force(self, theSphere, nu): foundNeighborIndex = self.findNeighborIndex(theSphere) if(self.keep_viscosity_coefficients_constant): return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu) if (foundNeighborIndex >= 0): if (self.neighbors[foundNeighborIndex].interface_type != "permanent"): return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu) else: return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu)*\ self.permanent_ratio_central return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu) def contactLineColor(self,interface_type): col="green" if(interface_type == "stick" ): col="red" if (interface_type == "permanent" ): col = "darkorange" return col
Ancestors
- particleShearLinkableObjects.SphereLinkable.SphereLinkable
- particleShearObjects.SphereFrictionLeesEdwards.SphereFrictionLeesEdwards
- particleShearBase.CircleFrictionElasticity.CircleFrictionElasticity
- particleShearBase.CircleBasicElasticity.CircleBasicElasticity
- particleShearBase.CircleMassNeighbors.CircleMassNeighbors
- particleShearBase.CircleMass.CircleMass
- particleShearBase.Circle.Circle
- particleShearBase.PointLeesEdwards.PointLeesEdwards
- particleShearBase.Point.Point
Methods
def contactLineColor(self, interface_type)
-
Find current contact line color
Color codes for the interface state; by default, red is locked, green is slipping. These settings can be changed in the
particleShear.CircleMassNeighbors.graphical_output_configuration
instance variableThis method is defined in class
CircleMassNeighbors
Expand source code
def contactLineColor(self,interface_type): col="green" if(interface_type == "stick" ): col="red" if (interface_type == "permanent" ): col = "darkorange" return col
def get_central_viscous_force(self, theSphere, nu)
-
Calculate and apply the central viscous force on this sphere by a given sphere (
theSphere
)Eq. 3 from Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The magnitude of the viscous force depends on the relative speed. The convention is similar to the central elastic force (
CircleBasicElasticity.get_elastic_force()
). If the current object is repulsed by an approaching sphere (theSphere
), a negative force is returned.This method is defined in class
CircleBasicElasticity
.Expand source code
def get_central_viscous_force(self, theSphere, nu): foundNeighborIndex = self.findNeighborIndex(theSphere) if(self.keep_viscosity_coefficients_constant): return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu) if (foundNeighborIndex >= 0): if (self.neighbors[foundNeighborIndex].interface_type != "permanent"): return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu) else: return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu)*\ self.permanent_ratio_central return super(SphereLinkableAdjustableInterfaceStrength, self).get_central_viscous_force(theSphere, nu)
def get_elastic_force_permanent(self, theSphere, k=1)
-
Expand source code
def get_elastic_force_permanent(self,theSphere,k=1): #print("get_elastic_force_permanent",k,self.permanent_ratio_central) return super(SphereLinkableAdjustableInterfaceStrength, self). \ get_elastic_force_permanent(theSphere, k)*self.permanent_ratio_central
def tangential_force_elastic_permanent(self, theSphere, k_t)
-
Expand source code
def tangential_force_elastic_permanent(self, theSphere, k_t): #print("tangential_force_elastic_permanent", k_t,self.permanent_ratio_tangential) return super(SphereLinkableAdjustableInterfaceStrength,self).\ tangential_force_elastic_permanent(theSphere, k_t)*self.permanent_ratio_tangential
def tangential_force_viscous_permanent(self, theSphere, nu)
-
Expand source code
def tangential_force_viscous_permanent(self, theSphere, nu): if(self.keep_viscosity_coefficients_constant): return super(SphereLinkableAdjustableInterfaceStrength, self).tangential_force_viscous_permanent(theSphere, nu) #print("tangential_force_viscous_permanent",self.permanent_ratio_tangential) return super(SphereLinkableAdjustableInterfaceStrength, self). \ tangential_force_viscous_permanent(theSphere, nu) * self.permanent_ratio_tangential
class StressTensorEvaluation (size_x, size_y, theCanvas=False)
-
Class to evaluate mean stress tensor components from a
Force_register
This class implements different expressions to evaluate mean force tensor components from the spatially localized force vectores as stored in
Force_register
variablesDefined in sub-package particleShearBase
Initialize by setting all the mean stress tensor expression to 2x2 zero matrices
Expand source code
class StressTensorEvaluation(): """Class to evaluate mean stress tensor components from a `particleShear.Force_register` This class implements different expressions to evaluate mean force tensor components from the spatially localized force vectores as stored in `particleShear.Force_register` variables\n Defined in sub-package particleShearBase""" def __init__(self,size_x,size_y,theCanvas=False): """ Initialize by setting all the mean stress tensor expression to 2x2 zero matrices""" self.size_x=size_x """ Width of the simulation area in micrometers""" self.size_y=size_y """ Height of the simulation area in micrometers""" self.overall_stress_tensor=[[0,0],[0,0]] """ Primary internal stress tensor, here implemented to be identical to `particleShear.StressTensorEvaluation.stress_tensor_LW`""" self.stress_tensor_LW = [[0,0],[0,0]] """ Love-Weber definition of the stress tensor (see for instance eq. 18 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression""" self.stress_tensor_unbalanced_forces = [[0, 0], [0, 0]] """ Non-compensated acceleration stress tensor (see for instance eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression""" self.stress_tensor_linear_acceleration_otsuki = [[0, 0], [0, 0]] """ Inertia compensation term given by eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. We do not use this term as compared to Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517, acceleration seems to be missing.""" self.stress_tensor_linear_acceleration=[[0, 0], [0, 0]] """ Linear acceleration stress tensor. Here, we neglect the effects of gravity, so the linear acceleration is linked solely to the unbalanced force (see for instance eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.). We consider therefore that `particleShear.StressTensorEvaluation.stress_tensor_linear_acceleration` is equal `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces`. Soil mechanics convention: normal is stress is positive for compression (which would lead to acceleration away from the center of the assembly)""" self.stress_tensor_with_external_forces=[[0,0],[0,0]] """Force stress tensor ascribed to external force (see eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression""" self.stress_tensor_spin_kinetic_energy=[[0,0],[0,0]] """Tensile stress tensor reflecting centrifugal forces from the rotation of the particles. This is the last of of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), Corrected here by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij).\n\n We also reverse the sign due to using the Soil mechanics convention: normal is stress is positive for compression, so this is by definition negative for rotation rates different from 0 """ self.stress_tensor_unbalanced_torque = [[0, 0], [0, 0]] """Asymmetric stress tensor reflecting the unbalanced torques causing rotational acceleration of the particles. This is the second last term of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected here by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij).\n\n We also reverse the sign due to using the Soil mechanics convention """ self.stress_tensor_internal_torque = [[0, 0], [0, 0]] """Asymmetric stress tensor reflecting the purely internal interaction torques causing rotational acceleration of the particles. With restriction to the purely internal torques (vs. externally applied ones), this is the second last term of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.). We corrected here the expression given by Nicot et al. by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij) We also reverse the sign due to using the Soil mechanics convention """ self.theCanvas=theCanvas """Reference to Tkinter canvas for drawing forces""" self.graphical_output_configuration=Graphical_output_configuration() """`particleShear.Graphical_output_configuration` to configure drawing""" self.toDeleteList=[] """To move lines on Tkinter displays, one needs to delete them and redraw them, this is to keep track of the currently active lines""" def evaluate_force_stress_tensors(self, force_register): """Evaluate the stress tensors linked to forces This method evaluates the terms `particleShear.StressTensorEvaluation.stress_tensor_LW`, `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` and `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces` from the forces and locations stored in the force_register argument (of type `particleShear.Force_register`) The `particleShear.StressTensorEvaluation.stress_tensor_LW` is evaluated from the internal forces acting between pairs of spheres. This information is stored in the `particleShear.Force_register.pair_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are excerting each repulsive forces on each other, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_LW`. Since repulsion arises if the ensemble is compressed, this means that the sign convention associates positive normal stress with a compressive state. This is the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.) The `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` is evaluated from the net forces acting on the individual internal spheres. This information is stored in the `particleShear.Force_register.total_particle_force_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are being pushed away from the center of the assembly, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces`. Such net repulsion arises on a compressed, but not spatially restricted assembly; a typical situation would be free boundaries that allow the spheres to move away from ech other and a compressed initial assembly that then expands into the free available space. Since the sign convention followed here associates positive normal stress with a compressive state, this means that the Soil mechanics sign convention is followed (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582. The `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces` is evaluated from the boundary forces acting through the domain boundaries or via spheres on the boundaries with imposed movement. This information is stored in the `particleShear.Force_register.external_force_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are on the average pushed inward, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`. Average inward forces mean that the assembly is kept in a compressive state by the external forces. Association between compressive state and positive normal stresses corresponds to the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.) There is a particularity when using Lees-Edwards boundary conditions and Sllod stabilization of motion in oscillatory shear experiments (i.e. the primary conditions used here and in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). The Sllod stabilization means that one considers only the excess momentum compared to the anticipated movement due to the general shearing of the assembly (see the peculiar momenta in Otsuki et al.; for the implementation of the shear speed change, see `particleShear.CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change` in class `particleShear.CanvasPointsBasicElasticityLeesEdwards` ). This means that the inertial forces that would be needed to accelerate the overall assembly to the local shearing speeds are ignored. For low frequencies and amplitudes, this becomes negligeable, but at higher shear and frequencies, the simulation will not take into account a large part of the inertial forces, whereas a rheological measurement would, at least before correction for inertia. `particleShear.StressTensorEvaluation.stress_tensor_linear_acceleration` is set equal to `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` since we neglect gravity here Method defined in `particleShear.StressTensorEvaluation`""" self.stress_tensor_LW = [[0, 0], [0, 0]] self.stress_tensor_unbalanced_forces = [[0, 0], [0, 0]] self.stress_tensor_linear_acceleration = [[0, 0], [0, 0]] self.stress_tensor_with_external_forces = [[0, 0], [0, 0]] for pair in force_register.pair_register: target=pair[0] source=pair[1] force=pair[2] delta_x = [target.x-source.x, target.y-source.y] for i in range(2): for j in range(2): self.stress_tensor_LW[i][j] = self.stress_tensor_LW[i][j] + force[i]*1e-12 * delta_x[j] * 1e-6 / 2 for i in range(2): for j in range(2): self.stress_tensor_LW[i][j] = self.stress_tensor_LW[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) for single in force_register.total_particle_force_register: target = single[0] force = single[1] delta_x = [target.x-self.size_x/2 , target.y-self.size_y/2 ] for i in range(2): for j in range(2): self.stress_tensor_unbalanced_forces[i][j] = self.stress_tensor_unbalanced_forces[i][j] + force[i]*1e-12 * delta_x[j] * 1e-6 for i in range(2): for j in range(2): self.stress_tensor_unbalanced_forces[i][j] = self.stress_tensor_unbalanced_forces[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) if self.theCanvas: for theLine in self.toDeleteList: self.theCanvas.delete(theLine) for external_force in force_register.external_force_register: target = external_force[0] force = external_force[1] r = [target.x-self.size_x/2, target.y-self.size_y/2] if self.theCanvas and self.graphical_output_configuration.draw_boundary_forces: self.toDeleteList.append( self.theCanvas.create_line(target.x,target.y,target.x+force[0]*1000,target.y+force[1]*1000,fill="darkorchid")) for i in range(2): for j in range(2): self.stress_tensor_with_external_forces[i][j] = self.stress_tensor_with_external_forces[i][j]\ -force[i]*1e-12 * r[j] * 1e-6 for i in range(2): for j in range(2): self.stress_tensor_with_external_forces[i][j] = \ self.stress_tensor_with_external_forces[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) for i in range(2): for j in range(2): # These are synonyms as we do not include gravity self.stress_tensor_linear_acceleration[i][j]=self.stress_tensor_unbalanced_forces[i][j] def evaluate_spin_kinetic_energy_stress_tensor(self,theSphereList): """Evaluate the stress tensor associated with the centrifugal forces arising by rotation of the spheres around their axis The tensile stress tensor reflects the centrifugal forces from the rotation of the particles. This is the spin kinetic term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_spin_kinetic_energy`.\n\n We also reverse the sign due to using the Soil mechanics convention: normal is stress is positive for compression, so this is by definition negative for rotation rates different from 0\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_spin_kinetic_energy = [[0, 0], [0, 0]] for theSphere in theSphereList: # The moment of inertia is in mg*micrometers^2, but we need it in kg*m^2 here. The # unit conversion factor is therefore 10^(-18) moment_of_inertia = theSphere.inertia * 1e-18 K = theSphere.omega *theSphere.omega / 2 * moment_of_inertia for i in range(2): self.stress_tensor_spin_kinetic_energy[i][i] = self.stress_tensor_spin_kinetic_energy[i][i] - K # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_spin_kinetic_energy[i][j] = self.stress_tensor_spin_kinetic_energy[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6) def evaluate_unbalanced_torque_stress_tensor(self,force_register): """Evaluate the stress tensor associated with overall unbalanced torques acting on the particle The unbalanced torque stress tensor reflects the asymmetric contribution of torques leading to acceleration or deceleration of the rotation of the particles. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_torque`.\n\n We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_unbalanced_torque= [[0, 0], [0, 0]] # The spring constant is in mg/s-2/m of depth, and is multiplied with micrometers to get the forces # So the force have the units micrometers*mg/s-2/m # For the torque, this is further multiplied by micrometers, so that makes # micrometers^2/mg/s-2/m of depth for unbalanced_torque in force_register.unbalanced_moment_register: M = unbalanced_torque[1]*1e-18 self.stress_tensor_unbalanced_torque[0][1]=self.stress_tensor_unbalanced_torque[0][1]-M/2 self.stress_tensor_unbalanced_torque[1][0] =self.stress_tensor_unbalanced_torque[1][0]+M/2 # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_unbalanced_torque[i][j] = self.stress_tensor_unbalanced_torque[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6) def evaluate_internal_torque_stress_tensor(self,force_register): """Evaluate the stress tensor associated with internal unbalanced torques acting on the particle The internal unbalanced torque stress tensor reflects the asymmetric contribution of torques arising from frictional particle-particle interaction strictly within the simulation area. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), restricted to the internal torques, and corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_internal_torque`.\n\n We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_internal_torque= [[0, 0], [0, 0]] # The spring constant is in mg/s-2/m of depth, and is multiplied with micrometers to get the forces # So the force have the units micrometers*mg/s-2/m # For the torque, this is further multiplied by micrometers, so that makes # micrometers^2/mg/s-2/m of depth for internal_torque in force_register.internal_moment_register: M = internal_torque[2]*1e-18 self.stress_tensor_internal_torque[0][1]=self.stress_tensor_internal_torque[0][1]-M/2 self.stress_tensor_internal_torque[1][0] =self.stress_tensor_internal_torque[1][0]+M/2 # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_internal_torque[i][j] = self.stress_tensor_internal_torque[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6) def evaluate_rotational_stress_tensors(self,force_register,theSphereList): """Evaluate the rotational stress tensors This function invokes `particleShear.StressTensorEvaluation.evaluate_spin_kinetic_energy_stress_tensor`, `particleShear.StressTensorEvaluation.evaluate_unbalanced_torque_stress_tensor` and `particleShear.StressTensorEvaluation.evaluate_internal_torque_stress_tensor`. The results are stored in the instance variables `particleShear.StressTensorEvaluation.stress_tensor_spin_kinetic_energy`, `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_torque` and `particleShear.StressTensorEvaluation.stress_tensor_internal_torque` \n\n Method defined in `particleShear.StressTensorEvaluation` """ self.evaluate_spin_kinetic_energy_stress_tensor(theSphereList) self.evaluate_unbalanced_torque_stress_tensor(force_register) self.evaluate_internal_torque_stress_tensor(force_register) def evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor(self,theSphereList,shear_rate): """ Evaluate the inertial compensation term defined by Otsuki et al. This term is the inertial compensation term (second righthand term) in eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. We provide this term for completeness. This term does not appear as such in other main references on shear tensors in granular media (Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), and it seems to have unphysical aspects. Consider a simplified assembly made from a single sphere without applied shear, moving linearly without applied force. All elements of the stress tensor should be zero for such a freely moving sphere free from any applied external force. If the speed of the sphere is not exactly along the x- or y-direction, the inertial compensation term by Otsuki et al. is non-zero, and this should not be. So while we provide the inertial term by Otsuki et al. for completeness, we do not recommend using it. Method defined in `particleShear.StressTensorEvaluation`""" self.stress_tensor_linear_acceleration_otsuki = [[0, 0], [0, 0]] for theSphere in theSphereList: # Units: forces are in N # the mass is in mg # the length scales are in micrometer # so here to kgm/s one needs to multiply by 10^-12 px=(theSphere.xspeed-shear_rate*(theSphere.y-self.size_y/2))*theSphere.m*1e-12 py=theSphere.yspeed*theSphere.m*1e-12 p=[px,py] for i in range(2): for j in range(2): # Eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional # jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. # This doesn't seem to be quite right as it doesn't seem to involve acceleration self.stress_tensor_linear_acceleration_otsuki[i][j] = self.stress_tensor_linear_acceleration_otsuki[i][j]-\ p[i]*p[j]/theSphere.m*1e6 for i in range(2): for j in range(2): self.stress_tensor_linear_acceleration_otsuki[i][j] = \ self.stress_tensor_linear_acceleration_otsuki[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) def evaluate_stress_tensors(self,force_register,theSphereList,shear_rate): """Evaluate the stress tensor expressions This is the main method to be called for stress tensor evaluation. It uses the forces stored in the various fields of the force_register to evaluate the stress tensor expressions linked to forces via `particleShear.StressTensorEvaluation.evaluate_force_stress_tensors`. The method also calls `particleShear.StressTensorEvaluation.evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor`, see these two methods for further details. Finally, the relevant overall internal force stress tensor (`particleShear.StressTensorEvaluation.overall_stress_tensor`) is set equal to the Love-Weber expression. This intrinsically compensates for inertia by local acceleration, but it ignores the spin terms (rotation of the spheres and change in rotation rate). The approximation is justified by the quasistatic conditions used in the simulations, but may need improved implementation in the future for simulations with rapidly spinning ("rolling") particles. Method defined in `particleShear.StressTensorEvaluation`""" self.evaluate_force_stress_tensors(force_register) self.evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor(theSphereList,shear_rate) self.evaluate_rotational_stress_tensors(force_register,theSphereList) self.overall_stress_tensor=[[0,0],[0,0]] for i in range(2): for j in range(2): # Compensation for linear acceleration as in Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On # the definition of the stress tensor in granular media. # International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517. # Also, taking into account only internal torque self.overall_stress_tensor[i][j] = self.overall_stress_tensor[i][j]+self.stress_tensor_LW[i][j] \ +self.stress_tensor_internal_torque[i][j]\ +self.stress_tensor_spin_kinetic_energy[i][j] def evaluate_quasistatic_shear_stress(self): """Evaluate the internal shear stress From the stress tensor (`particleShear.StressTensorEvaluation.overall_stress_tensor`) calculated from the internal forces, evaluate the shear stress (xy element). Since an external measurement apparatus would have to provide a force opposing the shear stress force arising at the surface, we invert signs and return the negative of the xy element of `particleShear.StressTensorEvaluation.overall_stress_tensor`. Method defined in `particleShear.StressTensorEvaluation`""" return (-self.overall_stress_tensor[0][1]) def evaluate_externally_applied_shear_stress(self): """Evaluate the shear stress from the external forces acting on the ensemble From the external force stress tensor (`particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`) calculated from the externally acting forces, evaluate the shear stress (xy element). To account for the fact that the force on an external measurement apparatus would be the inverse of the force applied on the boundary paraticles, we return the negative of the xy element of the `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`. Method defined in `particleShear.StressTensorEvaluation`""" return (-self.stress_tensor_with_external_forces[0][1])
Instance variables
var graphical_output_configuration
-
Graphical_output_configuration
to configure drawing var overall_stress_tensor
-
Primary internal stress tensor, here implemented to be identical to
StressTensorEvaluation.stress_tensor_LW
var size_x
-
Width of the simulation area in micrometers
var size_y
-
Height of the simulation area in micrometers
var stress_tensor_LW
-
Love-Weber definition of the stress tensor (see for instance eq. 18 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression
var stress_tensor_internal_torque
-
Asymmetric stress tensor reflecting the purely internal interaction torques causing rotational acceleration of the particles. With restriction to the purely internal torques (vs. externally applied ones), this is the second last term of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.). We corrected here the expression given by Nicot et al. by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij) We also reverse the sign due to using the Soil mechanics convention
var stress_tensor_linear_acceleration
-
Linear acceleration stress tensor. Here, we neglect the effects of gravity, so the linear acceleration is linked solely to the unbalanced force (see for instance eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.). We consider therefore that
StressTensorEvaluation.stress_tensor_linear_acceleration
is equalStressTensorEvaluation.stress_tensor_unbalanced_forces
. Soil mechanics convention: normal is stress is positive for compression (which would lead to acceleration away from the center of the assembly) var stress_tensor_linear_acceleration_otsuki
-
Inertia compensation term given by eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. We do not use this term as compared to Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517, acceleration seems to be missing.
var stress_tensor_spin_kinetic_energy
-
Tensile stress tensor reflecting centrifugal forces from the rotation of the particles. This is the last of of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), Corrected here by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij).
We also reverse the sign due to using the Soil mechanics convention: normal is stress is positive for compression, so this is by definition negative for rotation rates different from 0
var stress_tensor_unbalanced_forces
-
Non-compensated acceleration stress tensor (see for instance eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression
var stress_tensor_unbalanced_torque
-
Asymmetric stress tensor reflecting the unbalanced torques causing rotational acceleration of the particles. This is the second last term of eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected here by replacing the 2/3 coefficient by a coefficient of 1 (we think there is an error in Nicot et al. regarding the inertia matrix: eq. 29 in Nicot et al. should read Xij=1/5.mp.rp^2.delta_ij=1/2.Jp.delta_ij instead of Xij=2/15.mp.rp^2.delta_ij=1/3.Jp.delta_ij).
We also reverse the sign due to using the Soil mechanics convention
var stress_tensor_with_external_forces
-
Force stress tensor ascribed to external force (see eq. 6 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.) Soil mechanics convention: normal is stress is positive for compression
var theCanvas
-
Reference to Tkinter canvas for drawing forces
var toDeleteList
-
To move lines on Tkinter displays, one needs to delete them and redraw them, this is to keep track of the currently active lines
Methods
def evaluate_externally_applied_shear_stress(self)
-
Evaluate the shear stress from the external forces acting on the ensemble
From the external force stress tensor (
StressTensorEvaluation.stress_tensor_with_external_forces
) calculated from the externally acting forces, evaluate the shear stress (xy element). To account for the fact that the force on an external measurement apparatus would be the inverse of the force applied on the boundary paraticles, we return the negative of the xy element of theStressTensorEvaluation.stress_tensor_with_external_forces
.Method defined in
StressTensorEvaluation
Expand source code
def evaluate_externally_applied_shear_stress(self): """Evaluate the shear stress from the external forces acting on the ensemble From the external force stress tensor (`particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`) calculated from the externally acting forces, evaluate the shear stress (xy element). To account for the fact that the force on an external measurement apparatus would be the inverse of the force applied on the boundary paraticles, we return the negative of the xy element of the `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`. Method defined in `particleShear.StressTensorEvaluation`""" return (-self.stress_tensor_with_external_forces[0][1])
def evaluate_force_stress_tensors(self, force_register)
-
Evaluate the stress tensors linked to forces
This method evaluates the terms
StressTensorEvaluation.stress_tensor_LW
,StressTensorEvaluation.stress_tensor_unbalanced_forces
andStressTensorEvaluation.stress_tensor_with_external_forces
from the forces and locations stored in the force_register argument (of typeForce_register
)The
StressTensorEvaluation.stress_tensor_LW
is evaluated from the internal forces acting between pairs of spheres. This information is stored in theForce_register.pair_register
field of theForce_register
. The sign convention is such that if the spheres are excerting each repulsive forces on each other, positive normal (diagonal) elements result inStressTensorEvaluation.stress_tensor_LW
. Since repulsion arises if the ensemble is compressed, this means that the sign convention associates positive normal stress with a compressive state. This is the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.)The
StressTensorEvaluation.stress_tensor_unbalanced_forces
is evaluated from the net forces acting on the individual internal spheres. This information is stored in theForce_register.total_particle_force_register
field of theForce_register
. The sign convention is such that if the spheres are being pushed away from the center of the assembly, positive normal (diagonal) elements result inStressTensorEvaluation.stress_tensor_unbalanced_forces
. Such net repulsion arises on a compressed, but not spatially restricted assembly; a typical situation would be free boundaries that allow the spheres to move away from ech other and a compressed initial assembly that then expands into the free available space. Since the sign convention followed here associates positive normal stress with a compressive state, this means that the Soil mechanics sign convention is followed (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.The
StressTensorEvaluation.stress_tensor_with_external_forces
is evaluated from the boundary forces acting through the domain boundaries or via spheres on the boundaries with imposed movement. This information is stored in theForce_register.external_force_register
field of theForce_register
. The sign convention is such that if the spheres are on the average pushed inward, positive normal (diagonal) elements result inStressTensorEvaluation.stress_tensor_with_external_forces
. Average inward forces mean that the assembly is kept in a compressive state by the external forces. Association between compressive state and positive normal stresses corresponds to the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.)There is a particularity when using Lees-Edwards boundary conditions and Sllod stabilization of motion in oscillatory shear experiments (i.e. the primary conditions used here and in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). The Sllod stabilization means that one considers only the excess momentum compared to the anticipated movement due to the general shearing of the assembly (see the peculiar momenta in Otsuki et al.; for the implementation of the shear speed change, see
CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change()
in classCanvasPointsBasicElasticityLeesEdwards
). This means that the inertial forces that would be needed to accelerate the overall assembly to the local shearing speeds are ignored. For low frequencies and amplitudes, this becomes negligeable, but at higher shear and frequencies, the simulation will not take into account a large part of the inertial forces, whereas a rheological measurement would, at least before correction for inertia.StressTensorEvaluation.stress_tensor_linear_acceleration
is set equal toStressTensorEvaluation.stress_tensor_unbalanced_forces
since we neglect gravity hereMethod defined in
StressTensorEvaluation
Expand source code
def evaluate_force_stress_tensors(self, force_register): """Evaluate the stress tensors linked to forces This method evaluates the terms `particleShear.StressTensorEvaluation.stress_tensor_LW`, `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` and `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces` from the forces and locations stored in the force_register argument (of type `particleShear.Force_register`) The `particleShear.StressTensorEvaluation.stress_tensor_LW` is evaluated from the internal forces acting between pairs of spheres. This information is stored in the `particleShear.Force_register.pair_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are excerting each repulsive forces on each other, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_LW`. Since repulsion arises if the ensemble is compressed, this means that the sign convention associates positive normal stress with a compressive state. This is the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.) The `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` is evaluated from the net forces acting on the individual internal spheres. This information is stored in the `particleShear.Force_register.total_particle_force_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are being pushed away from the center of the assembly, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces`. Such net repulsion arises on a compressed, but not spatially restricted assembly; a typical situation would be free boundaries that allow the spheres to move away from ech other and a compressed initial assembly that then expands into the free available space. Since the sign convention followed here associates positive normal stress with a compressive state, this means that the Soil mechanics sign convention is followed (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582. The `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces` is evaluated from the boundary forces acting through the domain boundaries or via spheres on the boundaries with imposed movement. This information is stored in the `particleShear.Force_register.external_force_register` field of the `particleShear.Force_register`. The sign convention is such that if the spheres are on the average pushed inward, positive normal (diagonal) elements result in `particleShear.StressTensorEvaluation.stress_tensor_with_external_forces`. Average inward forces mean that the assembly is kept in a compressive state by the external forces. Association between compressive state and positive normal stresses corresponds to the Soil mechanics sign convention (Fortin, J., O. Millet, and G. de Saxce, Construction of an averaged stress tensor for a granular medium. European Journal of Mechanics a-Solids, 2003. 22(4): p. 567-582.) There is a particularity when using Lees-Edwards boundary conditions and Sllod stabilization of motion in oscillatory shear experiments (i.e. the primary conditions used here and in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902.). The Sllod stabilization means that one considers only the excess momentum compared to the anticipated movement due to the general shearing of the assembly (see the peculiar momenta in Otsuki et al.; for the implementation of the shear speed change, see `particleShear.CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change` in class `particleShear.CanvasPointsBasicElasticityLeesEdwards` ). This means that the inertial forces that would be needed to accelerate the overall assembly to the local shearing speeds are ignored. For low frequencies and amplitudes, this becomes negligeable, but at higher shear and frequencies, the simulation will not take into account a large part of the inertial forces, whereas a rheological measurement would, at least before correction for inertia. `particleShear.StressTensorEvaluation.stress_tensor_linear_acceleration` is set equal to `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_forces` since we neglect gravity here Method defined in `particleShear.StressTensorEvaluation`""" self.stress_tensor_LW = [[0, 0], [0, 0]] self.stress_tensor_unbalanced_forces = [[0, 0], [0, 0]] self.stress_tensor_linear_acceleration = [[0, 0], [0, 0]] self.stress_tensor_with_external_forces = [[0, 0], [0, 0]] for pair in force_register.pair_register: target=pair[0] source=pair[1] force=pair[2] delta_x = [target.x-source.x, target.y-source.y] for i in range(2): for j in range(2): self.stress_tensor_LW[i][j] = self.stress_tensor_LW[i][j] + force[i]*1e-12 * delta_x[j] * 1e-6 / 2 for i in range(2): for j in range(2): self.stress_tensor_LW[i][j] = self.stress_tensor_LW[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) for single in force_register.total_particle_force_register: target = single[0] force = single[1] delta_x = [target.x-self.size_x/2 , target.y-self.size_y/2 ] for i in range(2): for j in range(2): self.stress_tensor_unbalanced_forces[i][j] = self.stress_tensor_unbalanced_forces[i][j] + force[i]*1e-12 * delta_x[j] * 1e-6 for i in range(2): for j in range(2): self.stress_tensor_unbalanced_forces[i][j] = self.stress_tensor_unbalanced_forces[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) if self.theCanvas: for theLine in self.toDeleteList: self.theCanvas.delete(theLine) for external_force in force_register.external_force_register: target = external_force[0] force = external_force[1] r = [target.x-self.size_x/2, target.y-self.size_y/2] if self.theCanvas and self.graphical_output_configuration.draw_boundary_forces: self.toDeleteList.append( self.theCanvas.create_line(target.x,target.y,target.x+force[0]*1000,target.y+force[1]*1000,fill="darkorchid")) for i in range(2): for j in range(2): self.stress_tensor_with_external_forces[i][j] = self.stress_tensor_with_external_forces[i][j]\ -force[i]*1e-12 * r[j] * 1e-6 for i in range(2): for j in range(2): self.stress_tensor_with_external_forces[i][j] = \ self.stress_tensor_with_external_forces[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6) for i in range(2): for j in range(2): # These are synonyms as we do not include gravity self.stress_tensor_linear_acceleration[i][j]=self.stress_tensor_unbalanced_forces[i][j]
def evaluate_internal_torque_stress_tensor(self, force_register)
-
Evaluate the stress tensor associated with internal unbalanced torques acting on the particle
The internal unbalanced torque stress tensor reflects the asymmetric contribution of torques arising from frictional particle-particle interaction strictly within the simulation area. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), restricted to the internal torques, and corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable
StressTensorEvaluation.stress_tensor_internal_torque
.We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.
Method defined in
StressTensorEvaluation
Expand source code
def evaluate_internal_torque_stress_tensor(self,force_register): """Evaluate the stress tensor associated with internal unbalanced torques acting on the particle The internal unbalanced torque stress tensor reflects the asymmetric contribution of torques arising from frictional particle-particle interaction strictly within the simulation area. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), restricted to the internal torques, and corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_internal_torque`.\n\n We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_internal_torque= [[0, 0], [0, 0]] # The spring constant is in mg/s-2/m of depth, and is multiplied with micrometers to get the forces # So the force have the units micrometers*mg/s-2/m # For the torque, this is further multiplied by micrometers, so that makes # micrometers^2/mg/s-2/m of depth for internal_torque in force_register.internal_moment_register: M = internal_torque[2]*1e-18 self.stress_tensor_internal_torque[0][1]=self.stress_tensor_internal_torque[0][1]-M/2 self.stress_tensor_internal_torque[1][0] =self.stress_tensor_internal_torque[1][0]+M/2 # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_internal_torque[i][j] = self.stress_tensor_internal_torque[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6)
def evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor(self, theSphereList, shear_rate)
-
Evaluate the inertial compensation term defined by Otsuki et al.
This term is the inertial compensation term (second righthand term) in eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. We provide this term for completeness. This term does not appear as such in other main references on shear tensors in granular media (Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), and it seems to have unphysical aspects. Consider a simplified assembly made from a single sphere without applied shear, moving linearly without applied force. All elements of the stress tensor should be zero for such a freely moving sphere free from any applied external force. If the speed of the sphere is not exactly along the x- or y-direction, the inertial compensation term by Otsuki et al. is non-zero, and this should not be. So while we provide the inertial term by Otsuki et al. for completeness, we do not recommend using it.
Method defined in
StressTensorEvaluation
Expand source code
def evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor(self,theSphereList,shear_rate): """ Evaluate the inertial compensation term defined by Otsuki et al. This term is the inertial compensation term (second righthand term) in eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. We provide this term for completeness. This term does not appear as such in other main references on shear tensors in granular media (Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), and it seems to have unphysical aspects. Consider a simplified assembly made from a single sphere without applied shear, moving linearly without applied force. All elements of the stress tensor should be zero for such a freely moving sphere free from any applied external force. If the speed of the sphere is not exactly along the x- or y-direction, the inertial compensation term by Otsuki et al. is non-zero, and this should not be. So while we provide the inertial term by Otsuki et al. for completeness, we do not recommend using it. Method defined in `particleShear.StressTensorEvaluation`""" self.stress_tensor_linear_acceleration_otsuki = [[0, 0], [0, 0]] for theSphere in theSphereList: # Units: forces are in N # the mass is in mg # the length scales are in micrometer # so here to kgm/s one needs to multiply by 10^-12 px=(theSphere.xspeed-shear_rate*(theSphere.y-self.size_y/2))*theSphere.m*1e-12 py=theSphere.yspeed*theSphere.m*1e-12 p=[px,py] for i in range(2): for j in range(2): # Eq. 15 of Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional # jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. # This doesn't seem to be quite right as it doesn't seem to involve acceleration self.stress_tensor_linear_acceleration_otsuki[i][j] = self.stress_tensor_linear_acceleration_otsuki[i][j]-\ p[i]*p[j]/theSphere.m*1e6 for i in range(2): for j in range(2): self.stress_tensor_linear_acceleration_otsuki[i][j] = \ self.stress_tensor_linear_acceleration_otsuki[i][j]/(self.size_x*1e-6)/(self.size_y*1e-6)
def evaluate_quasistatic_shear_stress(self)
-
Evaluate the internal shear stress
From the stress tensor (
StressTensorEvaluation.overall_stress_tensor
) calculated from the internal forces, evaluate the shear stress (xy element). Since an external measurement apparatus would have to provide a force opposing the shear stress force arising at the surface, we invert signs and return the negative of the xy element ofStressTensorEvaluation.overall_stress_tensor
.Method defined in
StressTensorEvaluation
Expand source code
def evaluate_quasistatic_shear_stress(self): """Evaluate the internal shear stress From the stress tensor (`particleShear.StressTensorEvaluation.overall_stress_tensor`) calculated from the internal forces, evaluate the shear stress (xy element). Since an external measurement apparatus would have to provide a force opposing the shear stress force arising at the surface, we invert signs and return the negative of the xy element of `particleShear.StressTensorEvaluation.overall_stress_tensor`. Method defined in `particleShear.StressTensorEvaluation`""" return (-self.overall_stress_tensor[0][1])
def evaluate_rotational_stress_tensors(self, force_register, theSphereList)
-
Evaluate the rotational stress tensors
This function invokes
StressTensorEvaluation.evaluate_spin_kinetic_energy_stress_tensor()
,StressTensorEvaluation.evaluate_unbalanced_torque_stress_tensor()
andStressTensorEvaluation.evaluate_internal_torque_stress_tensor()
. The results are stored in the instance variablesStressTensorEvaluation.stress_tensor_spin_kinetic_energy
,StressTensorEvaluation.stress_tensor_unbalanced_torque
andStressTensorEvaluation.stress_tensor_internal_torque
Method defined in
StressTensorEvaluation
Expand source code
def evaluate_rotational_stress_tensors(self,force_register,theSphereList): """Evaluate the rotational stress tensors This function invokes `particleShear.StressTensorEvaluation.evaluate_spin_kinetic_energy_stress_tensor`, `particleShear.StressTensorEvaluation.evaluate_unbalanced_torque_stress_tensor` and `particleShear.StressTensorEvaluation.evaluate_internal_torque_stress_tensor`. The results are stored in the instance variables `particleShear.StressTensorEvaluation.stress_tensor_spin_kinetic_energy`, `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_torque` and `particleShear.StressTensorEvaluation.stress_tensor_internal_torque` \n\n Method defined in `particleShear.StressTensorEvaluation` """ self.evaluate_spin_kinetic_energy_stress_tensor(theSphereList) self.evaluate_unbalanced_torque_stress_tensor(force_register) self.evaluate_internal_torque_stress_tensor(force_register)
def evaluate_spin_kinetic_energy_stress_tensor(self, theSphereList)
-
Evaluate the stress tensor associated with the centrifugal forces arising by rotation of the spheres around their axis
The tensile stress tensor reflects the centrifugal forces from the rotation of the particles. This is the spin kinetic term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable
StressTensorEvaluation.stress_tensor_spin_kinetic_energy
.We also reverse the sign due to using the Soil mechanics convention: normal is stress is positive for compression, so this is by definition negative for rotation rates different from 0
Method defined in
StressTensorEvaluation
Expand source code
def evaluate_spin_kinetic_energy_stress_tensor(self,theSphereList): """Evaluate the stress tensor associated with the centrifugal forces arising by rotation of the spheres around their axis The tensile stress tensor reflects the centrifugal forces from the rotation of the particles. This is the spin kinetic term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_spin_kinetic_energy`.\n\n We also reverse the sign due to using the Soil mechanics convention: normal is stress is positive for compression, so this is by definition negative for rotation rates different from 0\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_spin_kinetic_energy = [[0, 0], [0, 0]] for theSphere in theSphereList: # The moment of inertia is in mg*micrometers^2, but we need it in kg*m^2 here. The # unit conversion factor is therefore 10^(-18) moment_of_inertia = theSphere.inertia * 1e-18 K = theSphere.omega *theSphere.omega / 2 * moment_of_inertia for i in range(2): self.stress_tensor_spin_kinetic_energy[i][i] = self.stress_tensor_spin_kinetic_energy[i][i] - K # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_spin_kinetic_energy[i][j] = self.stress_tensor_spin_kinetic_energy[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6)
def evaluate_stress_tensors(self, force_register, theSphereList, shear_rate)
-
Evaluate the stress tensor expressions
This is the main method to be called for stress tensor evaluation. It uses the forces stored in the various fields of the force_register to evaluate the stress tensor expressions linked to forces via
StressTensorEvaluation.evaluate_force_stress_tensors()
. The method also callsStressTensorEvaluation.evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor()
, see these two methods for further details.Finally, the relevant overall internal force stress tensor (
StressTensorEvaluation.overall_stress_tensor
) is set equal to the Love-Weber expression. This intrinsically compensates for inertia by local acceleration, but it ignores the spin terms (rotation of the spheres and change in rotation rate). The approximation is justified by the quasistatic conditions used in the simulations, but may need improved implementation in the future for simulations with rapidly spinning ("rolling") particles.Method defined in
StressTensorEvaluation
Expand source code
def evaluate_stress_tensors(self,force_register,theSphereList,shear_rate): """Evaluate the stress tensor expressions This is the main method to be called for stress tensor evaluation. It uses the forces stored in the various fields of the force_register to evaluate the stress tensor expressions linked to forces via `particleShear.StressTensorEvaluation.evaluate_force_stress_tensors`. The method also calls `particleShear.StressTensorEvaluation.evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor`, see these two methods for further details. Finally, the relevant overall internal force stress tensor (`particleShear.StressTensorEvaluation.overall_stress_tensor`) is set equal to the Love-Weber expression. This intrinsically compensates for inertia by local acceleration, but it ignores the spin terms (rotation of the spheres and change in rotation rate). The approximation is justified by the quasistatic conditions used in the simulations, but may need improved implementation in the future for simulations with rapidly spinning ("rolling") particles. Method defined in `particleShear.StressTensorEvaluation`""" self.evaluate_force_stress_tensors(force_register) self.evaluate_peculiar_momentum_transport_for_linear_acceleration_tensor(theSphereList,shear_rate) self.evaluate_rotational_stress_tensors(force_register,theSphereList) self.overall_stress_tensor=[[0,0],[0,0]] for i in range(2): for j in range(2): # Compensation for linear acceleration as in Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On # the definition of the stress tensor in granular media. # International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517. # Also, taking into account only internal torque self.overall_stress_tensor[i][j] = self.overall_stress_tensor[i][j]+self.stress_tensor_LW[i][j] \ +self.stress_tensor_internal_torque[i][j]\ +self.stress_tensor_spin_kinetic_energy[i][j]
def evaluate_unbalanced_torque_stress_tensor(self, force_register)
-
Evaluate the stress tensor associated with overall unbalanced torques acting on the particle
The unbalanced torque stress tensor reflects the asymmetric contribution of torques leading to acceleration or deceleration of the rotation of the particles. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable
StressTensorEvaluation.stress_tensor_unbalanced_torque
.We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.
Method defined in
StressTensorEvaluation
Expand source code
def evaluate_unbalanced_torque_stress_tensor(self,force_register): """Evaluate the stress tensor associated with overall unbalanced torques acting on the particle The unbalanced torque stress tensor reflects the asymmetric contribution of torques leading to acceleration or deceleration of the rotation of the particles. This is the moment term in eq. 34 of Nicot, F., N. Hadda, M. Guessasma, J. Fortin, and O. Millet, On the definition of the stress tensor in granular media. International Journal of Solids and Structures, 2013. 50(14-15): p. 2508-2517.), corrected by replacing the 2/3 coefficient by a coefficient of 1. See instance variable `particleShear.StressTensorEvaluation.stress_tensor_unbalanced_torque`.\n\n We also reverse the sign due to using the Soil mechanics convention, implying sign reversal as compared to Nicot et al.\n\n Method defined in `particleShear.StressTensorEvaluation` """ self.stress_tensor_unbalanced_torque= [[0, 0], [0, 0]] # The spring constant is in mg/s-2/m of depth, and is multiplied with micrometers to get the forces # So the force have the units micrometers*mg/s-2/m # For the torque, this is further multiplied by micrometers, so that makes # micrometers^2/mg/s-2/m of depth for unbalanced_torque in force_register.unbalanced_moment_register: M = unbalanced_torque[1]*1e-18 self.stress_tensor_unbalanced_torque[0][1]=self.stress_tensor_unbalanced_torque[0][1]-M/2 self.stress_tensor_unbalanced_torque[1][0] =self.stress_tensor_unbalanced_torque[1][0]+M/2 # Now, with respect to the sampling volume for i in range(2): for j in range(2): self.stress_tensor_unbalanced_torque[i][j] = self.stress_tensor_unbalanced_torque[i][j] \ / (self.size_x * 1e-6) / (self.size_y * 1e-6)
class TensileConfiguration
-
Class to define alternative tensile force laws, not used by default
Expand source code
class TensileConfiguration(): """ Class to define alternative tensile force laws, not used by default""" d_exponential=0.2
Class variables
var d_exponential
class neighbor_relation (friction_position, interface_type, theSphere, graphical_line_index=-1)
-
Provide information on the relation to a neighboring spheres
Defined in subpackage particleShearBase
Expand source code
class neighbor_relation(): """Provide information on the relation to a neighboring spheres Defined in subpackage particleShearBase""" def __init__(self,friction_position,interface_type,theSphere,graphical_line_index=-1): self.myindex=0 """Identification among the neighbors of a given sphere""" if hasattr(theSphere, "myindex"): self.myindex=theSphere.myindex self.friction_position=friction_position """Relative movement position of the interface See Eq. 7 in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The friction position changes when relativement movement occurs for locked spheres to account for elastic interaction in the locked state""" self.interface_type=interface_type """State of the interface, should be "stick", "slip", or permanent (in `particleShear.neighbor_relation_linkable`) """ self.theSphere = theSphere """Reference to the neighboring sphere""" self.graphical_line_index=graphical_line_index """Index on the Tkinter canvas of the line corresponding to the interface"""
Subclasses
- particleShearLinkableObjects.neighbor_relation_linkable.neighbor_relation_linkable
Instance variables
var friction_position
-
Relative movement position of the interface
See Eq. 7 in Otsuki, M. and H. Hayakawa, Discontinuous change of shear modulus for frictional jammed granular materials. Phys Rev E, 2017. 95(6-1): p. 062902. The friction position changes when relativement movement occurs for locked spheres to account for elastic interaction in the locked state
var graphical_line_index
-
Index on the Tkinter canvas of the line corresponding to the interface
var interface_type
-
State of the interface, should be "stick", "slip", or permanent (in
neighbor_relation_linkable
) var myindex
-
Identification among the neighbors of a given sphere
var theSphere
-
Reference to the neighboring sphere
class neighbor_relation_linkable (friction_position, interface_type, theSphere, graphical_line_index=-1, linking_line_index=-1)
-
Provide information on the relation to a neighboring spheres
Defined in subpackage particleShearBase
Expand source code
class neighbor_relation_linkable(neighbor_relation): def __init__(self,friction_position,interface_type,theSphere,graphical_line_index=-1,linking_line_index=-1): super(neighbor_relation_linkable,self).__init__(friction_position,interface_type,theSphere, graphical_line_index=graphical_line_index) self.linking_line_index = linking_line_index # -1 is for no linking line yet self.equilibrium_distance=0
Ancestors
- particleShearBase.neighbor_relation.neighbor_relation
class particle_shear_model_parameters (size_x=500, size_y=500, N=100, packing_fraction=1, Young_modulus_spheres=10000, density=50, bimodal_factor=1)
-
Model class to facilitate calculation of particle and simulation parameters from physical properties of the constituent spheres
Class defined in subpackage particleShearObjects
Initialize model
Parameters <code>size\_x</code> Width of area to be used in pixels = micrometers for the simulation <code>size\_y</code> Height of area to be used in pixels = micrometers for the simulation <code>N</code> The number of spheres to place <code>packing\_fraction</code> The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y) <code>Young\_modulus\_spheres</code> The Young modulus of the constituent spheres in Pa `density of the spheres in kg/m^3
`
bimodal_factor
is ratio of radii of the smaller and larger spheres in the bimodal distributionExpand source code
class particle_shear_model_parameters(): """Model class to facilitate calculation of particle and simulation parameters from physical properties of the constituent spheres Class defined in subpackage particleShearObjects""" def __init__(self,size_x=500,size_y=500,N=100,packing_fraction=1,Young_modulus_spheres=10000,density=50, bimodal_factor=1): """Initialize model Parameters\n `size_x` Width of area to be used in pixels = micrometers for the simulation\n `size_y` Height of area to be used in pixels = micrometers for the simulation\n `N` The number of spheres to place\n `packing_fraction` The packing fraction indicating the density; this is defined as the area occupied by the non-compressed spheres as compared to the actual available area (size_x times size_y)\n `Young_modulus_spheres` The Young modulus of the constituent spheres in Pa\n `density of the spheres in kg/m^3\n` `bimodal_factor` is ratio of radii of the smaller and larger spheres in the bimodal distribution """ self.bimodal_factor=bimodal_factor """Bimodal factor indicating the ratio between the larger and the smaller spheres (bimodal size distribution)""" self.size_x=size_x """Width of the simulation area in micrometers (=Pixels) """ self.size_y=size_y """Height of the simulation area in micrometers (=Pixels) """ self.size_x_m=size_x/1e6 # Pixel units are micrometers """Width of the simulation area in meters """ self.size_y_m=size_y/1e6 # Pixel units are micrometers """Height of the simulation area in meters """ self.N=N """Number of spheres""" self.packing_fraction=packing_fraction """Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area""" self.r=r_estimate(self.size_x,self.size_y,self.packing_fraction,self.N, bimodal_upper=math.sqrt(bimodal_factor),bimodal_lower=1/math.sqrt(bimodal_factor)) """Average radius in micrometers. The radius is defined such that the larger radius would be sqrt(bimodal_factor)*radius and the smaller 1/sqrt(bimodal_factor)*radius. The expected packing fraction is conserved by adjusting for the increase in average cross section area resulting from the use of a bimodal distribution""" self.r_m=self.r/1e6 # Pixel units are micrometers """Average radius in meters""" self.m=math.pi*self.r_m*self.r_m*density*1e6 #This is per m of depth for the cylinders; the mass is converted to mg/m """Average mass per particle, in mg/m (cylindrical particles shown in cross section, mass per m of cylinder depth)""" self.Young_modulus_spheres = Young_modulus_spheres """Young modulus of the constituent spheres; this is of the material of which the spheres are made, in general, the ensemble would be softer""" self.density=density """Density of the particles, in kg/m^3""" self.k=math.pi/8*Young_modulus_spheres*1e6 """Spring constant, in mg/s^2/m; this relates the force per unit length to the indentation depth. The formula used is pi/8*Young_modulus spheres; this is eq. 5.34 from Popov, V.L, Contact Mechanics and Friction: Physical Principles and Applications, Berlin, Germany, 2017, 2nd edition (DOI 10.1007/978-3-662-53081-8), with an effective modulus given by E*=E/2 from eq. 5.26 also in Popov's contact mechanics book. We neglect here the effect of the Poisson coefficient (i.e. we assume Poisson coefficient=0), this introduces at worst 25% error""" self.time_constant=math.sqrt(self.m/self.k)/math.sqrt(max(bimodal_factor,1/bimodal_factor)) """Characteristic time constant sqrt(m/k) Here, the expressions of k and m in per m of depth cancels out Also, regarding a possible bimodal distribution (bimodal_factor>1), we err on the side of caution If a bimodal distribution is provided, then, the smaller radius will be r/sqrt(bimodal_factor), and so the mass will be m/bimodal_factor. That makes the time constant smaller, by sqrt(bimodal_factor). To be sure against indication of bimodal factor < 1, use the maximum of (bimodal_factor, 1/bimodal_factor) """
Instance variables
var N
-
Number of spheres
var Young_modulus_spheres
-
Young modulus of the constituent spheres; this is of the material of which the spheres are made, in general, the ensemble would be softer
var bimodal_factor
-
Bimodal factor indicating the ratio between the larger and the smaller spheres (bimodal size distribution)
var density
-
Density of the particles, in kg/m^3
var k
-
Spring constant, in mg/s^2/m; this relates the force per unit length to the indentation depth.
The formula used is pi/8Young_modulus spheres; this is eq. 5.34 from Popov, V.L, Contact Mechanics and Friction: Physical Principles and Applications, Berlin, Germany, 2017, 2nd edition (DOI 10.1007/978-3-662-53081-8), with an effective modulus given by E=E/2 from eq. 5.26 also in Popov's contact mechanics book. We neglect here the effect of the Poisson coefficient (i.e. we assume Poisson coefficient=0), this introduces at worst 25% error
var m
-
Average mass per particle, in mg/m (cylindrical particles shown in cross section, mass per m of cylinder depth)
var packing_fraction
-
Packing fraction: relative area occupied by the uncompressed spheres as compared to the available area
var r
-
Average radius in micrometers.
The radius is defined such that the larger radius would be sqrt(bimodal_factor)radius and the smaller 1/sqrt(bimodal_factor)radius. The expected packing fraction is conserved by adjusting for the increase in average cross section area resulting from the use of a bimodal distribution
var r_m
-
Average radius in meters
var size_x
-
Width of the simulation area in micrometers (=Pixels)
var size_x_m
-
Width of the simulation area in meters
var size_y
-
Height of the simulation area in micrometers (=Pixels)
var size_y_m
-
Height of the simulation area in meters
var time_constant
-
Characteristic time constant sqrt(m/k)
Here, the expressions of k and m in per m of depth cancels out Also, regarding a possible bimodal distribution (bimodal_factor>1), we err on the side of caution If a bimodal distribution is provided, then, the smaller radius will be r/sqrt(bimodal_factor), and so the mass will be m/bimodal_factor. That makes the time constant smaller, by sqrt(bimodal_factor). To be sure against indication of bimodal factor < 1, use the maximum of (bimodal_factor, 1/bimodal_factor)