Module forcefield

Synopsis of forcefield Class

The forcefield class defines the core behavior for managing inter-particle interactions in molecular dynamics simulations or similar physics-based models. It provides methods to calculate interaction parameters, including pair styles, diagonal pair coefficients, and off-diagonal pair coefficients, which are crucial for forcefield models.

Key Attributes:

  • PAIR_STYLE (str): Defines the pair style command for the forcefield interactions.
  • PAIR_DIAGCOEFF (str): Defines the command for calculating diagonal pair coefficients.
  • PAIR_OFFDIAGCOEFF (str): Defines the command for calculating off-diagonal pair coefficients.
  • parameters (parameterforcefield): Stores the parameters used for interaction evaluations.
  • beadtype (int): The bead type used in the forcefield model.
  • userid (str): A unique identifier for the forcefield instance.

Key Methods:

  • pair_style(printflag=True): Returns the pair style command based on the current parameters, beadtype, and userid.
  • pair_diagcoeff(printflag=True, i=None): Returns the diagonal pair coefficient command based on the current bead type i and userid. The bead type can be overridden.
  • pair_offdiagcoeff(o=None, printflag=True, i=None): Returns the off-diagonal pair coefficient command for interactions between two bead types or forcefield objects. The bead type i and the interacting forcefield o can be specified or overridden.

    — forcefield methods for LAMMPS —

    The superclass provides a collection of classes to define materials and forcefields. Note that the following hierarchy is used: > forcefield() is the top class (to be called directly) > customff(forcefield) defines a new forcefield so called customff > customstyle(customff) defines a pair-style applicable to customff > custommaterial(customstyle) defines a new material

    — Material library (first implementations) —

    w = water(beadtype=1, userid="fluid")
    w.parameters.Cp = 20
    print("
    

    "*2,w)

    f = solidfood(beadtype=2, userid="elastic")
    print("
    

    "*2,f)

    r = rigidwall(beadtype=3, userid="wall")
    print("
    

    "*2,r)

    — Template to define a material —

      class mymaterial(myforcefield):
          userid = "short name"
          version = value
          def __init__(self, beadtype=1, userid=None):
              super().__init__()
              if userid!=None: self.userid = userid
              self.name.material"] = "short of the material"
              self.description.material"] = "full description"
              self.beadtype = beadtype
              self.parameters = parameterforcefield(
                  param1 = value1,
                  param2 = value2,
                  param3 = "math expression with ${param1, ${param2}"
              )
    

    — Example of outputs | LAMMPS:SMD:tlsph:solidfood —

    ========================== [ elastic | version=0.1 ] ===========================
    
      Bead of type 2 = [LAMMPS:SMD:tlsph:solidfood]
      -----------------:----------------------------------------
                    rho: 1000
                     c0: 10.0
                      E: 5*${c0}^2*${rho}
                     nu: 0.3
                     q1: 1.0
                     q2: 0.0
                     Hg: 10
                     Cp: 1.0
            sigma_yield: 0.1*${E}
              hardening: 0
          contact_scale: 1.5
      contact_stiffness: 2.5*${c0}^2*${rho}
      -----------------:----------------------------------------
    forcefield object with 12 parameters
    
    ............................... [ description ] ................................
    
            #       LAMMPS:SMD - solid, liquid, rigid forcefields (continuum mechanics)
            #       SMD:TLSPH - total Lagrangian for solids
            #       food beads - solid behavior
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ methods ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    replace FFi,FFj by your variable names <<<
            To assign a type, use: FFi.beadtype = integer value
            Use the methods FFi.pair_style() and FFi.pair_coeff(FFj)
            Note for pairs: the caller object is i (FFi), the argument is j (FFj or j)
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ template ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
        # [2:elastic] PAIR STYLE SMD
        pair_style      hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION &
                                       smd/tlsph smd/hertz 1.5
    
        # [2:elastic x 2:elastic] Diagonal pair coefficient tlsph
        pair_coeff      2 2 smd/tlsph *COMMON 1000 500000.0 0.3 1.0 0.0 10 1.0 &
                        *STRENGTH_LINEAR_PLASTIC 50000.0 0 &
                        *EOS_LINEAR &
                        *END
    
        # [2:elastic x 1:none] Off-diagonal pair coefficient (generic)
        pair_coeff      2 1 smd/hertz 250000.0
    
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    run this code by pressing

Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Synopsis of forcefield Class
============================

The `forcefield` class defines the core behavior for managing inter-particle interactions
in molecular dynamics simulations or similar physics-based models. It provides methods
to calculate interaction parameters, including pair styles, diagonal pair coefficients,
and off-diagonal pair coefficients, which are crucial for forcefield models.

Key Attributes:
---------------
- `PAIR_STYLE` (str): Defines the pair style command for the forcefield interactions.
- `PAIR_DIAGCOEFF` (str): Defines the command for calculating diagonal pair coefficients.
- `PAIR_OFFDIAGCOEFF` (str): Defines the command for calculating off-diagonal pair coefficients.
- `parameters` (parameterforcefield): Stores the parameters used for interaction evaluations.
- `beadtype` (int): The bead type used in the forcefield model.
- `userid` (str): A unique identifier for the forcefield instance.

Key Methods:
------------
- `pair_style(printflag=True)`: Returns the pair style command based on the current
  `parameters`, `beadtype`, and `userid`.
- `pair_diagcoeff(printflag=True, i=None)`: Returns the diagonal pair coefficient command
  based on the current bead type `i` and `userid`. The bead type can be overridden.
- `pair_offdiagcoeff(o=None, printflag=True, i=None)`: Returns the off-diagonal pair
  coefficient command for interactions between two bead types or forcefield objects.
  The bead type `i` and the interacting forcefield `o` can be specified or overridden.



    --- forcefield methods for LAMMPS ---

    The superclass provides a collection of classes to define materials
    and forcefields. Note that the following hierarchy is used:
        > forcefield() is the top class (to be called directly)
        > customff(forcefield) defines a new forcefield so called customff
        > customstyle(customff) defines a pair-style applicable to customff
        > custommaterial(customstyle) defines a new material


    --- Material library (first implementations) ---

        w = water(beadtype=1, userid="fluid")
        w.parameters.Cp = 20
        print("\n"*2,w)

        f = solidfood(beadtype=2, userid="elastic")
        print("\n"*2,f)

        r = rigidwall(beadtype=3, userid="wall")
        print("\n"*2,r)


    --- Template to define a material ---

          class mymaterial(myforcefield):
              userid = "short name"
              version = value
              def __init__(self, beadtype=1, userid=None):
                  super().__init__()
                  if userid!=None: self.userid = userid
                  self.name.material"] = "short of the material"
                  self.description.material"] = "full description"
                  self.beadtype = beadtype
                  self.parameters = parameterforcefield(
                      param1 = value1,
                      param2 = value2,
                      param3 = "math expression with ${param1, ${param2}"
                  )


    --- Example of outputs | LAMMPS:SMD:tlsph:solidfood ---

        ========================== [ elastic | version=0.1 ] ===========================

          Bead of type 2 = [LAMMPS:SMD:tlsph:solidfood]
          -----------------:----------------------------------------
                        rho: 1000
                         c0: 10.0
                          E: 5*${c0}^2*${rho}
                         nu: 0.3
                         q1: 1.0
                         q2: 0.0
                         Hg: 10
                         Cp: 1.0
                sigma_yield: 0.1*${E}
                  hardening: 0
              contact_scale: 1.5
          contact_stiffness: 2.5*${c0}^2*${rho}
          -----------------:----------------------------------------
        forcefield object with 12 parameters

        ............................... [ description ] ................................

                #       LAMMPS:SMD - solid, liquid, rigid forcefields (continuum mechanics)
                #       SMD:TLSPH - total Lagrangian for solids
                #       food beads - solid behavior

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ methods ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        replace FFi,FFj by your variable names <<<
                To assign a type, use: FFi.beadtype = integer value
                Use the methods FFi.pair_style() and FFi.pair_coeff(FFj)
                Note for pairs: the caller object is i (FFi), the argument is j (FFj or j)

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [ template ] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


            # [2:elastic] PAIR STYLE SMD
            pair_style      hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION &
                                           smd/tlsph smd/hertz 1.5


            # [2:elastic x 2:elastic] Diagonal pair coefficient tlsph
            pair_coeff      2 2 smd/tlsph *COMMON 1000 500000.0 0.3 1.0 0.0 10 1.0 &
                            *STRENGTH_LINEAR_PLASTIC 50000.0 0 &
                            *EOS_LINEAR &
                            *END


            # [2:elastic x 1:none] Off-diagonal pair coefficient (generic)
            pair_coeff      2 1 smd/hertz 250000.0


        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    run this code by pressing <F5>

"""

__project__ = "Pizza3"
__author__ = "Olivier Vitrac"
__copyright__ = "Copyright 2022"
__credits__ = ["Olivier Vitrac"]
__license__ = "GPLv3"
__maintainer__ = "Olivier Vitrac"
__email__ = "olivier.vitrac@agroparistech.fr"
__version__ = "0.9971"


# INRAE\Olivier Vitrac - rev. 2022-10-10
# contact: olivier.vitrac@agroparistech.fr

# History
# 2022-02-12 early version
# 2022-02-13 release candidate
# 2022-02-20 made compatible with the update private.mstruct.py
# 2022-02-28 fix class inheritance with mutable type, update is carried with + and struct()
# 2022-03-02 fix off-diagonal order for i,j
# 2022-03-19 standardized pizza path
# 2022-04-16 add saltTLSPH() forcefield in the material library, and document it better
# 2022-05-16 force sortdefintions for + and += with parameterforcefield()
# 2022-05-17 direct use of pizza.private.mstruct.paramauto()
# 2023-07-25 fix forcefield (deepduplicate instead of duplicate)
# 2024-09-10 updated documentation for pizza.forcefield (to be read along with pizza.dforcefield)
# 2024-09-12 upgrading of parameterforcefield
# 2024-09-21 dforcefield and forcefield can be combined indifferently (no precedence)
# 2024-10-10 fix the dynamic parameterization with dforcefield (more overrides)

# %% Dependencies
import types
# All forcefield parameters are stored à la Matlab in a structure
from pizza.private.mstruct import struct,paramauto

__all__ = ['forcefield', 'none', 'paramauto', 'parameterforcefield', 'rigidwall', 'saltTLSPH', 'smd', 'solidfood', 'struct', 'tlsph', 'ulsph', 'water']


# %% Parent class (not to be called directly)
# Note that some attributes are stored in the instances but in the class itself
#   recommendation 1: Recreate/derive a class when possible
#   recommendation 2: __dict__ list only properties in the instance,
#                     use getallattributes() to see all attributes

# container of forcefield parameters
class parameterforcefield(paramauto):
    """ class of forcefields parameters, derived from param
        note that conctanating two forcefields force them
        to to be sorted
    """
    _type = "FF"
    _fulltype = "forcefield"
    _ftype = "parameter"
    _maxdisplay = 80

    # same strategy as used in dscript for forcing  _returnerror = False (added 2024-09-12)
    def __init__(self, _protection=False, _evaluation=True, sortdefinitions=False, **kwargs):
        """
        Constructor for parameterforcefield. It forces the parent's _returnerror parameter to False.

        Parameters:
        -----------
        _protection : bool, optional
            Whether to enable protection on the parameters (default: False).
        _evaluation : bool, optional
            Whether evaluation is enabled for the parameters (default: True).
        sortdefinitions : bool, optional
            Whether to sort definitions upon initialization (default: False).
        **kwargs : dict
            Additional keyword arguments for the parent class.
        """
        # Call the parent class constructor
        super().__init__(_protection=_protection, _evaluation=_evaluation, sortdefinitions=sortdefinitions, **kwargs)
        # Override the _returnerror attribute at the instance level
        self._returnerror = False


# core class
class forcefield():
    """
    The `forcefield` class represents the core implementation of a forcefield model,
    defining interaction parameters and coefficients for simulations. This class provides
    methods to handle pair styles, diagonal pair coefficients, and off-diagonal pair coefficients,
    which are essential for simulating inter-particle interactions in molecular dynamics or
    other physics-based simulations.

    Attributes:
    -----------
    PAIR_STYLE : str
        The default pair style command for the forcefield interactions.

    PAIR_DIAGCOEFF : str
        The default command for calculating diagonal pair coefficients.

    PAIR_OFFDIAGCOEFF : str
        The default command for calculating off-diagonal pair coefficients.

    parameters : parameterforcefield
        An instance of `parameterforcefield` that stores the parameters for
        evaluating interaction commands.

    beadtype : int
        The bead type associated with the current forcefield instance.

    userid : str
        A unique identifier for the forcefield instance, used in interaction commands.

    Methods:
    --------
    pair_style(printflag=True):
        Generate and return the pair style command based on the current parameters,
        beadtype, and userid.

    pair_diagcoeff(printflag=True, i=None):
        Generate and return the diagonal pair coefficients based on the current parameters,
        beadtype, and userid. The bead type `i` can be overridden with an optional argument.

    pair_offdiagcoeff(o=None, printflag=True, i=None):
        Generate and return the off-diagonal pair coefficients between two different
        bead types or forcefield objects. The bead type `i` can be overridden, and the
        interaction with another forcefield object `o` can also be specified.

    Notes:
    ------
    - This class is intended to be extended by specific forcefield types such as `ulsph`.
    - The parameters used in the interaction commands are dynamically evaluated using
      the `parameterforcefield` class, which provides the required values during runtime.
    """

    # Main attributes (instance independent)
    name = struct(forcefield="undefined", style="undefined", material="undefined")
    description = struct(forcefield="missing", style="missing", material="missing")
    beadtype = 1  # default bead type
    parameters = parameterforcefield() # empty parameters object
    userid = "undefined"
    version = 0

    # print method for headers (static, no implicit argument)
    @staticmethod
    def printheader(txt,align="^",width=80,filler="~"):
        """ print header """
        if txt=="":
            print("\n"+filler*(width+6)+"\n")
        else:
            print(("\n{:"+filler+"{align}{width}}\n").format(' [ '+txt+' ] ', align=align, width=str(width)))

    # Display/representation method
    # The method provides full help for the end-user
    def __repr__(self):
        """ disp method """
        stamp = self.name.forcefield+":"+self.name.style+":"+self.name.material
        self.printheader("%s | version=%0.3g" % (self.userid,self.version),filler="=")
        print("  Bead of type %d = [%s]" % (self.beadtype,stamp))
        print(self.parameters)
        self.printheader("description",filler=".")
        print("\t# \t%s" % self.description.forcefield)
        print("\t# \t%s" % self.description.style)
        print("\t# \t%s" % self.description.material)
        self.printheader("methods")
        print("\t   >>> replace FFi,FFj by your variable names <<<")
        print("\tTo assign a type, use: FFi.beadtype = integer value")
        print("\tUse the methods FFi.pair_style() and FFi.pair_coeff(FFj)")
        print("\tNote for pairs: the caller object is i (FFi), the argument is j (FFj or j)")
        self.printheader("template")
        self.pair_style()
        self.pair_diagcoeff()
        self.pair_offdiagcoeff()
        self.printheader("")
        return stamp

    # Extract attributes within the class
    def getallattributes(self):
        """ advanced method to get all attributes including class ones"""
        return {k: getattr(self, k) for k in dir(self) \
                if (not k.startswith('_')) and (not isinstance(getattr(self, k),types.MethodType))}


    # Forcefield Methods: pair_style(), pair_coeff()
    # the substitution of LAMMPS variables is carried out with the method
    # parameters.format() method implemented in struct and inherited by parameterforcefield()
    def pair_style(self,printflag=False,verbose=True, raw=False,USER=None,beadtype=None,userid=None):
        """
        Generate and return the pair style command for the current forcefield instance.

        This method creates a formatted pair style command based on the interaction parameters
        stored in the `parameters` attribute. It allows customization of the command using the
        `beadtype` and `userid` arguments. The behavior can be altered by passing a `USER` object
        or opting for the raw command template.

        Parameters:
        -----------
        printflag : bool, optional, default=False
            If True, the generated pair style command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        raw : bool, optional, default=False
            If True, returns the raw template of the pair style without any interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier used in the generated command. If not provided, the
            instance's beadtype is used.
        userid : str, optional, default=None
            The user identifier to include in the formatted command. Defaults to the instance's
            userid if not specified.

        Returns:
        --------
        str
            The formatted pair style command string.

        Raises:
        -------
        TypeError
            If `USER` is provided but is not of type `struct` or derived from `struct`.
        """
        # raw format
        if raw:
            return self.PAIR_STYLE
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # cmd
        cmd = parameters.formateval(self.PAIR_STYLE)
        # Replace [comment] with the formatted comment (e.g., "[2:my_user_id]")
        cmd = cmd.replace("[comment]","[%d:%s]" % (beadtype, userid) if verbose else "")
        if printflag: print(cmd)
        return cmd


    def pair_diagcoeff(self,printflag=False,verbose=True, i=None,raw=False,USER=None,beadtype=None,userid=None):
        """
        Generate and return the diagonal pair coefficients for the current forcefield instance.

        This method evaluates the diagonal pair coefficients based on the interaction parameters,
        the bead type (`beadtype`), and the user identifier (`userid`). The bead type `i` can
        be overridden by passing it as an argument. The method supports returning the raw template
        without evaluation and modifying parameters using a `USER` object.

        Parameters:
        -----------
        printflag : bool, optional, default=False
            If True, the generated diagonal pair coefficient command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        i : int, optional, default=None
            The bead type used for evaluating the diagonal pair coefficients. If not provided,
            defaults to the instance's bead type (`self.beadtype`).
        raw : bool, optional, default=False
            If True, returns the raw template for the diagonal pair coefficients without interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier to use in the command. Defaults to the instance's beadtype
            if not provided.
        userid : str, optional, default=None
            The user identifier to include in the formatted command. Defaults to the instance's
            userid if not specified.

        Returns:
        --------
        str
            The formatted diagonal pair coefficient command string.

        Raises:
        -------
        TypeError
            If `USER` is provided but is not of type `struct` or derived from `struct`.
        """
        # raw format
        if raw:
            return self.PAIR_DIAGCOEFF
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # diagonal index
        i = i if i is not None else beadtype
        # cmd
        cmd = parameters.formateval(self.PAIR_DIAGCOEFF) % (i,i)
        # Replace [comment] with the formatted string, without using .format()
        cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, userid, i, userid) if verbose else "")
        if printflag: print(cmd)
        return cmd


    def pair_offdiagcoeff(self,o=None,printflag=False,verbose=True,i=None,raw=False,USER=None,beadtype=None,userid=None,oname=None):
        """
        Generate and return the off-diagonal pair coefficients for the current forcefield instance.

        This method evaluates the off-diagonal pair coefficients between two different bead types
        or forcefield objects, using the interaction parameters, bead type, and user identifier.
        The bead type `i` can be overridden, and the interaction with another forcefield object `o`
        can also be specified.

        Parameters:
        -----------
        o : forcefield or int, optional, default=None
            The second forcefield object or bead type used for calculating the off-diagonal
            pair coefficients. If not provided, the method assumes interactions between
            beads of the same type.
        printflag : bool, optional, default=False
            If True, the generated off-diagonal pair coefficient command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        i : int, optional, default=None
            The bead type used for the current forcefield instance. If not provided,
            defaults to the instance's bead type (`self.beadtype`).
        raw : bool, optional, default=False
            If True, returns the raw template for the off-diagonal pair coefficients without interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier used in the command. Defaults to the instance's beadtype
            if not provided.
        userid : str, optional, default=None
            The user identifier included in the formatted command. Defaults to the instance's
            userid if not specified.
        oname : str, optional, default=None
            The user identifier for the second forcefield or bead type. If not provided, it
            defaults to `"none"`.

        Returns:
        --------
        str
            The formatted off-diagonal pair coefficient command string.

        Raises:
        -------
        TypeError
            If `USER` is not of type `struct` or derived from `struct`.
        IndexError
            If the first argument `o` is not a forcefield object or an integer.
        """

        # raw format
        if raw:
            return self.PAIR_OFFDIAGCOEFF
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
            i = i if i is not None else beadtype
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # Determine the first bead type (i)
        i = i if i is not None else beadtype
        # Determine the second bead type (j) based on o
        if o is None:
            j = i
        elif hasattr(o, 'beadtype'):
            j = o.beadtype
        elif isinstance(o, (float, int)):
            j = int(o)
        else:
            raise IndexError("The first argument should be a forcefield object or an integer representing bead type.")
        # Adjust j if it matches i (to ensure off-diagonal interaction)
        if j == i:
            j = i - 1 if i > 1 else i + 1
        oname = oname if oname is not None else o.userid if hasattr(o, "userid") else "none"
        # cmd
        cmd = parameters.formateval(self.PAIR_OFFDIAGCOEFF) % (min(i,j),max(j,i))
        # Replace [comment] with the formatted string, without using .format()
        cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, self.userid, j, oname) if verbose else "")
        if printflag: print(cmd)
        return cmd


# %% Forecefield library
# This section can be upgraded by the end-user according to the manual of each style
# numerical value shoud be declared with variable/parameter with the same syntax as in LAMMPS.
# Note that you can copy directly LAMMPS code between triple """ for templates
# Main templates are strings: PAIR_STYLE, PAIR_DIAGCOEFF, PAIR_OFFDIAGCOEFF
#
# ${param} represents the variable called "param", and whose value will be defined
# in the parameters section of the material class as parameters.param = value
#
# use {comment} to add an automatic comment
# %d in PAIR_COEFF represent place holder for corresponding beadtype
#
# The subsitutions are managed by the parent class forcefield().

# BEGIN PAIR-STYLE FORCEFIELD ===========================
class smd(forcefield):
    """ SMD forcefield """
    name = forcefield.name + struct(forcefield="LAMMPS:SMD")
    description = forcefield.description + struct(forcefield="LAMMPS:SMD - solid, liquid, rigid forcefields (continuum mechanics)")

    # forcefield definition (LAMMPS code between triple """)
    PAIR_STYLE = """
    # [comment] PAIR STYLE SMD
    pair_style      hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION &
                                   smd/tlsph smd/hertz ${contact_scale}
    """
# END PAIR-STYLE FORCEFIELD ===========================


# BEGIN PAIR-COEFF FORCEFIELD ===========================
class ulsph(smd):
    """ SMD:ULSPH forcefield (updated Lagrangian) """
    name = smd.name + struct(style="ulsph")
    description = smd.description + struct(style="SMD:ULSPH - updated Lagrangian for fluids - SPH-like")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Pair diagonal coefficient ulsph
    pair_coeff      %d %d smd/ulsph *COMMON ${rho} ${c0} ${q1} ${Cp} 0 &
                    *EOS_TAIT ${taitexponent} &
                    *END
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """
# END PAIR-COEFF FORCEFIELD ===========================


# BEGIN PAIR-COEFF FORCEFIELD ===========================
class tlsph(smd):
    """ SMD:TLSPH forcefield (total Lagrangian) """
    name = smd.name + struct(style="tlsph")
    description = smd.description + struct(style="SMD:TLSPH - total Lagrangian for solids")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Diagonal pair coefficient tlsph
    pair_coeff      %d %d smd/tlsph *COMMON ${rho} ${E} ${nu} ${q1} ${q2} ${Hg} ${Cp} &
                    *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening} &
                    *EOS_LINEAR &
                    *END
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """
# END PAIR-COEFF FORCEFIELD ===========================


# BEGIN PAIR-COEFF FORCEFIELD ===========================
class none(smd):
    """ SMD:TLSPH forcefield (updated Lagrangian) """
    name = smd.name + struct(style="none")
    description = smd.description + struct(style="no interactions")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Diagonal pair coefficient tlsph
    pair_coeff      %d %d none
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """
# END PAIR-COEFF FORCEFIELD ===========================



# %% Material library
# template:
#   class mymaterial(myforcefield):
#       userid = "short name"
#       version = value
#       def __init__(self, beadtype=1, userid=None):
#           super().__init__()
#           if userid!=None: self.userid = userid
#           self.name.material"] = "short of the material"
#           self.description.material"] = "full description"
#           self.beadtype = beadtype
#           self.parameters = parameterforcefield(
#               param1 = value1,
#               param2 = value2,
#               param3 = "math expression with ${param1, ${param2}"
#           )
#
# ATTENTION: ${param1} and ${param2} cannot be used in an expression
#            if they do not have been prealably defined

# BEGIN MATERIAL: WATER ========================================
class water(ulsph):
    """ water material (smd:ulsph): generic water model
            water()
            water(beadtype=index, userid="myfluid", USER=...)

            override any propery with USER.parameter (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=1000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.q1: standard artificial viscosity linear coefficient (default=1.0)
                USER.Cp: heat capacity of material -- not used here (default=1.0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = ulsph.name + struct(material="water")
    description = ulsph.description + struct(material="water beads - SPH-like")
    userid = 'water'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ water forcefield:
            water(beadtype=index, userid="myfluid") """
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # water-water interactions
            rho = 1000,
            c0 = 10.0,
            q1 = 1.0,
            Cp = 1.0,
            taitexponent = 7,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = '2.5*${c0}^2*${rho}'
            ) + USER # update with user properties if any

# END MATERIAL: WATER ==========================================


# BEGIN MATERIAL: SOLID FOOD ========================================
class solidfood(tlsph):
    """ solidfood material (smd:tlsph): model solid food object
            solidfood()
            solidfood(beadtype=index, userid="myfood", USER=...)

            override any propery with USER.property=value (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=1000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.E: Young's modulus in Pa (default="5*${c0}^2*${rho}")
                USER.nu: Poisson ratio (default=0.3)
                USER.q1: standard artificial viscosity linear coefficient (default=1.0)
                USER.q2: standard artificial viscosity quadratic coefficient (default=0)
                USER.Hg: hourglass control coefficient (default=10.0)
                USER.Cp: heat capacity of material -- not used here (default=1.0)
                USER.sigma_yield: plastic yield stress in Pa (default="0.1*${E}")
                USER.hardening: hardening parameter (default=0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = tlsph.name + struct(material="solidfood")
    description = tlsph.description + struct(material="food beads - solid behavior")
    userid = 'solidfood'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ food forcefield:
            solidfood(beadtype=index, userid="myfood") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # food-food interactions
            rho = 1000,
            c0 = 10.0,
            E = "5*${c0}^2*${rho}",
            nu = 0.3,
            q1 = 1.0,
            q2 = 0.0,
            Hg = 10.0,
            Cp = 1.0,
            sigma_yield = "0.1*${E}",
            hardening = 0,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = "2.5*${c0}^2*${rho}"
            ) + USER # update with user properties if any

# END MATERIAL: SOLID FOOD ==========================================


# BEGIN MATERIAL: SALT TLSPH ========================================
class saltTLSPH(tlsph):
    """ SALTLSPH (smd:tlsph): ongoing "salting" beadtype for rheology control
            saltTLSPH()
            saltTLSPH(beadtype=index, userid="salt", USER=...)

            override any property with USER.property = value
    """
    name = tlsph.name + struct(material="solidfood")
    description = tlsph.description + struct(material="food beads - solid behavior")
    userid = '"salt"'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ saltTLSPH forcefield:
            saltTLSPH(beadtype=index, userid="salt") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # food-food interactions
            rho = 1000,
            c0 = 10.0,
            E = '5*${c0}^2*${rho}',
            nu = 0.3,
            q1 = 1.0,
            q2 = 0.0,
            Hg = 10,
            Cp = 1.0,
            sigma_yield = '0.1*${E}',
            hardening = 0,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = '2.5*${c0}^2*${rho}'
            ) + USER # update with user properties if any

# END MATERIAL: SOLID FOOD ==========================================



# BEGIN MATERIAL: RIGID WALLS ========================================
class rigidwall(none):
    """ rigid walls (smd:none):
            rigidwall()
            rigidwall(beadtype=index, userid="wall", USER=...)

            override any propery with USER.parameter (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=3000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = none.name + struct(material="walls")
    description = none.description + struct(material="rigid walls")
    userid = 'solidfood'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ rigidwall forcefield:
            rigidwall(beadtype=index, userid="mywall") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            rho = 3000,
            c0 = 10.0,
            contact_stiffness = '2.5*${c0}^2*${rho}',
            contact_scale = 1.5
            ) + USER # update with user properties if any


# END MATERIAL: RIGID WALLS ==========================================


# %% DEBUG
# ===================================================
# main()
# ===================================================
# for debugging purposes (code called as a script)
# the code is called from here
# ===================================================
if __name__ == '__main__':
    w = water(beadtype=1, userid="fluid")
    w.parameters.Cp = 20
    print("\n"*2,w)
    f = solidfood(beadtype=2, userid="elastic")
    print("\n"*2,f)
    r = rigidwall(beadtype=3, userid="wall")
    print("\n"*2,r)

Classes

class forcefield

The forcefield class represents the core implementation of a forcefield model, defining interaction parameters and coefficients for simulations. This class provides methods to handle pair styles, diagonal pair coefficients, and off-diagonal pair coefficients, which are essential for simulating inter-particle interactions in molecular dynamics or other physics-based simulations.

Attributes:

PAIR_STYLE : str The default pair style command for the forcefield interactions.

PAIR_DIAGCOEFF : str The default command for calculating diagonal pair coefficients.

PAIR_OFFDIAGCOEFF : str The default command for calculating off-diagonal pair coefficients.

parameters : parameterforcefield An instance of parameterforcefield that stores the parameters for evaluating interaction commands.

beadtype : int The bead type associated with the current forcefield instance.

userid : str A unique identifier for the forcefield instance, used in interaction commands.

Methods:

pair_style(printflag=True): Generate and return the pair style command based on the current parameters, beadtype, and userid.

pair_diagcoeff(printflag=True, i=None): Generate and return the diagonal pair coefficients based on the current parameters, beadtype, and userid. The bead type i can be overridden with an optional argument.

pair_offdiagcoeff(o=None, printflag=True, i=None): Generate and return the off-diagonal pair coefficients between two different bead types or forcefield objects. The bead type i can be overridden, and the interaction with another forcefield object o can also be specified.

Notes:

  • This class is intended to be extended by specific forcefield types such as ulsph.
  • The parameters used in the interaction commands are dynamically evaluated using the parameterforcefield class, which provides the required values during runtime.
Expand source code
class forcefield():
    """
    The `forcefield` class represents the core implementation of a forcefield model,
    defining interaction parameters and coefficients for simulations. This class provides
    methods to handle pair styles, diagonal pair coefficients, and off-diagonal pair coefficients,
    which are essential for simulating inter-particle interactions in molecular dynamics or
    other physics-based simulations.

    Attributes:
    -----------
    PAIR_STYLE : str
        The default pair style command for the forcefield interactions.

    PAIR_DIAGCOEFF : str
        The default command for calculating diagonal pair coefficients.

    PAIR_OFFDIAGCOEFF : str
        The default command for calculating off-diagonal pair coefficients.

    parameters : parameterforcefield
        An instance of `parameterforcefield` that stores the parameters for
        evaluating interaction commands.

    beadtype : int
        The bead type associated with the current forcefield instance.

    userid : str
        A unique identifier for the forcefield instance, used in interaction commands.

    Methods:
    --------
    pair_style(printflag=True):
        Generate and return the pair style command based on the current parameters,
        beadtype, and userid.

    pair_diagcoeff(printflag=True, i=None):
        Generate and return the diagonal pair coefficients based on the current parameters,
        beadtype, and userid. The bead type `i` can be overridden with an optional argument.

    pair_offdiagcoeff(o=None, printflag=True, i=None):
        Generate and return the off-diagonal pair coefficients between two different
        bead types or forcefield objects. The bead type `i` can be overridden, and the
        interaction with another forcefield object `o` can also be specified.

    Notes:
    ------
    - This class is intended to be extended by specific forcefield types such as `ulsph`.
    - The parameters used in the interaction commands are dynamically evaluated using
      the `parameterforcefield` class, which provides the required values during runtime.
    """

    # Main attributes (instance independent)
    name = struct(forcefield="undefined", style="undefined", material="undefined")
    description = struct(forcefield="missing", style="missing", material="missing")
    beadtype = 1  # default bead type
    parameters = parameterforcefield() # empty parameters object
    userid = "undefined"
    version = 0

    # print method for headers (static, no implicit argument)
    @staticmethod
    def printheader(txt,align="^",width=80,filler="~"):
        """ print header """
        if txt=="":
            print("\n"+filler*(width+6)+"\n")
        else:
            print(("\n{:"+filler+"{align}{width}}\n").format(' [ '+txt+' ] ', align=align, width=str(width)))

    # Display/representation method
    # The method provides full help for the end-user
    def __repr__(self):
        """ disp method """
        stamp = self.name.forcefield+":"+self.name.style+":"+self.name.material
        self.printheader("%s | version=%0.3g" % (self.userid,self.version),filler="=")
        print("  Bead of type %d = [%s]" % (self.beadtype,stamp))
        print(self.parameters)
        self.printheader("description",filler=".")
        print("\t# \t%s" % self.description.forcefield)
        print("\t# \t%s" % self.description.style)
        print("\t# \t%s" % self.description.material)
        self.printheader("methods")
        print("\t   >>> replace FFi,FFj by your variable names <<<")
        print("\tTo assign a type, use: FFi.beadtype = integer value")
        print("\tUse the methods FFi.pair_style() and FFi.pair_coeff(FFj)")
        print("\tNote for pairs: the caller object is i (FFi), the argument is j (FFj or j)")
        self.printheader("template")
        self.pair_style()
        self.pair_diagcoeff()
        self.pair_offdiagcoeff()
        self.printheader("")
        return stamp

    # Extract attributes within the class
    def getallattributes(self):
        """ advanced method to get all attributes including class ones"""
        return {k: getattr(self, k) for k in dir(self) \
                if (not k.startswith('_')) and (not isinstance(getattr(self, k),types.MethodType))}


    # Forcefield Methods: pair_style(), pair_coeff()
    # the substitution of LAMMPS variables is carried out with the method
    # parameters.format() method implemented in struct and inherited by parameterforcefield()
    def pair_style(self,printflag=False,verbose=True, raw=False,USER=None,beadtype=None,userid=None):
        """
        Generate and return the pair style command for the current forcefield instance.

        This method creates a formatted pair style command based on the interaction parameters
        stored in the `parameters` attribute. It allows customization of the command using the
        `beadtype` and `userid` arguments. The behavior can be altered by passing a `USER` object
        or opting for the raw command template.

        Parameters:
        -----------
        printflag : bool, optional, default=False
            If True, the generated pair style command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        raw : bool, optional, default=False
            If True, returns the raw template of the pair style without any interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier used in the generated command. If not provided, the
            instance's beadtype is used.
        userid : str, optional, default=None
            The user identifier to include in the formatted command. Defaults to the instance's
            userid if not specified.

        Returns:
        --------
        str
            The formatted pair style command string.

        Raises:
        -------
        TypeError
            If `USER` is provided but is not of type `struct` or derived from `struct`.
        """
        # raw format
        if raw:
            return self.PAIR_STYLE
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # cmd
        cmd = parameters.formateval(self.PAIR_STYLE)
        # Replace [comment] with the formatted comment (e.g., "[2:my_user_id]")
        cmd = cmd.replace("[comment]","[%d:%s]" % (beadtype, userid) if verbose else "")
        if printflag: print(cmd)
        return cmd


    def pair_diagcoeff(self,printflag=False,verbose=True, i=None,raw=False,USER=None,beadtype=None,userid=None):
        """
        Generate and return the diagonal pair coefficients for the current forcefield instance.

        This method evaluates the diagonal pair coefficients based on the interaction parameters,
        the bead type (`beadtype`), and the user identifier (`userid`). The bead type `i` can
        be overridden by passing it as an argument. The method supports returning the raw template
        without evaluation and modifying parameters using a `USER` object.

        Parameters:
        -----------
        printflag : bool, optional, default=False
            If True, the generated diagonal pair coefficient command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        i : int, optional, default=None
            The bead type used for evaluating the diagonal pair coefficients. If not provided,
            defaults to the instance's bead type (`self.beadtype`).
        raw : bool, optional, default=False
            If True, returns the raw template for the diagonal pair coefficients without interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier to use in the command. Defaults to the instance's beadtype
            if not provided.
        userid : str, optional, default=None
            The user identifier to include in the formatted command. Defaults to the instance's
            userid if not specified.

        Returns:
        --------
        str
            The formatted diagonal pair coefficient command string.

        Raises:
        -------
        TypeError
            If `USER` is provided but is not of type `struct` or derived from `struct`.
        """
        # raw format
        if raw:
            return self.PAIR_DIAGCOEFF
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # diagonal index
        i = i if i is not None else beadtype
        # cmd
        cmd = parameters.formateval(self.PAIR_DIAGCOEFF) % (i,i)
        # Replace [comment] with the formatted string, without using .format()
        cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, userid, i, userid) if verbose else "")
        if printflag: print(cmd)
        return cmd


    def pair_offdiagcoeff(self,o=None,printflag=False,verbose=True,i=None,raw=False,USER=None,beadtype=None,userid=None,oname=None):
        """
        Generate and return the off-diagonal pair coefficients for the current forcefield instance.

        This method evaluates the off-diagonal pair coefficients between two different bead types
        or forcefield objects, using the interaction parameters, bead type, and user identifier.
        The bead type `i` can be overridden, and the interaction with another forcefield object `o`
        can also be specified.

        Parameters:
        -----------
        o : forcefield or int, optional, default=None
            The second forcefield object or bead type used for calculating the off-diagonal
            pair coefficients. If not provided, the method assumes interactions between
            beads of the same type.
        printflag : bool, optional, default=False
            If True, the generated off-diagonal pair coefficient command is printed to the console.
        verbose : bool, optional, default=True
            If True, enables verbose output during the script generation.
        i : int, optional, default=None
            The bead type used for the current forcefield instance. If not provided,
            defaults to the instance's bead type (`self.beadtype`).
        raw : bool, optional, default=False
            If True, returns the raw template for the off-diagonal pair coefficients without interpretation.
        USER : struct, optional, default=None
            A user-defined struct object used for overriding the default parameters.
            When provided, the method updates parameters using `USER` in conjunction with
            the instance's base parameters.
        beadtype : int, optional, default=None
            The bead type identifier used in the command. Defaults to the instance's beadtype
            if not provided.
        userid : str, optional, default=None
            The user identifier included in the formatted command. Defaults to the instance's
            userid if not specified.
        oname : str, optional, default=None
            The user identifier for the second forcefield or bead type. If not provided, it
            defaults to `"none"`.

        Returns:
        --------
        str
            The formatted off-diagonal pair coefficient command string.

        Raises:
        -------
        TypeError
            If `USER` is not of type `struct` or derived from `struct`.
        IndexError
            If the first argument `o` is not a forcefield object or an integer.
        """

        # raw format
        if raw:
            return self.PAIR_OFFDIAGCOEFF
        # USER overrride if the forcefield class is inherited
        if USER is None: # ---- default behavior for forcefield
            parameters = self.parameters
            beadtype = self.beadtype
            userid = self.userid
            i = i if i is not None else beadtype
        elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
            parameters = self.parameters+USER
            beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
            userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
        else:
            raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
        # Determine the first bead type (i)
        i = i if i is not None else beadtype
        # Determine the second bead type (j) based on o
        if o is None:
            j = i
        elif hasattr(o, 'beadtype'):
            j = o.beadtype
        elif isinstance(o, (float, int)):
            j = int(o)
        else:
            raise IndexError("The first argument should be a forcefield object or an integer representing bead type.")
        # Adjust j if it matches i (to ensure off-diagonal interaction)
        if j == i:
            j = i - 1 if i > 1 else i + 1
        oname = oname if oname is not None else o.userid if hasattr(o, "userid") else "none"
        # cmd
        cmd = parameters.formateval(self.PAIR_OFFDIAGCOEFF) % (min(i,j),max(j,i))
        # Replace [comment] with the formatted string, without using .format()
        cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, self.userid, j, oname) if verbose else "")
        if printflag: print(cmd)
        return cmd

Subclasses

Class variables

var beadtype
var description
var name
var parameters
var userid
var version

Static methods

def printheader(txt, align='^', width=80, filler='~')

print header

Expand source code
@staticmethod
def printheader(txt,align="^",width=80,filler="~"):
    """ print header """
    if txt=="":
        print("\n"+filler*(width+6)+"\n")
    else:
        print(("\n{:"+filler+"{align}{width}}\n").format(' [ '+txt+' ] ', align=align, width=str(width)))

Methods

def getallattributes(self)

advanced method to get all attributes including class ones

Expand source code
def getallattributes(self):
    """ advanced method to get all attributes including class ones"""
    return {k: getattr(self, k) for k in dir(self) \
            if (not k.startswith('_')) and (not isinstance(getattr(self, k),types.MethodType))}
def pair_diagcoeff(self, printflag=False, verbose=True, i=None, raw=False, USER=None, beadtype=None, userid=None)

Generate and return the diagonal pair coefficients for the current forcefield instance.

This method evaluates the diagonal pair coefficients based on the interaction parameters, the bead type (beadtype), and the user identifier (userid). The bead type i can be overridden by passing it as an argument. The method supports returning the raw template without evaluation and modifying parameters using a USER object.

Parameters:

printflag : bool, optional, default=False If True, the generated diagonal pair coefficient command is printed to the console. verbose : bool, optional, default=True If True, enables verbose output during the script generation. i : int, optional, default=None The bead type used for evaluating the diagonal pair coefficients. If not provided, defaults to the instance's bead type (self.beadtype). raw : bool, optional, default=False If True, returns the raw template for the diagonal pair coefficients without interpretation. USER : struct, optional, default=None A user-defined struct object used for overriding the default parameters. When provided, the method updates parameters using USER in conjunction with the instance's base parameters. beadtype : int, optional, default=None The bead type identifier to use in the command. Defaults to the instance's beadtype if not provided. userid : str, optional, default=None The user identifier to include in the formatted command. Defaults to the instance's userid if not specified.

Returns:

str The formatted diagonal pair coefficient command string.

Raises:

TypeError If USER is provided but is not of type struct or derived from struct.

Expand source code
def pair_diagcoeff(self,printflag=False,verbose=True, i=None,raw=False,USER=None,beadtype=None,userid=None):
    """
    Generate and return the diagonal pair coefficients for the current forcefield instance.

    This method evaluates the diagonal pair coefficients based on the interaction parameters,
    the bead type (`beadtype`), and the user identifier (`userid`). The bead type `i` can
    be overridden by passing it as an argument. The method supports returning the raw template
    without evaluation and modifying parameters using a `USER` object.

    Parameters:
    -----------
    printflag : bool, optional, default=False
        If True, the generated diagonal pair coefficient command is printed to the console.
    verbose : bool, optional, default=True
        If True, enables verbose output during the script generation.
    i : int, optional, default=None
        The bead type used for evaluating the diagonal pair coefficients. If not provided,
        defaults to the instance's bead type (`self.beadtype`).
    raw : bool, optional, default=False
        If True, returns the raw template for the diagonal pair coefficients without interpretation.
    USER : struct, optional, default=None
        A user-defined struct object used for overriding the default parameters.
        When provided, the method updates parameters using `USER` in conjunction with
        the instance's base parameters.
    beadtype : int, optional, default=None
        The bead type identifier to use in the command. Defaults to the instance's beadtype
        if not provided.
    userid : str, optional, default=None
        The user identifier to include in the formatted command. Defaults to the instance's
        userid if not specified.

    Returns:
    --------
    str
        The formatted diagonal pair coefficient command string.

    Raises:
    -------
    TypeError
        If `USER` is provided but is not of type `struct` or derived from `struct`.
    """
    # raw format
    if raw:
        return self.PAIR_DIAGCOEFF
    # USER overrride if the forcefield class is inherited
    if USER is None: # ---- default behavior for forcefield
        parameters = self.parameters
        beadtype = self.beadtype
        userid = self.userid
    elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
        parameters = self.parameters+USER
        beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
        userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
    else:
        raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
    # diagonal index
    i = i if i is not None else beadtype
    # cmd
    cmd = parameters.formateval(self.PAIR_DIAGCOEFF) % (i,i)
    # Replace [comment] with the formatted string, without using .format()
    cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, userid, i, userid) if verbose else "")
    if printflag: print(cmd)
    return cmd
def pair_offdiagcoeff(self, o=None, printflag=False, verbose=True, i=None, raw=False, USER=None, beadtype=None, userid=None, oname=None)

Generate and return the off-diagonal pair coefficients for the current forcefield instance.

This method evaluates the off-diagonal pair coefficients between two different bead types or forcefield objects, using the interaction parameters, bead type, and user identifier. The bead type i can be overridden, and the interaction with another forcefield object o can also be specified.

Parameters:

o : forcefield or int, optional, default=None The second forcefield object or bead type used for calculating the off-diagonal pair coefficients. If not provided, the method assumes interactions between beads of the same type. printflag : bool, optional, default=False If True, the generated off-diagonal pair coefficient command is printed to the console. verbose : bool, optional, default=True If True, enables verbose output during the script generation. i : int, optional, default=None The bead type used for the current forcefield instance. If not provided, defaults to the instance's bead type (self.beadtype). raw : bool, optional, default=False If True, returns the raw template for the off-diagonal pair coefficients without interpretation. USER : struct, optional, default=None A user-defined struct object used for overriding the default parameters. When provided, the method updates parameters using USER in conjunction with the instance's base parameters. beadtype : int, optional, default=None The bead type identifier used in the command. Defaults to the instance's beadtype if not provided. userid : str, optional, default=None The user identifier included in the formatted command. Defaults to the instance's userid if not specified. oname : str, optional, default=None The user identifier for the second forcefield or bead type. If not provided, it defaults to "none".

Returns:

str The formatted off-diagonal pair coefficient command string.

Raises:

TypeError If USER is not of type struct or derived from struct. IndexError If the first argument o is not a forcefield object or an integer.

Expand source code
def pair_offdiagcoeff(self,o=None,printflag=False,verbose=True,i=None,raw=False,USER=None,beadtype=None,userid=None,oname=None):
    """
    Generate and return the off-diagonal pair coefficients for the current forcefield instance.

    This method evaluates the off-diagonal pair coefficients between two different bead types
    or forcefield objects, using the interaction parameters, bead type, and user identifier.
    The bead type `i` can be overridden, and the interaction with another forcefield object `o`
    can also be specified.

    Parameters:
    -----------
    o : forcefield or int, optional, default=None
        The second forcefield object or bead type used for calculating the off-diagonal
        pair coefficients. If not provided, the method assumes interactions between
        beads of the same type.
    printflag : bool, optional, default=False
        If True, the generated off-diagonal pair coefficient command is printed to the console.
    verbose : bool, optional, default=True
        If True, enables verbose output during the script generation.
    i : int, optional, default=None
        The bead type used for the current forcefield instance. If not provided,
        defaults to the instance's bead type (`self.beadtype`).
    raw : bool, optional, default=False
        If True, returns the raw template for the off-diagonal pair coefficients without interpretation.
    USER : struct, optional, default=None
        A user-defined struct object used for overriding the default parameters.
        When provided, the method updates parameters using `USER` in conjunction with
        the instance's base parameters.
    beadtype : int, optional, default=None
        The bead type identifier used in the command. Defaults to the instance's beadtype
        if not provided.
    userid : str, optional, default=None
        The user identifier included in the formatted command. Defaults to the instance's
        userid if not specified.
    oname : str, optional, default=None
        The user identifier for the second forcefield or bead type. If not provided, it
        defaults to `"none"`.

    Returns:
    --------
    str
        The formatted off-diagonal pair coefficient command string.

    Raises:
    -------
    TypeError
        If `USER` is not of type `struct` or derived from `struct`.
    IndexError
        If the first argument `o` is not a forcefield object or an integer.
    """

    # raw format
    if raw:
        return self.PAIR_OFFDIAGCOEFF
    # USER overrride if the forcefield class is inherited
    if USER is None: # ---- default behavior for forcefield
        parameters = self.parameters
        beadtype = self.beadtype
        userid = self.userid
        i = i if i is not None else beadtype
    elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
        parameters = self.parameters+USER
        beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
        userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
    else:
        raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
    # Determine the first bead type (i)
    i = i if i is not None else beadtype
    # Determine the second bead type (j) based on o
    if o is None:
        j = i
    elif hasattr(o, 'beadtype'):
        j = o.beadtype
    elif isinstance(o, (float, int)):
        j = int(o)
    else:
        raise IndexError("The first argument should be a forcefield object or an integer representing bead type.")
    # Adjust j if it matches i (to ensure off-diagonal interaction)
    if j == i:
        j = i - 1 if i > 1 else i + 1
    oname = oname if oname is not None else o.userid if hasattr(o, "userid") else "none"
    # cmd
    cmd = parameters.formateval(self.PAIR_OFFDIAGCOEFF) % (min(i,j),max(j,i))
    # Replace [comment] with the formatted string, without using .format()
    cmd = cmd.replace("[comment]", "[%d:%s x %d:%s]" % (i, self.userid, j, oname) if verbose else "")
    if printflag: print(cmd)
    return cmd
def pair_style(self, printflag=False, verbose=True, raw=False, USER=None, beadtype=None, userid=None)

Generate and return the pair style command for the current forcefield instance.

This method creates a formatted pair style command based on the interaction parameters stored in the parameters attribute. It allows customization of the command using the beadtype and userid arguments. The behavior can be altered by passing a USER object or opting for the raw command template.

Parameters:

printflag : bool, optional, default=False If True, the generated pair style command is printed to the console. verbose : bool, optional, default=True If True, enables verbose output during the script generation. raw : bool, optional, default=False If True, returns the raw template of the pair style without any interpretation. USER : struct, optional, default=None A user-defined struct object used for overriding the default parameters. When provided, the method updates parameters using USER in conjunction with the instance's base parameters. beadtype : int, optional, default=None The bead type identifier used in the generated command. If not provided, the instance's beadtype is used. userid : str, optional, default=None The user identifier to include in the formatted command. Defaults to the instance's userid if not specified.

Returns:

str The formatted pair style command string.

Raises:

TypeError If USER is provided but is not of type struct or derived from struct.

Expand source code
def pair_style(self,printflag=False,verbose=True, raw=False,USER=None,beadtype=None,userid=None):
    """
    Generate and return the pair style command for the current forcefield instance.

    This method creates a formatted pair style command based on the interaction parameters
    stored in the `parameters` attribute. It allows customization of the command using the
    `beadtype` and `userid` arguments. The behavior can be altered by passing a `USER` object
    or opting for the raw command template.

    Parameters:
    -----------
    printflag : bool, optional, default=False
        If True, the generated pair style command is printed to the console.
    verbose : bool, optional, default=True
        If True, enables verbose output during the script generation.
    raw : bool, optional, default=False
        If True, returns the raw template of the pair style without any interpretation.
    USER : struct, optional, default=None
        A user-defined struct object used for overriding the default parameters.
        When provided, the method updates parameters using `USER` in conjunction with
        the instance's base parameters.
    beadtype : int, optional, default=None
        The bead type identifier used in the generated command. If not provided, the
        instance's beadtype is used.
    userid : str, optional, default=None
        The user identifier to include in the formatted command. Defaults to the instance's
        userid if not specified.

    Returns:
    --------
    str
        The formatted pair style command string.

    Raises:
    -------
    TypeError
        If `USER` is provided but is not of type `struct` or derived from `struct`.
    """
    # raw format
    if raw:
        return self.PAIR_STYLE
    # USER overrride if the forcefield class is inherited
    if USER is None: # ---- default behavior for forcefield
        parameters = self.parameters
        beadtype = self.beadtype
        userid = self.userid
    elif isinstance(USER,struct): # ---- behavior for dforcefield (using baseclass)
        parameters = self.parameters+USER
        beadtype = beadtype if beadtype is not None else USER.beadtype if hasattr(USER, 'beadtype') else self.beadtype
        userid = userid if userid is not None else USER.userid if hasattr(USER, 'userid') else self.userid
    else:
        raise TypeError(f'USER must be of type struct or derived from struct, not {type(USER)}')
    # cmd
    cmd = parameters.formateval(self.PAIR_STYLE)
    # Replace [comment] with the formatted comment (e.g., "[2:my_user_id]")
    cmd = cmd.replace("[comment]","[%d:%s]" % (beadtype, userid) if verbose else "")
    if printflag: print(cmd)
    return cmd
class none

SMD:TLSPH forcefield (updated Lagrangian)

Expand source code
class none(smd):
    """ SMD:TLSPH forcefield (updated Lagrangian) """
    name = smd.name + struct(style="none")
    description = smd.description + struct(style="no interactions")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Diagonal pair coefficient tlsph
    pair_coeff      %d %d none
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """

Ancestors

Subclasses

Class variables

var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name

Inherited members

class paramauto (sortdefinitions=False, **kwargs)

Class: paramauto

A subclass of param with enhanced handling for automatic sorting and evaluation of definitions. The paramauto class ensures that all fields are sorted to resolve dependencies, allowing seamless stacking of partially defined objects.


Features

  • Inherits all functionalities of param.
  • Automatically sorts definitions for dependency resolution.
  • Simplifies handling of partial definitions in dynamic structures.
  • Supports safe concatenation of definitions.

Examples

Automatic Dependency Sorting

Definitions are automatically sorted to resolve dependencies:

p = paramauto(a=1, b="${a}+1", c="${a}+${b}")
p.disp()
# Output:
# --------
#      a: 1
#      b: ${a} + 1 (= 2)
#      c: ${a} + ${b} (= 3)
# --------

Handling Missing Definitions

Unresolved dependencies raise warnings but do not block execution:

p = paramauto(a=1, b="${a}+1", c="${a}+${d}")
p.disp()
# Output:
# --------
#      a: 1
#      b: ${a} + 1 (= 2)
#      c: ${a} + ${d} (= < undef definition "${d}" >)
# --------

Concatenation and Inheritance

Concatenating paramauto objects resolves definitions:

p1 = paramauto(a=1, b="${a}+2")
p2 = paramauto(c="${b}*3")
p3 = p1 + p2
p3.disp()
# Output:
# --------
#      a: 1
#      b: ${a} + 2 (= 3)
#      c: ${b} * 3 (= 9)
# --------

Utility Methods

Method Description
sortdefinitions() Automatically sorts fields to resolve dependencies.
eval() Evaluate all fields, resolving dependencies.
disp() Display all fields with their resolved values.

Overloaded Operators

Supported Operators

  • +: Concatenates two paramauto objects, resolving dependencies.
  • +=: Updates the current object with another, resolving dependencies.
  • len(): Number of fields.
  • in: Check for field existence.

Advanced Usage

Partial Definitions

The paramauto class simplifies handling of partially defined fields:

p = paramauto(a="${d}", b="${a}+1")
p.disp()
# Warning: Unable to resolve dependencies.
# --------
#      a: ${d} (= < undef definition "${d}" >)
#      b: ${a} + 1 (= < undef definition "${d}" >)
# --------

p.d = 10
p.disp()
# Dependencies are resolved:
# --------
#      d: 10
#      a: ${d} (= 10)
#      b: ${a} + 1 (= 11)
# --------

Notes

  • The paramauto class is computationally more intensive than param due to automatic sorting.
  • It is ideal for managing dynamic systems with complex interdependencies.

Examples

            p = paramauto()
            p.b = "${aa}"
            p.disp()
        yields
            WARNING: unable to interpret 1/1 expressions in "definitions"
              -----------:----------------------------------------
                        b: ${aa}
                         = < undef definition "${aa}" >
              -----------:----------------------------------------
              p.aa = 2
              p.disp()
        yields
            -----------:----------------------------------------
                     aa: 2
                      b: ${aa}
                       = 2
            -----------:----------------------------------------
            q = paramauto(c="${aa}+${b}")+p
            q.disp()
        yields
            -----------:----------------------------------------
                     aa: 2
                      b: ${aa}
                       = 2
                      c: ${aa}+${b}
                       = 4
            -----------:----------------------------------------
            q.aa = 30
            q.disp()
        yields
            -----------:----------------------------------------
                     aa: 30
                      b: ${aa}
                       = 30
                      c: ${aa}+${b}
                       = 60
            -----------:----------------------------------------
            q.aa = "${d}"
            q.disp()
        yields multiple errors (recursion)
        WARNING: unable to interpret 3/3 expressions in "definitions"
          -----------:----------------------------------------
                   aa: ${d}
                     = < undef definition "${d}" >
                    b: ${aa}
                     = Eval Error < invalid [...] (<string>, line 1) >
                    c: ${aa}+${b}
                     = Eval Error < invalid [...] (<string>, line 1) >
          -----------:----------------------------------------
            q.d = 100
            q.disp()
        yields
          -----------:----------------------------------------
                    d: 100
                   aa: ${d}
                     = 100
                    b: ${aa}
                     = 100
                    c: ${aa}+${b}
                     = 200
          -----------:----------------------------------------


    Example:

        p = paramauto(b="${a}+1",c="${a}+${d}",a=1)
        p.disp()
    generates:
        WARNING: unable to interpret 1/3 expressions in "definitions"
          -----------:----------------------------------------
                    a: 1
                    b: ${a}+1
                     = 2
                    c: ${a}+${d}
                     = < undef definition "${d}" >
          -----------:----------------------------------------
    setting p.d
        p.d = 2
        p.disp()
    produces
          -----------:----------------------------------------
                    a: 1
                    d: 2
                    b: ${a}+1
                     = 2
                    c: ${a}+${d}
                     = 3
          -----------:----------------------------------------

constructor

Expand source code
class paramauto(param):
    """
    Class: `paramauto`
    ==================

    A subclass of `param` with enhanced handling for automatic sorting and evaluation
    of definitions. The `paramauto` class ensures that all fields are sorted to resolve
    dependencies, allowing seamless stacking of partially defined objects.

    ---

    ### Features
    - Inherits all functionalities of `param`.
    - Automatically sorts definitions for dependency resolution.
    - Simplifies handling of partial definitions in dynamic structures.
    - Supports safe concatenation of definitions.

    ---

    ### Examples

    #### Automatic Dependency Sorting
    Definitions are automatically sorted to resolve dependencies:
    ```python
    p = paramauto(a=1, b="${a}+1", c="${a}+${b}")
    p.disp()
    # Output:
    # --------
    #      a: 1
    #      b: ${a} + 1 (= 2)
    #      c: ${a} + ${b} (= 3)
    # --------
    ```

    #### Handling Missing Definitions
    Unresolved dependencies raise warnings but do not block execution:
    ```python
    p = paramauto(a=1, b="${a}+1", c="${a}+${d}")
    p.disp()
    # Output:
    # --------
    #      a: 1
    #      b: ${a} + 1 (= 2)
    #      c: ${a} + ${d} (= < undef definition "${d}" >)
    # --------
    ```

    ---

    ### Concatenation and Inheritance
    Concatenating `paramauto` objects resolves definitions:
    ```python
    p1 = paramauto(a=1, b="${a}+2")
    p2 = paramauto(c="${b}*3")
    p3 = p1 + p2
    p3.disp()
    # Output:
    # --------
    #      a: 1
    #      b: ${a} + 2 (= 3)
    #      c: ${b} * 3 (= 9)
    # --------
    ```

    ---

    ### Utility Methods

    | Method                | Description                                            |
    |-----------------------|--------------------------------------------------------|
    | `sortdefinitions()`   | Automatically sorts fields to resolve dependencies.    |
    | `eval()`              | Evaluate all fields, resolving dependencies.           |
    | `disp()`              | Display all fields with their resolved values.         |

    ---

    ### Overloaded Operators

    #### Supported Operators
    - `+`: Concatenates two `paramauto` objects, resolving dependencies.
    - `+=`: Updates the current object with another, resolving dependencies.
    - `len()`: Number of fields.
    - `in`: Check for field existence.

    ---

    ### Advanced Usage

    #### Partial Definitions
    The `paramauto` class simplifies handling of partially defined fields:
    ```python
    p = paramauto(a="${d}", b="${a}+1")
    p.disp()
    # Warning: Unable to resolve dependencies.
    # --------
    #      a: ${d} (= < undef definition "${d}" >)
    #      b: ${a} + 1 (= < undef definition "${d}" >)
    # --------

    p.d = 10
    p.disp()
    # Dependencies are resolved:
    # --------
    #      d: 10
    #      a: ${d} (= 10)
    #      b: ${a} + 1 (= 11)
    # --------
    ```

    ---

    ### Notes
    - The `paramauto` class is computationally more intensive than `param` due to automatic sorting.
    - It is ideal for managing dynamic systems with complex interdependencies.

    ### Examples
                    p = paramauto()
                    p.b = "${aa}"
                    p.disp()
                yields
                    WARNING: unable to interpret 1/1 expressions in "definitions"
                      -----------:----------------------------------------
                                b: ${aa}
                                 = < undef definition "${aa}" >
                      -----------:----------------------------------------
                      p.aa = 2
                      p.disp()
                yields
                    -----------:----------------------------------------
                             aa: 2
                              b: ${aa}
                               = 2
                    -----------:----------------------------------------
                    q = paramauto(c="${aa}+${b}")+p
                    q.disp()
                yields
                    -----------:----------------------------------------
                             aa: 2
                              b: ${aa}
                               = 2
                              c: ${aa}+${b}
                               = 4
                    -----------:----------------------------------------
                    q.aa = 30
                    q.disp()
                yields
                    -----------:----------------------------------------
                             aa: 30
                              b: ${aa}
                               = 30
                              c: ${aa}+${b}
                               = 60
                    -----------:----------------------------------------
                    q.aa = "${d}"
                    q.disp()
                yields multiple errors (recursion)
                WARNING: unable to interpret 3/3 expressions in "definitions"
                  -----------:----------------------------------------
                           aa: ${d}
                             = < undef definition "${d}" >
                            b: ${aa}
                             = Eval Error < invalid [...] (<string>, line 1) >
                            c: ${aa}+${b}
                             = Eval Error < invalid [...] (<string>, line 1) >
                  -----------:----------------------------------------
                    q.d = 100
                    q.disp()
                yields
                  -----------:----------------------------------------
                            d: 100
                           aa: ${d}
                             = 100
                            b: ${aa}
                             = 100
                            c: ${aa}+${b}
                             = 200
                  -----------:----------------------------------------


            Example:

                p = paramauto(b="${a}+1",c="${a}+${d}",a=1)
                p.disp()
            generates:
                WARNING: unable to interpret 1/3 expressions in "definitions"
                  -----------:----------------------------------------
                            a: 1
                            b: ${a}+1
                             = 2
                            c: ${a}+${d}
                             = < undef definition "${d}" >
                  -----------:----------------------------------------
            setting p.d
                p.d = 2
                p.disp()
            produces
                  -----------:----------------------------------------
                            a: 1
                            d: 2
                            b: ${a}+1
                             = 2
                            c: ${a}+${d}
                             = 3
                  -----------:----------------------------------------

    """

    def __add__(self,p):
        return super(param,self).__add__(p,sortdefinitions=True,raiseerror=False)

    def __iadd__(self,p):
        return super(param,self).__iadd__(p,sortdefinitions=True,raiseerror=False)

    def __repr__(self):
        self.sortdefinitions(raiseerror=False)
        #super(param,self).__repr__()
        super().__repr__()
        return str(self)

Ancestors

  • pizza.private.mstruct.param
  • pizza.private.mstruct.struct

Subclasses

  • parameterforcefield
  • pizza.dscript.lambdaScriptdata
  • pizza.forcefield.parameterforcefield
  • pizza.region.regiondata
class parameterforcefield (sortdefinitions=False, **kwargs)

class of forcefields parameters, derived from param note that conctanating two forcefields force them to to be sorted

Constructor for parameterforcefield. It forces the parent's _returnerror parameter to False.

Parameters:

_protection : bool, optional Whether to enable protection on the parameters (default: False). _evaluation : bool, optional Whether evaluation is enabled for the parameters (default: True). sortdefinitions : bool, optional Whether to sort definitions upon initialization (default: False). **kwargs : dict Additional keyword arguments for the parent class.

Expand source code
class parameterforcefield(paramauto):
    """ class of forcefields parameters, derived from param
        note that conctanating two forcefields force them
        to to be sorted
    """
    _type = "FF"
    _fulltype = "forcefield"
    _ftype = "parameter"
    _maxdisplay = 80

    # same strategy as used in dscript for forcing  _returnerror = False (added 2024-09-12)
    def __init__(self, _protection=False, _evaluation=True, sortdefinitions=False, **kwargs):
        """
        Constructor for parameterforcefield. It forces the parent's _returnerror parameter to False.

        Parameters:
        -----------
        _protection : bool, optional
            Whether to enable protection on the parameters (default: False).
        _evaluation : bool, optional
            Whether evaluation is enabled for the parameters (default: True).
        sortdefinitions : bool, optional
            Whether to sort definitions upon initialization (default: False).
        **kwargs : dict
            Additional keyword arguments for the parent class.
        """
        # Call the parent class constructor
        super().__init__(_protection=_protection, _evaluation=_evaluation, sortdefinitions=sortdefinitions, **kwargs)
        # Override the _returnerror attribute at the instance level
        self._returnerror = False

Ancestors

  • pizza.private.mstruct.paramauto
  • pizza.private.mstruct.param
  • pizza.private.mstruct.struct
class rigidwall (beadtype=1, userid=None, USER=forcefield (FF object) with 0 parameters)

rigid walls (smd:none): rigidwall() rigidwall(beadtype=index, userid="wall", USER=…)

override any propery with USER.parameter (set only the parameters you want to override) USER.rho: density in kg/m3 (default=3000) USER.c0: speed of the sound in m/s (default=10.0) USER.contact_scale: scaling coefficient for contact (default=1.5) USER.contact_stiffness: contact stifness in Pa (default="2.5${c0}^2${rho}")

rigidwall forcefield: rigidwall(beadtype=index, userid="mywall")

Expand source code
class rigidwall(none):
    """ rigid walls (smd:none):
            rigidwall()
            rigidwall(beadtype=index, userid="wall", USER=...)

            override any propery with USER.parameter (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=3000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = none.name + struct(material="walls")
    description = none.description + struct(material="rigid walls")
    userid = 'solidfood'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ rigidwall forcefield:
            rigidwall(beadtype=index, userid="mywall") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            rho = 3000,
            c0 = 10.0,
            contact_stiffness = '2.5*${c0}^2*${rho}',
            contact_scale = 1.5
            ) + USER # update with user properties if any

Ancestors

Class variables

var description
var name
var userid
var version

Inherited members

class saltTLSPH (beadtype=1, userid=None, USER=forcefield (FF object) with 0 parameters)

SALTLSPH (smd:tlsph): ongoing "salting" beadtype for rheology control saltTLSPH() saltTLSPH(beadtype=index, userid="salt", USER=…)

override any property with USER.property = value

saltTLSPH forcefield: saltTLSPH(beadtype=index, userid="salt")

Expand source code
class saltTLSPH(tlsph):
    """ SALTLSPH (smd:tlsph): ongoing "salting" beadtype for rheology control
            saltTLSPH()
            saltTLSPH(beadtype=index, userid="salt", USER=...)

            override any property with USER.property = value
    """
    name = tlsph.name + struct(material="solidfood")
    description = tlsph.description + struct(material="food beads - solid behavior")
    userid = '"salt"'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ saltTLSPH forcefield:
            saltTLSPH(beadtype=index, userid="salt") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # food-food interactions
            rho = 1000,
            c0 = 10.0,
            E = '5*${c0}^2*${rho}',
            nu = 0.3,
            q1 = 1.0,
            q2 = 0.0,
            Hg = 10,
            Cp = 1.0,
            sigma_yield = '0.1*${E}',
            hardening = 0,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = '2.5*${c0}^2*${rho}'
            ) + USER # update with user properties if any

Ancestors

Class variables

var description
var name
var userid
var version

Inherited members

class smd

SMD forcefield

Expand source code
class smd(forcefield):
    """ SMD forcefield """
    name = forcefield.name + struct(forcefield="LAMMPS:SMD")
    description = forcefield.description + struct(forcefield="LAMMPS:SMD - solid, liquid, rigid forcefields (continuum mechanics)")

    # forcefield definition (LAMMPS code between triple """)
    PAIR_STYLE = """
    # [comment] PAIR STYLE SMD
    pair_style      hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION &
                                   smd/tlsph smd/hertz ${contact_scale}
    """

Ancestors

Subclasses

Class variables

var PAIR_STYLE
var description
var name

Inherited members

class solidfood (beadtype=1, userid=None, USER=forcefield (FF object) with 0 parameters)

solidfood material (smd:tlsph): model solid food object solidfood() solidfood(beadtype=index, userid="myfood", USER=…)

override any propery with USER.property=value (set only the parameters you want to override) USER.rho: density in kg/m3 (default=1000) USER.c0: speed of the sound in m/s (default=10.0) USER.E: Young's modulus in Pa (default="5${c0}^2${rho}") USER.nu: Poisson ratio (default=0.3) USER.q1: standard artificial viscosity linear coefficient (default=1.0) USER.q2: standard artificial viscosity quadratic coefficient (default=0) USER.Hg: hourglass control coefficient (default=10.0) USER.Cp: heat capacity of material – not used here (default=1.0) USER.sigma_yield: plastic yield stress in Pa (default="0.1${E}") USER.hardening: hardening parameter (default=0) USER.contact_scale: scaling coefficient for contact (default=1.5) USER.contact_stiffness: contact stifness in Pa (default="2.5${c0}^2*${rho}")

food forcefield: solidfood(beadtype=index, userid="myfood")

Expand source code
class solidfood(tlsph):
    """ solidfood material (smd:tlsph): model solid food object
            solidfood()
            solidfood(beadtype=index, userid="myfood", USER=...)

            override any propery with USER.property=value (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=1000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.E: Young's modulus in Pa (default="5*${c0}^2*${rho}")
                USER.nu: Poisson ratio (default=0.3)
                USER.q1: standard artificial viscosity linear coefficient (default=1.0)
                USER.q2: standard artificial viscosity quadratic coefficient (default=0)
                USER.Hg: hourglass control coefficient (default=10.0)
                USER.Cp: heat capacity of material -- not used here (default=1.0)
                USER.sigma_yield: plastic yield stress in Pa (default="0.1*${E}")
                USER.hardening: hardening parameter (default=0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = tlsph.name + struct(material="solidfood")
    description = tlsph.description + struct(material="food beads - solid behavior")
    userid = 'solidfood'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ food forcefield:
            solidfood(beadtype=index, userid="myfood") """
        # super().__init__()
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # food-food interactions
            rho = 1000,
            c0 = 10.0,
            E = "5*${c0}^2*${rho}",
            nu = 0.3,
            q1 = 1.0,
            q2 = 0.0,
            Hg = 10.0,
            Cp = 1.0,
            sigma_yield = "0.1*${E}",
            hardening = 0,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = "2.5*${c0}^2*${rho}"
            ) + USER # update with user properties if any

Ancestors

Class variables

var description
var name
var userid
var version

Inherited members

class struct (**kwargs)

Class: struct

A lightweight class that mimics Matlab-like structures, with additional features such as dynamic field creation, indexing, concatenation, and compatibility with evaluated parameters (param).


Features

  • Dynamic creation of fields.
  • Indexing and iteration support for fields.
  • Concatenation and subtraction of structures.
  • Conversion to and from dictionaries.
  • Compatible with param and paramauto for evaluation and dependency handling.

Examples

Basic Usage

s = struct(a=1, b=2, c='${a} + ${b} # evaluate me if you can')
print(s.a)  # 1
s.d = 11    # Append a new field
delattr(s, 'd')  # Delete the field

Using param for Evaluation

p = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can')
p.eval()
# Output:
# --------
#      a: 1
#      b: 2
#      c: ${a} + ${b} # evaluate me if you can (= 3)
# --------

Concatenation and Subtraction

Fields from the right-most structure overwrite existing values.

a = struct(a=1, b=2)
b = struct(c=3, d="d", e="e")
c = a + b
e = c - a

Practical Shorthands

Constructing a Structure from Keys

s = struct.fromkeys(["a", "b", "c", "d"])
# Output:
# --------
#      a: None
#      b: None
#      c: None
#      d: None
# --------

Building a Structure from Variables in a String

s = struct.scan("${a} + ${b} * ${c} / ${d} --- ${ee}")
s.a = 1
s.b = "test"
s.c = [1, "a", 2]
s.generator()
# Output:
# X = struct(
#      a=1,
#      b="test",
#      c=[1, 'a', 2],
#      d=None,
#      ee=None
# )

Indexing and Iteration

Structures can be indexed or sliced like lists.

c = a + b
c[0]      # Access the first field
c[-1]     # Access the last field
c[:2]     # Slice the structure
for field in c:
    print(field)

Dynamic Dependency Management

struct provides control over dependencies, sorting, and evaluation.

s = struct(d=3, e="${c} + {d}", c='${a} + ${b}', a=1, b=2)
s.sortdefinitions()
# Output:
# --------
#      d: 3
#      a: 1
#      b: 2
#      c: ${a} + ${b}
#      e: ${c} + ${d}
# --------

For dynamic evaluation, use param:

p = param(sortdefinitions=True, d=3, e="${c} + ${d}", c='${a} + ${b}', a=1, b=2)
# Output:
# --------
#      d: 3
#      a: 1
#      b: 2
#      c: ${a} + ${b}  (= 3)
#      e: ${c} + ${d}  (= 6)
# --------

Overloaded Methods and Operators

Supported Operators

  • +: Concatenation of two structures (__add__).
  • -: Subtraction of fields (__sub__).
  • len(): Number of fields (__len__).
  • in: Check for field existence (__contains__).

Method Overview

Method Description
check(default) Populate fields with defaults if missing.
clear() Remove all fields.
dict2struct(dico) Create a structure from a dictionary.
disp() Display the structure.
eval() Evaluate expressions within fields.
fromkeys(keys) Create a structure from a list of keys.
generator() Generate Python code representing the structure.
items() Return key-value pairs.
keys() Return all keys in the structure.
read(file) Load structure fields from a file.
scan(string) Extract variables from a string and populate fields.
sortdefinitions() Sort fields to resolve dependencies.
struct2dict() Convert the structure to a dictionary.
values() Return all field values.
write(file) Save the structure to a file.

Dynamic Properties

Property Description
isempty True if the structure is empty.
isdefined True if all fields are defined.

constructor

Expand source code
class struct():
    """
    Class: `struct`
    ================

    A lightweight class that mimics Matlab-like structures, with additional features
    such as dynamic field creation, indexing, concatenation, and compatibility with
    evaluated parameters (`param`).

    ---

    ### Features
    - Dynamic creation of fields.
    - Indexing and iteration support for fields.
    - Concatenation and subtraction of structures.
    - Conversion to and from dictionaries.
    - Compatible with `param` and `paramauto` for evaluation and dependency handling.

    ---

    ### Examples

    #### Basic Usage
    ```python
    s = struct(a=1, b=2, c='${a} + ${b} # evaluate me if you can')
    print(s.a)  # 1
    s.d = 11    # Append a new field
    delattr(s, 'd')  # Delete the field
    ```

    #### Using `param` for Evaluation
    ```python
    p = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can')
    p.eval()
    # Output:
    # --------
    #      a: 1
    #      b: 2
    #      c: ${a} + ${b} # evaluate me if you can (= 3)
    # --------
    ```

    ---

    ### Concatenation and Subtraction
    Fields from the right-most structure overwrite existing values.
    ```python
    a = struct(a=1, b=2)
    b = struct(c=3, d="d", e="e")
    c = a + b
    e = c - a
    ```

    ---

    ### Practical Shorthands

    #### Constructing a Structure from Keys
    ```python
    s = struct.fromkeys(["a", "b", "c", "d"])
    # Output:
    # --------
    #      a: None
    #      b: None
    #      c: None
    #      d: None
    # --------
    ```

    #### Building a Structure from Variables in a String
    ```python
    s = struct.scan("${a} + ${b} * ${c} / ${d} --- ${ee}")
    s.a = 1
    s.b = "test"
    s.c = [1, "a", 2]
    s.generator()
    # Output:
    # X = struct(
    #      a=1,
    #      b="test",
    #      c=[1, 'a', 2],
    #      d=None,
    #      ee=None
    # )
    ```

    #### Indexing and Iteration
    Structures can be indexed or sliced like lists.
    ```python
    c = a + b
    c[0]      # Access the first field
    c[-1]     # Access the last field
    c[:2]     # Slice the structure
    for field in c:
        print(field)
    ```

    ---

    ### Dynamic Dependency Management
    `struct` provides control over dependencies, sorting, and evaluation.

    ```python
    s = struct(d=3, e="${c} + {d}", c='${a} + ${b}', a=1, b=2)
    s.sortdefinitions()
    # Output:
    # --------
    #      d: 3
    #      a: 1
    #      b: 2
    #      c: ${a} + ${b}
    #      e: ${c} + ${d}
    # --------
    ```

    For dynamic evaluation, use `param`:
    ```python
    p = param(sortdefinitions=True, d=3, e="${c} + ${d}", c='${a} + ${b}', a=1, b=2)
    # Output:
    # --------
    #      d: 3
    #      a: 1
    #      b: 2
    #      c: ${a} + ${b}  (= 3)
    #      e: ${c} + ${d}  (= 6)
    # --------
    ```

    ---

    ### Overloaded Methods and Operators
    #### Supported Operators
    - `+`: Concatenation of two structures (`__add__`).
    - `-`: Subtraction of fields (`__sub__`).
    - `len()`: Number of fields (`__len__`).
    - `in`: Check for field existence (`__contains__`).

    #### Method Overview
    | Method                | Description                                             |
    |-----------------------|---------------------------------------------------------|
    | `check(default)`      | Populate fields with defaults if missing.               |
    | `clear()`             | Remove all fields.                                      |
    | `dict2struct(dico)`   | Create a structure from a dictionary.                   |
    | `disp()`              | Display the structure.                                  |
    | `eval()`              | Evaluate expressions within fields.                     |
    | `fromkeys(keys)`      | Create a structure from a list of keys.                 |
    | `generator()`         | Generate Python code representing the structure.        |
    | `items()`             | Return key-value pairs.                                 |
    | `keys()`              | Return all keys in the structure.                       |
    | `read(file)`          | Load structure fields from a file.                      |
    | `scan(string)`        | Extract variables from a string and populate fields.    |
    | `sortdefinitions()`   | Sort fields to resolve dependencies.                    |
    | `struct2dict()`       | Convert the structure to a dictionary.                  |
    | `values()`            | Return all field values.                                |
    | `write(file)`         | Save the structure to a file.                           |

    ---

    ### Dynamic Properties
    | Property    | Description                            |
    |-------------|----------------------------------------|
    | `isempty`   | `True` if the structure is empty.      |
    | `isdefined` | `True` if all fields are defined.      |

    ---
    """

    # attributes to be overdefined
    _type = "struct"        # object type
    _fulltype = "structure" # full name
    _ftype = "field"        # field name
    _evalfeature = False    # true if eval() is available
    _maxdisplay = 40        # maximum number of characters to display (should be even)
    _propertyasattribute = False

    # attributes for the iterator method
    # Please keep it static, duplicate the object before changing _iter_
    _iter_ = 0

    # excluded attributes (keep the , in the Tupple if it is singleton)
    _excludedattr = {'_iter_','__class__','_protection','_evaluation','_returnerror'} # used by keys() and len()


    # Methods
    def __init__(self,**kwargs):
        """ constructor """
        # Optionally extend _excludedattr here
        self._excludedattr = self._excludedattr | {'_excludedattr', '_type', '_fulltype','_ftype'} # addition 2024-10-11
        self.set(**kwargs)

    def zip(self):
        """ zip keys and values """
        return zip(self.keys(),self.values())

    @staticmethod
    def dict2struct(dico,makeparam=False):
        """ create a structure from a dictionary """
        if isinstance(dico,dict):
            s = param() if makeparam else struct()
            s.set(**dico)
            return s
        raise TypeError("the argument must be a dictionary")

    def struct2dict(self):
        """ create a dictionary from the current structure """
        return dict(self.zip())

    def struct2param(self,protection=False,evaluation=True):
        """ convert an object struct() to param() """
        p = param(**self.struct2dict())
        for i in range(len(self)):
            if isinstance(self[i],pstr): p[i] = pstr(p[i])
        p._protection = protection
        p._evaluation = evaluation
        return p

    def set(self,**kwargs):
        """ initialization """
        self.__dict__.update(kwargs)

    def setattr(self,key,value):
        """ set field and value """
        if isinstance(value,list) and len(value)==0 and key in self:
            delattr(self, key)
        else:
            self.__dict__[key] = value

    def getattr(self,key):
        """Get attribute override to access both instance attributes and properties if allowed."""
        if key in self.__dict__:
            return self.__dict__[key]
        elif getattr(self, '_propertyasattribute', False) and \
             key not in self._excludedattr and \
             key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property):
            # If _propertyasattribute is True and it's a property, get its value
            return self.__class__.__dict__[key].fget(self)
        else:
            raise AttributeError(f'the {self._ftype} "{key}" does not exist')

    def hasattr(self, key):
        """Return true if the field exists, considering properties as regular attributes if allowed."""
        return key in self.__dict__ or (
            getattr(self, '_propertyasattribute', False) and
            key not in self._excludedattr and
            key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property)
        )

    def __getstate__(self):
        """ getstate for cooperative inheritance / duplication """
        return self.__dict__.copy()

    def __setstate__(self,state):
        """ setstate for cooperative inheritance / duplication """
        self.__dict__.update(state)

    def __getattr__(self,key):
        """ get attribute override """
        return pstr.eval(self.getattr(key))

    def __setattr__(self,key,value):
        """ set attribute override """
        self.setattr(key,value)

    def __contains__(self,item):
        """ in override """
        return self.hasattr(item)

    def keys(self):
        """ return the fields """
        # keys() is used by struct() and its iterator
        return [key for key in self.__dict__.keys() if key not in self._excludedattr]

    def keyssorted(self,reverse=True):
        """ sort keys by length() """
        klist = self.keys()
        l = [len(k) for k in klist]
        return [k for _,k in sorted(zip(l,klist),reverse=reverse)]

    def values(self):
        """ return the values """
        # values() is used by struct() and its iterator
        return [pstr.eval(value) for key,value in self.__dict__.items() if key not in self._excludedattr]

    @staticmethod
    def fromkeysvalues(keys,values,makeparam=False):
        """ struct.keysvalues(keys,values) creates a structure from keys and values
            use makeparam = True to create a param instead of struct
        """
        if keys is None: raise AttributeError("the keys must not empty")
        if not isinstance(keys,(list,tuple,np.ndarray,np.generic)): keys = [keys]
        if not isinstance(values,(list,tuple,np.ndarray,np.generic)): values = [values]
        nk,nv = len(keys), len(values)
        s = param() if makeparam else struct()
        if nk>0 and nv>0:
            iv = 0
            for ik in range(nk):
                s.setattr(keys[ik], values[iv])
                iv = min(nv-1,iv+1)
            for ik in range(nk,nv):
                s.setattr(f"key{ik}", values[ik])
        return s

    def items(self):
        """ return all elements as iterable key, value """
        return self.zip()

    def __getitem__(self,idx):
        """
            s[i] returns the ith element of the structure
            s[:4] returns a structure with the four first fields
            s[[1,3]] returns the second and fourth elements
        """
        if isinstance(idx,int):
            if idx<len(self):
                return self.getattr(self.keys()[idx])
            raise IndexError(f"the {self._ftype} index should be comprised between 0 and {len(self)-1}")
        elif isinstance(idx,slice):
            return struct.fromkeysvalues(self.keys()[idx], self.values()[idx])
        elif isinstance(idx,(list,tuple)):
            k,v= self.keys(), self.values()
            nk = len(k)
            s = param() if isinstance(self,param) else struct()
            for i in idx:
                if isinstance(i,int) and i>=0 and i<nk:
                    s.setattr(k[i],v[i])
                else:
                    raise IndexError("idx must contains only integers ranged between 0 and %d" % (nk-1))
            return s
        elif isinstance(idx,str):
            return self.getattr(idx)
        else:
            raise TypeError("The index must be an integer or a slice and not a %s" % type(idx).__name__)

    def __setitem__(self,idx,value):
        """ set the ith element of the structure  """
        if isinstance(idx,int):
            if idx<len(self):
                self.setattr(self.keys()[idx], value)
            else:
                raise IndexError(f"the {self._ftype} index should be comprised between 0 and {len(self)-1}")
        elif isinstance(idx,slice):
            k = self.keys()[idx]
            if len(value)<=1:
                for i in range(len(k)): self.setattr(k[i], value)
            elif len(k) == len(value):
                for i in range(len(k)): self.setattr(k[i], value[i])
            else:
                raise IndexError("the number of values (%d) does not match the number of elements in the slive (%d)" \
                       % (len(value),len(idx)))
        elif isinstance(idx,(list,tuple)):
            if len(value)<=1:
                for i in range(len(idx)): self[idx[i]]=value
            elif len(idx) == len(value):
                for i in range(len(idx)): self[idx[i]]=value[i]
            else:
                raise IndexError("the number of values (%d) does not match the number of indices (%d)" \
                                 % (len(value),len(idx)))

    def __len__(self):
        """ return the number of fields """
        # len() is used by struct() and its iterator
        return len(self.keys())

    def __iter__(self):
        """ struct iterator """
        # note that in the original object _iter_ is a static property not in dup
        dup = duplicate(self)
        dup._iter_ = 0
        return dup

    def __next__(self):
        """ increment iterator """
        self._iter_ += 1
        if self._iter_<=len(self):
            return self[self._iter_-1]
        self._iter_ = 0
        raise StopIteration(f"Maximum {self._ftype} iteration reached {len(self)}")

    def __add__(self,s,sortdefinitions=False,raiseerror=True, silentmode=True):
        """ add a structure
            set sortdefintions=True to sort definitions (to maintain executability)
        """
        if not isinstance(s,struct):
            raise TypeError(f"the second operand must be {self._type}")
        dup = duplicate(self)
        dup.__dict__.update(s.__dict__)
        if sortdefinitions: dup.sortdefinitions(raiseerror=raiseerror,silentmode=silentmode)
        return dup

    def __iadd__(self,s,sortdefinitions=False,raiseerror=False, silentmode=True):
        """ iadd a structure
            set sortdefintions=True to sort definitions (to maintain executability)
        """
        if not isinstance(s,struct):
            raise TypeError(f"the second operand must be {self._type}")
        self.__dict__.update(s.__dict__)
        if sortdefinitions: self.sortdefinitions(raiseerror=raiseerror,silentmode=silentmode)
        return self

    def __sub__(self,s):
        """ sub a structure """
        if not isinstance(s,struct):
            raise TypeError(f"the second operand must be {self._type}")
        dup = duplicate(self)
        listofkeys = dup.keys()
        for k in s.keys():
            if k in listofkeys:
                delattr(dup,k)
        return dup

    def __isub__(self,s):
        """ isub a structure """
        if not isinstance(s,struct):
            raise TypeError(f"the second operand must be {self._type}")
        listofkeys = self.keys()
        for k in s.keys():
            if k in listofkeys:
                delattr(self,k)
        return self

    def dispmax(self,content):
        """ optimize display """
        strcontent = str(content)
        if len(strcontent)>self._maxdisplay:
            nchar = round(self._maxdisplay/2)
            return strcontent[:nchar]+" [...] "+strcontent[-nchar:]
        else:
            return content

    def __repr__(self):
        """ display method """
        if self.__dict__=={}:
            print(f"empty {self._fulltype} ({self._type} object) with no {self._type}s")
            return f"empty {self._fulltype}"
        else:
            tmp = self.eval() if self._evalfeature else []
            keylengths = [len(key) for key in self.__dict__]
            width = max(10,max(keylengths)+2)
            fmt = "%%%ss:" % width
            fmteval = fmt[:-1]+"="
            fmtcls =  fmt[:-1]+":"
            line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) )
            print(line)
            for key,value in self.__dict__.items():
                if key not in self._excludedattr:
                    if isinstance(value,(int,float,str,list,tuple,np.ndarray,np.generic)):
                        if isinstance(value,pstr):
                            print(fmt % key,'p"'+self.dispmax(value)+'"')
                        if isinstance(value,str) and value=="":
                            print(fmt % key,'""')
                        else:
                            print(fmt % key,self.dispmax(value))
                    elif isinstance(value,struct):
                        print(fmt % key,self.dispmax(value.__str__()))
                    elif isinstance(value,type):
                        print(fmt % key,self.dispmax(str(value)))
                    else:
                        print(fmt % key,type(value))
                        print(fmtcls % "",self.dispmax(str(value)))
                    if self._evalfeature:
                        if isinstance(self,paramauto):
                            try:
                                if isinstance(value,pstr):
                                    print(fmteval % "",'p"'+self.dispmax(tmp.getattr(key))+'"')
                                elif isinstance(value,str):
                                    if value == "":
                                        print(fmteval % "",self.dispmax("<empty string>"))
                                    else:
                                        print(fmteval % "",self.dispmax(tmp.getattr(key)))
                            except Exception as err:
                                print(fmteval % "",err.message, err.args)
                        else:
                            if isinstance(value,pstr):
                                print(fmteval % "",'p"'+self.dispmax(tmp.getattr(key))+'"')
                            elif isinstance(value,str):
                                if value == "":
                                    print(fmteval % "",self.dispmax("<empty string>"))
                                else:
                                    print(fmteval % "",self.dispmax(tmp.getattr(key)))
            print(line)
            return f"{self._fulltype} ({self._type} object) with {len(self)} {self._ftype}s"

    def disp(self):
        """ display method """
        self.__repr__()

    def __str__(self):
        return f"{self._fulltype} ({self._type} object) with {len(self)} {self._ftype}s"

    @property
    def isempty(self):
        """ isempty is set to True for an empty structure """
        return len(self)==0

    def clear(self):
        """ clear() delete all fields while preserving the original class """
        for k in self.keys(): delattr(self,k)

    def format(self,s,escape=False,raiseerror=True):
        """
            format a string with field (use {field} as placeholders)
                s.replace(string), s.replace(string,escape=True)
                where:
                    s is a struct object
                    string is a string with possibly ${variable1}
                    escape is a flag to prevent ${} replaced by {}
        """
        if raiseerror:
            try:
                if escape:
                    return s.format(**self.__dict__)
                else:
                    return s.replace("${","{").format(**self.__dict__)
            except KeyError as kerr:
                s_ = s.replace("{","${")
                print(f"WARNING: the {self._ftype} {kerr} is undefined in '{s_}'")
                return s_ # instead of s (we put back $) - OV 2023/01/27
            except Exception as othererr:
                s_ = s.replace("{","${")
                raise RuntimeError from othererr
        else:
            if escape:
                return s.format(**self.__dict__)
            else:
                return s.replace("${","{").format(**self.__dict__)

    def fromkeys(self,keys):
        """ returns a structure from keys """
        return self+struct(**dict.fromkeys(keys,None))

    @staticmethod
    def scan(s):
        """ scan(string) scan a string for variables """
        if not isinstance(s,str): raise TypeError("scan() requires a string")
        tmp = struct()
        #return tmp.fromkeys(set(re.findall(r"\$\{(.*?)\}",s)))
        found = re.findall(r"\$\{(.*?)\}",s);
        uniq = []
        for x in found:
            if x not in uniq: uniq.append(x)
        return tmp.fromkeys(uniq)

    @staticmethod
    def isstrexpression(s):
        """ isstrexpression(string) returns true if s contains an expression  """
        if not isinstance(s,str): raise TypeError("s must a string")
        return re.search(r"\$\{.*?\}",s) is not None

    @property
    def isexpression(self):
        """ same structure with True if it is an expression """
        s = param() if isinstance(self,param) else struct()
        for k,v in self.items():
            if isinstance(v,str):
                s.setattr(k,struct.isstrexpression(v))
            else:
                s.setattr(k,False)
        return s

    @staticmethod
    def isstrdefined(s,ref):
        """ isstrdefined(string,ref) returns true if it is defined in ref  """
        if not isinstance(s,str): raise TypeError("s must a string")
        if not isinstance(ref,struct): raise TypeError("ref must be a structure")
        if struct.isstrexpression(s):
            k = struct.scan(s).keys()
            allfound,i,nk = True,0,len(k)
            while (i<nk) and allfound:
                allfound = k[i] in ref
                i += 1
            return allfound
        else:
            return False


    def isdefined(self,ref=None):
        """ isdefined(ref) returns true if it is defined in ref """
        s = param() if isinstance(self,param) else struct()
        k,v,isexpr = self.keys(), self.values(), self.isexpression.values()
        nk = len(k)
        if ref is None:
            for i in range(nk):
                if isexpr[i]:
                    s.setattr(k[i],struct.isstrdefined(v[i],self[:i]))
                else:
                    s.setattr(k[i],True)
        else:
            if not isinstance(ref,struct): raise TypeError("ref must be a structure")
            for i in range(nk):
                if isexpr[i]:
                    s.setattr(k[i],struct.isstrdefined(v[i],ref))
                else:
                    s.setattr(k[i],True)
        return s

    def sortdefinitions(self,raiseerror=True,silentmode=False):
        """ sortdefintions sorts all definitions
            so that they can be executed as param().
            If any inconsistency is found, an error message is generated.

            Flags = default values
                raiseerror=True show erros of True
                silentmode=False no warning if True
        """
        find = lambda xlist: [i for i, x in enumerate(xlist) if x]
        findnot = lambda xlist: [i for i, x in enumerate(xlist) if not x]
        k,v,isexpr =  self.keys(), self.values(), self.isexpression.values()
        istatic = findnot(isexpr)
        idynamic = find(isexpr)
        static = struct.fromkeysvalues(
            [ k[i] for i in istatic ],
            [ v[i] for i in istatic ],
            makeparam = False)
        dynamic = struct.fromkeysvalues(
            [ k[i] for i in idynamic ],
            [ v[i] for i in idynamic ],
            makeparam=False)
        current = static # make static the current structure
        nmissing, anychange, errorfound = len(dynamic), False, False
        while nmissing:
            itst, found = 0, False
            while itst<nmissing and not found:
                teststruct = current + dynamic[[itst]] # add the test field
                found = all(list(teststruct.isdefined()))
                ifound = itst
                itst += 1
            if found:
                current = teststruct # we accept the new field
                dynamic[ifound] = []
                nmissing -= 1
                anychange = True
            else:
                if raiseerror:
                    raise KeyError('unable to interpret %d/%d expressions in "%ss"' % \
                                   (nmissing,len(self),self._ftype))
                else:
                    if (not errorfound) and (not silentmode):
                        print('WARNING: unable to interpret %d/%d expressions in "%ss"' % \
                              (nmissing,len(self),self._ftype))
                    current = teststruct # we accept the new field (even if it cannot be interpreted)
                    dynamic[ifound] = []
                    nmissing -= 1
                    errorfound = True
        if anychange:
            self.clear() # reset all fields and assign them in the proper order
            k,v = current.keys(), current.values()
            for i in range(len(k)):
                self.setattr(k[i],v[i])

    def generator(self):
        """ generate Python code of the equivalent structure """
        nk = len(self)
        if nk==0:
            print("X = struct()")
        else:
            ik = 0
            fmt = "%%%ss=" % max(10,max([len(k) for k in self.keys()])+2)
            print("\nX = struct(")
            for k in self.keys():
                ik += 1
                end = ",\n" if ik<nk else "\n"+(fmt[:-1] % ")")+"\n"
                v = getattr(self,k)
                if isinstance(v,(int,float)) or v == None:
                    print(fmt % k,v,end=end)
                elif isinstance(v,str):
                    print(fmt % k,f'"{v}"',end=end)
                elif isinstance(v,(list,tuple)):
                    print(fmt % k,v,end=end)
                else:
                    print(fmt % k,"/* unsupported type */",end=end)

    # copy and deep copy methpds for the class
    def __copy__(self):
        """ copy method """
        cls = self.__class__
        copie = cls.__new__(cls)
        copie.__dict__.update(self.__dict__)
        return copie

    def __deepcopy__(self, memo):
        """ deep copy method """
        cls = self.__class__
        copie = cls.__new__(cls)
        memo[id(self)] = copie
        for k, v in self.__dict__.items():
            setattr(copie, k, duplicatedeep(v, memo))
        return copie


    # write a file
    def write(self, file, overwrite=True, mkdir=False):
        """
            write the equivalent structure (not recursive for nested struct)
                write(filename, overwrite=True, mkdir=False)

            Parameters:
            - file: The file path to write to.
            - overwrite: Whether to overwrite the file if it exists (default: True).
            - mkdir: Whether to create the directory if it doesn't exist (default: False).
        """
        # Create a Path object for the file to handle cross-platform paths
        file_path = Path(file).resolve()

        # Check if the directory exists or if mkdir is set to True, create it
        if mkdir:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        elif not file_path.parent.exists():
            raise FileNotFoundError(f"The directory {file_path.parent} does not exist.")
        # If overwrite is False and the file already exists, raise an exception
        if not overwrite and file_path.exists():
            raise FileExistsError(f"The file {file_path} already exists, and overwrite is set to False.")
        # Open and write to the file using the resolved path
        with file_path.open(mode="w", encoding='utf-8') as f:
            print(f"# {self._fulltype} with {len(self)} {self._ftype}s\n", file=f)
            for k, v in self.items():
                if v is None:
                    print(k, "=None", file=f, sep="")
                elif isinstance(v, (int, float)):
                    print(k, "=", v, file=f, sep="")
                elif isinstance(v, str):
                    print(k, '="', v, '"', file=f, sep="")
                else:
                    print(k, "=", str(v), file=f, sep="")


    # read a file
    @staticmethod
    def read(file):
        """
            read the equivalent structure
                read(filename)

            Parameters:
            - file: The file path to read from.
        """
        # Create a Path object for the file to handle cross-platform paths
        file_path = Path(file).resolve()
        # Check if the parent directory exists, otherwise raise an error
        if not file_path.parent.exists():
            raise FileNotFoundError(f"The directory {file_path.parent} does not exist.")
        # If the file does not exist, raise an exception
        if not file_path.exists():
            raise FileNotFoundError(f"The file {file_path} does not exist.")
        # Open and read the file
        with file_path.open(mode="r", encoding="utf-8") as f:
            s = struct()  # Assuming struct is defined elsewhere
            while True:
                line = f.readline()
                if not line:
                    break
                line = line.strip()
                expr = line.split(sep="=")
                if len(line) > 0 and line[0] != "#" and len(expr) > 0:
                    lhs = expr[0]
                    rhs = "".join(expr[1:]).strip()
                    if len(rhs) == 0 or rhs == "None":
                        v = None
                    else:
                        v = eval(rhs)
                    s.setattr(lhs, v)
        return s

    # argcheck
    def check(self,default):
        """
        populate fields from a default structure
            check(defaultstruct)
            missing field, None and [] values are replaced by default ones

            Note: a.check(b) is equivalent to b+a except for [] and None values
        """
        if not isinstance(default,struct):
            raise TypeError("the first argument must be a structure")
        for f in default.keys():
            ref = default.getattr(f)
            if f not in self:
                self.setattr(f, ref)
            else:
                current = self.getattr(f)
                if ((current is None)  or (current==[])) and \
                    ((ref is not None) and (ref!=[])):
                        self.setattr(f, ref)


    # update values based on key:value
    def update(self, **kwargs):
        """
        Update multiple fields at once, while protecting certain attributes.

        Parameters:
        -----------
        **kwargs : dict
            The fields to update and their new values.

        Protected attributes defined in _excludedattr are not updated.

        Usage:
        ------
        s.update(a=10, b=[1, 2, 3], new_field="new_value")
        """
        protected_attributes = getattr(self, '_excludedattr', ())

        for key, value in kwargs.items():
            if key in protected_attributes:
                print(f"Warning: Cannot update protected attribute '{key}'")
            else:
                self.setattr(key, value)


    # override () for subindexing structure with key names
    def __call__(self, *keys):
        """
        Extract a sub-structure based on the specified keys,
        keeping the same class type.

        Parameters:
        -----------
        *keys : str
            The keys for the fields to include in the sub-structure.

        Returns:
        --------
        struct
            A new instance of the same class as the original, containing
            only the specified keys.

        Usage:
        ------
        sub_struct = s('key1', 'key2', ...)
        """
        # Create a new instance of the same class
        sub_struct = self.__class__()

        # Get the full type and field type for error messages
        fulltype = getattr(self, '_fulltype', 'structure')
        ftype = getattr(self, '_ftype', 'field')

        # Add only the specified keys to the new sub-structure
        for key in keys:
            if key in self:
                sub_struct.setattr(key, self.getattr(key))
            else:
                raise KeyError(f"{fulltype} does not contain the {ftype} '{key}'.")

        return sub_struct


    def __delattr__(self, key):
        """ Delete an instance attribute if it exists and is not a class or excluded attribute. """
        if key in self._excludedattr:
            raise AttributeError(f"Cannot delete excluded attribute '{key}'")
        elif key in self.__class__.__dict__:  # Check if it's a class attribute
            raise AttributeError(f"Cannot delete class attribute '{key}'")
        elif key in self.__dict__:  # Delete only if in instance's __dict__
            del self.__dict__[key]
        else:
            raise AttributeError(f"{self._type} has no attribute '{key}'")

