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 particles

    d0 Equilibrium center-center distance

    k Spring constant

    central_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 particles

    d0 Equilibrium center-center distance

    k Spring constant

    central_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 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

sqrt(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 in CanvasPoints.sphereList.

Class defined in subpackage particleShearBase

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    doDrawing 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 type Point 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 canvas

To be overriden by subclasses of CanvasPoints with special boundary conditions

This 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 from PointLeesEdwards or otherwise present a cool method

This 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 from CircleMass or otherwise possess a do_linear_acceleration method

This 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 True

This 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 by CanvasPoints.size_x * CanvasPoints.size_y

  • parameters

    rand_fraction allows local random fluctuations of the sphere positions around the grid positions

    This 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 type CircleBasicElasticity or derived and adds basic functions to handle this collection

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    doDrawing Flag to indicate whether graphical output should be produced or not

    k 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 compression

    nu Viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

    m 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 via CanvasPointsNeighbors.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 via CanvasPointsNeighbors.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 update

This 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 type CircleBasicElasticity or derived and adds functions specific to Lees-Edwards boundary conditions

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    doDrawing Flag to indicate whether graphical output should be produced or not

    k 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 compression

    nu Viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

    m 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 type CircleFrictionElasticity or derived and adds functions for handling friction and associated non-central forces

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

    nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

    m is the mass per unit of depth in mg/m of depth

    k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

    nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

    mu 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 method do_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 type CircleFrictionElasticityor a subclass thereof, or otherwise possess a method tangential_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 via CanvasPointsNeighbors.test_neighbor_relation(). For this function to work, the spheres must be of type CircleFrictionElasticityor a subclass thereof, or otherwise possess a method tangential_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 type SphereLinkable and adds elementary functions related to crosslinking

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

    nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

    m is the mass per unit of depth in mg/m of depth

    k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

    nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

    mu 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 line

    angle Angle of the cut line, in radians; 0=vertical

    Method 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. See CanvasPoints.sphereList of the base class CanvasPoints.

Initialize self

  • parameters

size_x Width of area to be used in pixels = micrometers for the simulation

size_y Height of area to be used in pixels = micrometers for the simulation

theCanvas possibility to transmit a tkinter Canvas object for graphical output

doDrawing Flag to indicate whether graphical output should be produced or not

m is the mass per unit of depth in mg/m of depth

Expand 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 to CanvasPointsMass.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 to CanvasPointsMass.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 type CircleMassNeighbors or derived and adds basic functions to handle this collection

Initialize self

  • parameters

size_x Width of area to be used in pixels = micrometers for the simulation

size_y Height of area to be used in pixels = micrometers for the simulation

theCanvas possibility to transmit a tkinter Canvas object for graphical output

doDrawing Flag to indicate whether graphical output should be produced or not

m is the mass per unit of depth in mg/m of depth

Expand 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 the CanvasPoints.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 type CircleMassNeighbors or derived and adds specific functionality linked to shear under Lees-Edwards boundary conditions

Initialize self

  • parameters

size_x Width of area to be used in pixels = micrometers for the simulation

size_y Height of area to be used in pixels = micrometers for the simulation

theCanvas possibility to transmit a tkinter Canvas object for graphical output

doDrawing Flag to indicate whether graphical output should be produced or not

m is the mass per unit of depth in mg/m of depth

Expand 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 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 CanvasPointsShear.applyingShear is set True

This 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 class CanvasPoints), this method imposes the local speed given by the current CanvasPointsShear.shear_rate at the top and lower boundary to the immobile spheres

This 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 the PointLeesEdwards.use_lees_edwards field in the target sphere to False

This 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 provided PointLeesEdwards.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 desired

doDrawing Explicit switch to turn of drawing

use_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 in Circle.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 uses Circle.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 provided PointLeesEdwards.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 diameter diameter in micrometers

theCanvas Tkinter canvas; provide False if no Canvas is available or no drawing is desired

doDrawing Explicit switch to turn of drawing

