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 currentparameters
,beadtype
, anduserid
.pair_diagcoeff(printflag=True, i=None)
: Returns the diagonal pair coefficient command based on the current bead typei
anduserid
. 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 typei
and the interacting forcefieldo
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 objecto
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 typei
can be overridden by passing it as an argument. The method supports returning the raw template without evaluation and modifying parameters using aUSER
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 usingUSER
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 typestruct
or derived fromstruct
.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 objecto
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 usingUSER
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 typestruct
or derived fromstruct
. IndexError If the first argumento
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 thebeadtype
anduserid
arguments. The behavior can be altered by passing aUSER
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 typestruct
or derived fromstruct
.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
- This class is intended to be extended by specific forcefield types such as
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. Theparamauto
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 twoparamauto
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 thanparam
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
- Inherits all functionalities of
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
andparamauto
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 Evaluationp = 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