Subclasses

  • pizza.private.mstruct.param
  • pizza.raster.collection
  • pizza.region.regioncollection
  • pizza.script.scriptobject
  • pizza.script.scriptobjectgroup

Static methods

def dict2struct(dico, makeparam=False)

create a structure from a dictionary

Expand source code
@staticmethod
def dict2struct(dico,makeparam=False):
    """ create a structure from a dictionary """
    if isinstance(dico,dict):
        s = param() if makeparam else struct()
        s.set(**dico)
        return s
    raise TypeError("the argument must be a dictionary")
def fromkeysvalues(keys, values, makeparam=False)

struct.keysvalues(keys,values) creates a structure from keys and values use makeparam = True to create a param instead of struct

Expand source code
@staticmethod
def fromkeysvalues(keys,values,makeparam=False):
    """ struct.keysvalues(keys,values) creates a structure from keys and values
        use makeparam = True to create a param instead of struct
    """
    if keys is None: raise AttributeError("the keys must not empty")
    if not isinstance(keys,(list,tuple,np.ndarray,np.generic)): keys = [keys]
    if not isinstance(values,(list,tuple,np.ndarray,np.generic)): values = [values]
    nk,nv = len(keys), len(values)
    s = param() if makeparam else struct()
    if nk>0 and nv>0:
        iv = 0
        for ik in range(nk):
            s.setattr(keys[ik], values[iv])
            iv = min(nv-1,iv+1)
        for ik in range(nk,nv):
            s.setattr(f"key{ik}", values[ik])
    return s