force_registerPossibility to provide a reference to a Force_register variable to register the forces calculated during simulation

use_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 particles

    d0 Equilibrium center-center distance

    k Spring constant

    central_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 provided PointLeesEdwards.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 diameter diameter in micrometers

theCanvas Tkinter canvas; provide False if no Canvas is available or no drawing is desired

doDrawing Explicit switch to turn of drawing

force_registerPossibility to provide a reference to a Force_register variable to register the forces calculated during simulation

use_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 in Circle.shape. Compared to its parent method ( Circle.initiate_drawing() defined in Circle), here, a line indicating the rotational position CircleFrictionElasticity.phi is optionnally added. This method is defined in class CircleFrictionElasticity

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 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.

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 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.

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 in Circle. 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 and CircleMass.yforce) are reset to 0; as the spheres are meant to be associated with a simulation area object derived from CanvasPointsMass, this typically happens through a call to the method CanvasPointsMass.reset_force() of 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 CircleMass.force_register, which is provided through the canvas class (derived from 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 CanvasPointsMass.reset_force_register() of CanvasPointsMass 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 and CircleMass.yforce. Depending on their nature, they are also registered in various ways in the common Force_register object, which is the instance variable CanvasPointsMass.force_register of the relevant subclass of CanvasPointsMass; for practical purposes, this very same Force_register is also known to each sphere as its own instance variable CircleMass.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, the 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 StressTensorEvaluation object. The CircleMass.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 provided PointLeesEdwards.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 depth

theCanvas Tkinter canvas; provide False if no Canvas is available or no drawing is desired

doDrawing Explicit switch to turn of drawing

force_registerPossibility to provide a reference to a common Force_register variable to register the forces calculated during simulation

use_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 the register_homolog parameter is provided as True, then the opposing equal force is also registered in the 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.

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 0

This 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 class

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 depth

theCanvas Tkinter canvas; provide False if no Canvas is available or no drawing is desired

doDrawing Explicit switch to turn of drawing

force_registerPossibility to provide a reference to a common Force_register variable to register the forces calculated during simulation

use_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 variable

This 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 conditions

This class creates and maintains a list of objects of type SphereFriction. These objects are generically referred to as spheres and are stored in CanvasPoints.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 simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

Expand 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 the 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.

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 particles

This class maintains a list of objects of type SphereLinkable and adds specific functions to the generation of the particles

Initialize self

  • parameters

size_x Width of area to be used in pixels = micrometers for the simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

mu Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu

dt Time step for a single simulation step dt_max Maximal time step used during pre-equilibration, can be larger than dt

theTkSimulation Tk master object associated with the CanvasPoints.theCanvas used to trigger automatic update of the canvas in a simulation setting

Expand 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))
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 particles

This class maintains a list of objects of type SphereLinkable and adds specific functions to the generation of the particles

Initialize self

  • parameters

size_x Width of area to be used in pixels = micrometers for the simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

mu Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu

dt Time step for a single simulation step dt_max Maximal time step used during pre-equilibration, can be larger than dt

theTkSimulation Tk master object associated with the CanvasPoints.theCanvas used to trigger automatic update of the canvas in a simulation setting

Expand 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 conditions

This class creates and maintains a list of objects of type SphereFriction. These objects are generically referred to as spheres and are stored in CanvasPoints.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 simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the average mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

mu Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu

Expand 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 method do_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 type SphereFrictionor a subclass thereof, or otherwise possess a method tangential_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 via CanvasPointsNeighbors.test_neighbor_relation(). For this function to work, the spheres must be of type SphereFrictionor a subclass thereof, or otherwise possess a method tangential_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 conditions

This class creates and maintains a list of objects of type SphereFrictionLeesEdwards. These objects are generically referred to as spheres and are stored in CanvasPoints.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 simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the average mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

mu Friction coefficient, describes the maximum interface force for non-permanent interfaces by F_tangential_max = F_central*mu

Expand 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 conditions