def isstrdefined(s, ref)

isstrdefined(string,ref) returns true if it is defined in ref

Expand source code
@staticmethod
def isstrdefined(s,ref):
    """ isstrdefined(string,ref) returns true if it is defined in ref  """
    if not isinstance(s,str): raise TypeError("s must a string")
    if not isinstance(ref,struct): raise TypeError("ref must be a structure")
    if struct.isstrexpression(s):
        k = struct.scan(s).keys()
        allfound,i,nk = True,0,len(k)
        while (i<nk) and allfound:
            allfound = k[i] in ref
            i += 1
        return allfound
    else:
        return False
def isstrexpression(s)

isstrexpression(string) returns true if s contains an expression

Expand source code
@staticmethod
def isstrexpression(s):
    """ isstrexpression(string) returns true if s contains an expression  """
    if not isinstance(s,str): raise TypeError("s must a string")
    return re.search(r"\$\{.*?\}",s) is not None
def read(file)

read the equivalent structure read(filename)

Parameters: - file: The file path to read from.

Expand source code
@staticmethod
def read(file):
    """
        read the equivalent structure
            read(filename)

        Parameters:
        - file: The file path to read from.
    """
    # Create a Path object for the file to handle cross-platform paths
    file_path = Path(file).resolve()
    # Check if the parent directory exists, otherwise raise an error
    if not file_path.parent.exists():
        raise FileNotFoundError(f"The directory {file_path.parent} does not exist.")
    # If the file does not exist, raise an exception
    if not file_path.exists():
        raise FileNotFoundError(f"The file {file_path} does not exist.")
    # Open and read the file
    with file_path.open(mode="r", encoding="utf-8") as f:
        s = struct()  # Assuming struct is defined elsewhere
        while True:
            line = f.readline()
            if not line:
                break
            line = line.strip()
            expr = line.split(sep="=")
            if len(line) > 0 and line[0] != "#" and len(expr) > 0:
                lhs = expr[0]
                rhs = "".join(expr[1:]).strip()
                if len(rhs) == 0 or rhs == "None":
                    v = None
                else:
                    v = eval(rhs)
                s.setattr(lhs, v)
    return s
def scan(s)

scan(string) scan a string for variables

Expand source code
@staticmethod
def scan(s):
    """ scan(string) scan a string for variables """
    if not isinstance(s,str): raise TypeError("scan() requires a string")
    tmp = struct()
    #return tmp.fromkeys(set(re.findall(r"\$\{(.*?)\}",s)))
    found = re.findall(r"\$\{(.*?)\}",s);
    uniq = []
    for x in found:
        if x not in uniq: uniq.append(x)
    return tmp.fromkeys(uniq)

Instance variables

var isempty

isempty is set to True for an empty structure

Expand source code
@property
def isempty(self):
    """ isempty is set to True for an empty structure """
    return len(self)==0
var isexpression

same structure with True if it is an expression

Expand source code
@property
def isexpression(self):
    """ same structure with True if it is an expression """
    s = param() if isinstance(self,param) else struct()
    for k,v in self.items():
        if isinstance(v,str):
            s.setattr(k,struct.isstrexpression(v))
        else:
            s.setattr(k,False)
    return s

Methods

def check(self, default)

populate fields from a default structure check(defaultstruct) missing field, None and [] values are replaced by default ones

Note: a.check(b) is equivalent to b+a except for [] and None values
Expand source code
def check(self,default):
    """
    populate fields from a default structure
        check(defaultstruct)
        missing field, None and [] values are replaced by default ones

        Note: a.check(b) is equivalent to b+a except for [] and None values
    """
    if not isinstance(default,struct):
        raise TypeError("the first argument must be a structure")
    for f in default.keys():
        ref = default.getattr(f)
        if f not in self:
            self.setattr(f, ref)
        else:
            current = self.getattr(f)
            if ((current is None)  or (current==[])) and \
                ((ref is not None) and (ref!=[])):
                    self.setattr(f, ref)