This class creates and maintains a list of objects of type SphereLeesEdwards. These objects are generically referred to as spheres and are stored in CanvasPoints.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 simulation

size_y Height of area to be used in pixels = micrometers for the simulation

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

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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

m is the average mass per unit of depth in mg/m of depth

bimodal_factor is ratio of radii of the smaller and larger spheres in the bimodal distribution

Expand 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 type SphereLinkable and adds elementary functions related to crosslinking

Initialize self

  • parameters

    size_x Width of area to be used in pixels = micrometers for the simulation

    size_y Height of area to be used in pixels = micrometers for the simulation

    theCanvas possibility to transmit a tkinter Canvas object for graphical output

    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=-kxL with L the depth of the stack (the simulation is 2D) and x the compression

    nu Central viscosity force constant in (mg/s)/m of depth. This is to have F=nuvL, L again the depth

    m is the mass per unit of depth in mg/m of depth

    k_t Tangential spring constant in (mg/s^2)/m of depth; relevant for locked or permanent interfaces but not for slipping interfaces

    nu_t Central viscosity force constant in (mg/s)/m of depth. Relevant for frictionally locked and permanent interfaces but not slipping ones

    mu 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 (class Circle 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 locked neighbor_relations

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 locked neighbor_relations

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_relations 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.Pointor derived

The 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 of particleShearBase.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 of particleShearBase.Point.d or particleShearBase.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 simulation

size_y Height of area to be used in pixels = micrometers for the simulation

shear 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 using PointLeesEdwards.lee_edwards_boundary_conditions() if PointLeesEdwards.use_lees_edwards is set to True

This 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(). If PointLeesEdwards.use_lees_edwards is False, the parent method Point.d() of class Point 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. If PointLeesEdwards.use_lees_edwards is False, the parent method Point.n() of class Point 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 uses PointLeesEdwards.lee_edwards_relative_speed(); otherwise, it uses Point.relative_speed() defined in the parent class Point

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 the PointLeesEdwards.use_lees_edwards field in the target sphere to False

This 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 conditions

Constructor 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 conditions

Constructor as CircleFrictionElasticity in CircleFrictionElasticity but with and indexing element (my_index) and with the parameter use_lees_edwards set to False

Expand 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 conditions

Constructor as CircleFrictionElasticity in CircleFrictionElasticity but with and indexing element (my_index) and with the parameter use_lees_edwards set to True

Expand 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 conditions

Constructor 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 conditions

Constructor as CircleFrictionElasticity in CircleFrictionElasticity but with and indexing element (my_index) and with the parameter use_lees_edwards set to True

Expand 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 variable

This 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
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]])
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 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.

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 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.

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 conditions

Constructor as CircleFrictionElasticity in CircleFrictionElasticity but with and indexing element (my_index) and with the parameter use_lees_edwards set to True

Expand 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 variable

This 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 variables

Defined 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 equal 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)

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 the StressTensorEvaluation.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 and StressTensorEvaluation.stress_tensor_with_external_forces from the forces and locations stored in the force_register argument (of type Force_register)

The StressTensorEvaluation.stress_tensor_LW is evaluated from the internal forces acting between pairs of spheres. This information is stored in the Force_register.pair_register field of the 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 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 StressTensorEvaluation.stress_tensor_unbalanced_forces is evaluated from the net forces acting on the individual internal spheres. This information is stored in the Force_register.total_particle_force_register field of the 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 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 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 Force_register.external_force_register field of the Force_register. The sign convention is such that if the spheres are on the average pushed inward, positive normal (diagonal) elements result in 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 CanvasPointsBasicElasticityLeesEdwards.adjust_sphere_speed_to_shear_rate_change() in class 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.

StressTensorEvaluation.stress_tensor_linear_acceleration is set equal to StressTensorEvaluation.stress_tensor_unbalanced_forces since we neglect gravity here

Method 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 of StressTensorEvaluation.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)
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 calls StressTensorEvaluation.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 distribution

Expand 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)