def clear(self)

clear() delete all fields while preserving the original class

Expand source code
def clear(self):
    """ clear() delete all fields while preserving the original class """
    for k in self.keys(): delattr(self,k)
def disp(self)

display method

Expand source code
def disp(self):
    """ display method """
    self.__repr__()
def dispmax(self, content)

optimize display

Expand source code
def dispmax(self,content):
    """ optimize display """
    strcontent = str(content)
    if len(strcontent)>self._maxdisplay:
        nchar = round(self._maxdisplay/2)
        return strcontent[:nchar]+" [...] "+strcontent[-nchar:]
    else:
        return content
def format(self, s, escape=False, raiseerror=True)

format a string with field (use {field} as placeholders) s.replace(string), s.replace(string,escape=True) where: s is a struct object string is a string with possibly ${variable1} escape is a flag to prevent ${} replaced by {}

Expand source code
def format(self,s,escape=False,raiseerror=True):
    """
        format a string with field (use {field} as placeholders)
            s.replace(string), s.replace(string,escape=True)
            where:
                s is a struct object
                string is a string with possibly ${variable1}
                escape is a flag to prevent ${} replaced by {}
    """
    if raiseerror:
        try:
            if escape:
                return s.format(**self.__dict__)
            else:
                return s.replace("${","{").format(**self.__dict__)
        except KeyError as kerr:
            s_ = s.replace("{","${")
            print(f"WARNING: the {self._ftype} {kerr} is undefined in '{s_}'")
            return s_ # instead of s (we put back $) - OV 2023/01/27
        except Exception as othererr:
            s_ = s.replace("{","${")
            raise RuntimeError from othererr
    else:
        if escape:
            return s.format(**self.__dict__)
        else:
            return s.replace("${","{").format(**self.__dict__)
def fromkeys(self, keys)

returns a structure from keys

Expand source code
def fromkeys(self,keys):
    """ returns a structure from keys """
    return self+struct(**dict.fromkeys(keys,None))
def generator(self)

generate Python code of the equivalent structure

Expand source code
def generator(self):
    """ generate Python code of the equivalent structure """
    nk = len(self)
    if nk==0:
        print("X = struct()")
    else:
        ik = 0
        fmt = "%%%ss=" % max(10,max([len(k) for k in self.keys()])+2)
        print("\nX = struct(")
        for k in self.keys():
            ik += 1
            end = ",\n" if ik<nk else "\n"+(fmt[:-1] % ")")+"\n"
            v = getattr(self,k)
            if isinstance(v,(int,float)) or v == None:
                print(fmt % k,v,end=end)
            elif isinstance(v,str):
                print(fmt % k,f'"{v}"',end=end)
            elif isinstance(v,(list,tuple)):
                print(fmt % k,v,end=end)
            else:
                print(fmt % k,"/* unsupported type */",end=end)
def getattr(self, key)

Get attribute override to access both instance attributes and properties if allowed.

Expand source code
def getattr(self,key):
    """Get attribute override to access both instance attributes and properties if allowed."""
    if key in self.__dict__:
        return self.__dict__[key]
    elif getattr(self, '_propertyasattribute', False) and \
         key not in self._excludedattr and \
         key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property):
        # If _propertyasattribute is True and it's a property, get its value
        return self.__class__.__dict__[key].fget(self)
    else:
        raise AttributeError(f'the {self._ftype} "{key}" does not exist')
def hasattr(self, key)

Return true if the field exists, considering properties as regular attributes if allowed.

Expand source code
def hasattr(self, key):
    """Return true if the field exists, considering properties as regular attributes if allowed."""
    return key in self.__dict__ or (
        getattr(self, '_propertyasattribute', False) and
        key not in self._excludedattr and
        key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property)
    )
def isdefined(self, ref=None)

isdefined(ref) returns true if it is defined in ref

Expand source code
def isdefined(self,ref=None):
    """ isdefined(ref) returns true if it is defined in ref """
    s = param() if isinstance(self,param) else struct()
    k,v,isexpr = self.keys(), self.values(), self.isexpression.values()
    nk = len(k)
    if ref is None:
        for i in range(nk):
            if isexpr[i]:
                s.setattr(k[i],struct.isstrdefined(v[i],self[:i]))
            else:
                s.setattr(k[i],True)
    else:
        if not isinstance(ref,struct): raise TypeError("ref must be a structure")
        for i in range(nk):
            if isexpr[i]:
                s.setattr(k[i],struct.isstrdefined(v[i],ref))
            else:
                s.setattr(k[i],True)
    return s
def items(self)

return all elements as iterable key, value

Expand source code
def items(self):
    """ return all elements as iterable key, value """
    return self.zip()
def keys(self)

return the fields

Expand source code
def keys(self):
    """ return the fields """
    # keys() is used by struct() and its iterator
    return [key for key in self.__dict__.keys() if key not in self._excludedattr]
def keyssorted(self, reverse=True)

sort keys by length()

Expand source code
def keyssorted(self,reverse=True):
    """ sort keys by length() """
    klist = self.keys()
    l = [len(k) for k in klist]
    return [k for _,k in sorted(zip(l,klist),reverse=reverse)]
def set(self, **kwargs)

initialization

Expand source code
def set(self,**kwargs):
    """ initialization """
    self.__dict__.update(kwargs)
def setattr(self, key, value)

set field and value

Expand source code
def setattr(self,key,value):
    """ set field and value """
    if isinstance(value,list) and len(value)==0 and key in self:
        delattr(self, key)
    else:
        self.__dict__[key] = value
def sortdefinitions(self, raiseerror=True, silentmode=False)

sortdefintions sorts all definitions so that they can be executed as param(). If any inconsistency is found, an error message is generated.

Flags = default values raiseerror=True show erros of True silentmode=False no warning if True

Expand source code
def sortdefinitions(self,raiseerror=True,silentmode=False):
    """ sortdefintions sorts all definitions
        so that they can be executed as param().
        If any inconsistency is found, an error message is generated.

        Flags = default values
            raiseerror=True show erros of True
            silentmode=False no warning if True
    """
    find = lambda xlist: [i for i, x in enumerate(xlist) if x]
    findnot = lambda xlist: [i for i, x in enumerate(xlist) if not x]
    k,v,isexpr =  self.keys(), self.values(), self.isexpression.values()
    istatic = findnot(isexpr)
    idynamic = find(isexpr)
    static = struct.fromkeysvalues(
        [ k[i] for i in istatic ],
        [ v[i] for i in istatic ],
        makeparam = False)
    dynamic = struct.fromkeysvalues(
        [ k[i] for i in idynamic ],
        [ v[i] for i in idynamic ],
        makeparam=False)
    current = static # make static the current structure
    nmissing, anychange, errorfound = len(dynamic), False, False
    while nmissing:
        itst, found = 0, False
        while itst<nmissing and not found:
            teststruct = current + dynamic[[itst]] # add the test field
            found = all(list(teststruct.isdefined()))
            ifound = itst
            itst += 1
        if found:
            current = teststruct # we accept the new field
            dynamic[ifound] = []
            nmissing -= 1
            anychange = True
        else:
            if raiseerror:
                raise KeyError('unable to interpret %d/%d expressions in "%ss"' % \
                               (nmissing,len(self),self._ftype))
            else:
                if (not errorfound) and (not silentmode):
                    print('WARNING: unable to interpret %d/%d expressions in "%ss"' % \
                          (nmissing,len(self),self._ftype))
                current = teststruct # we accept the new field (even if it cannot be interpreted)
                dynamic[ifound] = []
                nmissing -= 1
                errorfound = True
    if anychange:
        self.clear() # reset all fields and assign them in the proper order
        k,v = current.keys(), current.values()
        for i in range(len(k)):
            self.setattr(k[i],v[i])
def struct2dict(self)

create a dictionary from the current structure

Expand source code
def struct2dict(self):
    """ create a dictionary from the current structure """
    return dict(self.zip())
def struct2param(self, protection=False, evaluation=True)

convert an object struct() to param()

Expand source code
def struct2param(self,protection=False,evaluation=True):
    """ convert an object struct() to param() """
    p = param(**self.struct2dict())
    for i in range(len(self)):
        if isinstance(self[i],pstr): p[i] = pstr(p[i])
    p._protection = protection
    p._evaluation = evaluation
    return p
def update(self, **kwargs)

Update multiple fields at once, while protecting certain attributes.

Parameters:

**kwargs : dict The fields to update and their new values.

Protected attributes defined in _excludedattr are not updated.

Usage:

s.update(a=10, b=[1, 2, 3], new_field="new_value")

Expand source code
def update(self, **kwargs):
    """
    Update multiple fields at once, while protecting certain attributes.

    Parameters:
    -----------
    **kwargs : dict
        The fields to update and their new values.

    Protected attributes defined in _excludedattr are not updated.

    Usage:
    ------
    s.update(a=10, b=[1, 2, 3], new_field="new_value")
    """
    protected_attributes = getattr(self, '_excludedattr', ())

    for key, value in kwargs.items():
        if key in protected_attributes:
            print(f"Warning: Cannot update protected attribute '{key}'")
        else:
            self.setattr(key, value)
def values(self)

return the values

Expand source code
def values(self):
    """ return the values """
    # values() is used by struct() and its iterator
    return [pstr.eval(value) for key,value in self.__dict__.items() if key not in self._excludedattr]
def write(self, file, overwrite=True, mkdir=False)

write the equivalent structure (not recursive for nested struct) write(filename, overwrite=True, mkdir=False)

Parameters: - file: The file path to write to. - overwrite: Whether to overwrite the file if it exists (default: True). - mkdir: Whether to create the directory if it doesn't exist (default: False).

Expand source code
def write(self, file, overwrite=True, mkdir=False):
    """
        write the equivalent structure (not recursive for nested struct)
            write(filename, overwrite=True, mkdir=False)

        Parameters:
        - file: The file path to write to.
        - overwrite: Whether to overwrite the file if it exists (default: True).
        - mkdir: Whether to create the directory if it doesn't exist (default: False).
    """
    # Create a Path object for the file to handle cross-platform paths
    file_path = Path(file).resolve()

    # Check if the directory exists or if mkdir is set to True, create it
    if mkdir:
        file_path.parent.mkdir(parents=True, exist_ok=True)
    elif not file_path.parent.exists():
        raise FileNotFoundError(f"The directory {file_path.parent} does not exist.")
    # If overwrite is False and the file already exists, raise an exception
    if not overwrite and file_path.exists():
        raise FileExistsError(f"The file {file_path} already exists, and overwrite is set to False.")
    # Open and write to the file using the resolved path
    with file_path.open(mode="w", encoding='utf-8') as f:
        print(f"# {self._fulltype} with {len(self)} {self._ftype}s\n", file=f)
        for k, v in self.items():
            if v is None:
                print(k, "=None", file=f, sep="")
            elif isinstance(v, (int, float)):
                print(k, "=", v, file=f, sep="")
            elif isinstance(v, str):
                print(k, '="', v, '"', file=f, sep="")
            else:
                print(k, "=", str(v), file=f, sep="")
def zip(self)

zip keys and values

Expand source code
def zip(self):
    """ zip keys and values """
    return zip(self.keys(),self.values())
class tlsph

SMD:TLSPH forcefield (total Lagrangian)

Expand source code
class tlsph(smd):
    """ SMD:TLSPH forcefield (total Lagrangian) """
    name = smd.name + struct(style="tlsph")
    description = smd.description + struct(style="SMD:TLSPH - total Lagrangian for solids")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Diagonal pair coefficient tlsph
    pair_coeff      %d %d smd/tlsph *COMMON ${rho} ${E} ${nu} ${q1} ${q2} ${Hg} ${Cp} &
                    *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening} &
                    *EOS_LINEAR &
                    *END
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """

Ancestors

Subclasses

Class variables

var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name

Inherited members

class ulsph

SMD:ULSPH forcefield (updated Lagrangian)

Expand source code
class ulsph(smd):
    """ SMD:ULSPH forcefield (updated Lagrangian) """
    name = smd.name + struct(style="ulsph")
    description = smd.description + struct(style="SMD:ULSPH - updated Lagrangian for fluids - SPH-like")

    # style definition (LAMMPS code between triple """)
    PAIR_DIAGCOEFF = """
    # [comment] Pair diagonal coefficient ulsph
    pair_coeff      %d %d smd/ulsph *COMMON ${rho} ${c0} ${q1} ${Cp} 0 &
                    *EOS_TAIT ${taitexponent} &
                    *END
    """
    PAIR_OFFDIAGCOEFF = """
    # [comment] Off-diagonal pair coefficient (generic)
    pair_coeff      %d %d smd/hertz ${contact_stiffness}
    """

Ancestors

Subclasses

Class variables

var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name

Inherited members

class water (beadtype=1, userid=None, USER=forcefield (FF object) with 0 parameters)

water material (smd:ulsph): generic water model water() water(beadtype=index, userid="myfluid", USER=…)

override any propery with USER.parameter (set only the parameters you want to override) USER.rho: density in kg/m3 (default=1000) USER.c0: speed of the sound in m/s (default=10.0) USER.q1: standard artificial viscosity linear coefficient (default=1.0) USER.Cp: heat capacity of material – not used here (default=1.0) USER.contact_scale: scaling coefficient for contact (default=1.5) USER.contact_stiffness: contact stifness in Pa (default="2.5${c0}^2${rho}")

water forcefield: water(beadtype=index, userid="myfluid")

Expand source code
class water(ulsph):
    """ water material (smd:ulsph): generic water model
            water()
            water(beadtype=index, userid="myfluid", USER=...)

            override any propery with USER.parameter (set only the parameters you want to override)
                USER.rho: density in kg/m3 (default=1000)
                USER.c0: speed of the sound in m/s (default=10.0)
                USER.q1: standard artificial viscosity linear coefficient (default=1.0)
                USER.Cp: heat capacity of material -- not used here (default=1.0)
                USER.contact_scale: scaling coefficient for contact (default=1.5)
                USER.contact_stiffness: contact stifness in Pa (default="2.5*${c0}^2*${rho}")
    """
    name = ulsph.name + struct(material="water")
    description = ulsph.description + struct(material="water beads - SPH-like")
    userid = 'water'
    version = 0.1

    # constructor (do not forgert to include the constuctor)
    def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()):
        """ water forcefield:
            water(beadtype=index, userid="myfluid") """
        if userid!=None: self.userid = userid
        self.beadtype = beadtype
        self.parameters = parameterforcefield(
            # water-water interactions
            rho = 1000,
            c0 = 10.0,
            q1 = 1.0,
            Cp = 1.0,
            taitexponent = 7,
            # hertz contacts
            contact_scale = 1.5,
            contact_stiffness = '2.5*${c0}^2*${rho}'
            ) + USER # update with user properties if any

Ancestors

Class variables

var description
var name
var userid
var version

Inherited members