Module region
================================================================================ REGION Module Documentation ================================================================================
Project: Pizza3 Authors: Olivier Vitrac, Han Chen Copyright: 2024 Credits: Olivier Vitrac, Han Chen License: GPLv3 Maintainer: Olivier Vitrac Email: olivier.vitrac@agroparistech.fr Version: 0.9999
Overview
The REGION module provides a suite of tools to define and manipulate native geometries in Python for LAMMPS (Large-scale Atomic/Molecular Massively Parallel Simulator). It is designed to facilitate the creation, concatenation, and manipulation of geometric regions used in molecular dynamics simulations.
Public Features
- Concatenation of Regions:
R1 + R2
concatenates two regions (objects of R2 are inherited in R1, higher precedence for R2).- Generation of Objects:
R.do()
generates the objects (similar functionality todo()
in pizza.script).- Script Generation:
R.script
returns the script (similar functionality toscript
in pizza.script).- Object Deletion:
R.o1 = []
deletes objecto1
.- Union of Objects:
R.union(o1, o2, name=...)
creates a union ofo1
ando2
(in the LAMMPS sense, see region manual).
Classes And Methods
Class: region
Description:
The region
class is used to define and manipulate geometries for LAMMPS simulations. It includes
methods for creating different geometric shapes, combining regions, and generating corresponding LAMMPS scripts.
Methods:
__init__(self, name='', width=0, height=0, depth=0, regionunits='lattice', separationdistance=0.0, lattice_scale=1.0)
- Description: Initializes a new region with specified dimensions and parameters.
-
Parameters:
name
(str): The name of the region.width
(float): Width of the region.height
(float): Height of the region.depth
(float): Depth of the region.regionunits
(str): Units of the region dimensions ('lattice' or 'si').separationdistance
(float): Separation distance between regions.lattice_scale
(float): Scale of the lattice.
-
do(self)
- Description: Generates the objects within the region.
-
Returns: None
-
script(self)
- Description: Returns the LAMMPS script for the region.
-
Returns: str
-
union(self, o1, o2, name='')
- Description: Creates a union of two objects within the region.
- Parameters:
o1
(object): The first object.o2
(object): The second object.name
(str): The name of the union.
-
Returns: None
-
cylinder(self, name, dim, c1, c2, radius, lo, hi, beadtype)
- Description: Adds a cylinder to the region.
- Parameters:
name
(str): The name of the cylinder.dim
(str): Dimension along which the cylinder is oriented ('x', 'y', or 'z').c1
(float): First coordinate of the center of the cylinder's base.c2
(float): Second coordinate of the center of the cylinder's base.radius
(float): Radius of the cylinder.lo
(float): Lower bound of the cylinder along the specified dimension.hi
(float): Upper bound of the cylinder along the specified dimension.beadtype
(int): Type of beads to use for the cylinder.
-
Returns: None
-
delete(self, name)
- Description: Deletes an object from the region.
- Parameters:
name
(str): The name of the object to delete.
- Returns: None
Examples
Below are some examples demonstrating how to use the REGION module:
-
Concatenating Two Regions:
python R1 = pizza.regions() R2 = pizza.regions() R = R1 + R2
-
Generating Objects:
python R.do()
-
Retrieving the Script:
python script_content = R.script()
-
Deleting an Object:
python R.o1 = []
-
Creating a Union of Objects:
python R.union(o1, o2, name='union_name')
-
Adding a Cylinder: ```python R.cylinder(name='cyl1', dim='z', c1=0, c2=0, radius=1.0, lo=0.0, hi=5.0, beadtype=1)
Advanced Features
Public features (i.e. to be used by the end-user) Let R1, R2 being pizza.regions() R = R1 + R2 concatenates two regions (objects of R2 are inherited in R1, higher precedence for R2) R.do() generate the objects (do() should work as in pizza.script) R.script returns the script (script() should work as in pizza.script)
Let o1, o2 are objects of R
R.o1 = [] delete o1
R.union(o1,o2,name=...) creates an union of o1 and o2 (in the LAMMPS sense, see region manual)
R.intersect(o1,o2,name=...) creates an intersection of o1 and o2 (in the LAMMPS sense, see region manual)
R.eval(expr,name=)
expr any algebraic expression including +
o1+o2+...
Private features (i.e. to be used inside the code) Overloading operators +, +=, | for any coregeometry object Note that coregeometry have four main SECTIONS (scripts) SECTIONS["variables"] SECTIONS["region"] SECTIONS["create"] SECTIONS["group"] SECTIONS["move"] USER, VARIABLES are overdefined as attributes
+, += merge regions (no piping)
| pipe them
Add other geometries: block, sphere, cylinder....
```
Dependencies
- Python 3.x
- LAMMPS
- pizza3.pizza
Installation
To use the REGION module, ensure that you have Python 3.x and LAMMPS installed. You can integrate the module into your project by placing the region.py
file in your working directory or your Python path.
License
This project is licensed under the terms of the GPLv3 license.
Contact
For any queries or contributions, please contact the maintainer: - Olivier Vitrac, Han Chen - Email: olivier.vitrac@agroparistech.fr
Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
================================================================================
REGION Module Documentation
================================================================================
Project: Pizza3
Authors: Olivier Vitrac, Han Chen
Copyright: 2024
Credits: Olivier Vitrac, Han Chen
License: GPLv3
Maintainer: Olivier Vitrac
Email: olivier.vitrac@agroparistech.fr
Version: 0.9999
Overview
--------
The REGION module provides a suite of tools to define and manipulate native geometries in Python
for LAMMPS (Large-scale Atomic/Molecular Massively Parallel Simulator). It is designed to facilitate
the creation, concatenation, and manipulation of geometric regions used in molecular dynamics simulations.
Public Features
---------------
- Concatenation of Regions:
- `R1 + R2` concatenates two regions (objects of R2 are inherited in R1, higher precedence for R2).
- Generation of Objects:
- `R.do()` generates the objects (similar functionality to `do()` in pizza.script).
- Script Generation:
- `R.script` returns the script (similar functionality to `script()` in pizza.script).
- Object Deletion:
- `R.o1 = []` deletes object `o1`.
- Union of Objects:
- `R.union(o1, o2, name=...)` creates a union of `o1` and `o2` (in the LAMMPS sense, see region manual).
Classes and Methods
-------------------
### Class: `region`
#### Description:
The `region` class is used to define and manipulate geometries for LAMMPS simulations. It includes
methods for creating different geometric shapes, combining regions, and generating corresponding LAMMPS scripts.
#### Methods:
- **`__init__(self, name='', width=0, height=0, depth=0, regionunits='lattice', separationdistance=0.0, lattice_scale=1.0)`**
- **Description**: Initializes a new region with specified dimensions and parameters.
- **Parameters**:
- `name` (str): The name of the region.
- `width` (float): Width of the region.
- `height` (float): Height of the region.
- `depth` (float): Depth of the region.
- `regionunits` (str): Units of the region dimensions ('lattice' or 'si').
- `separationdistance` (float): Separation distance between regions.
- `lattice_scale` (float): Scale of the lattice.
- **`do(self)`**
- **Description**: Generates the objects within the region.
- **Returns**: None
- **`script(self)`**
- **Description**: Returns the LAMMPS script for the region.
- **Returns**: str
- **`union(self, o1, o2, name='')`**
- **Description**: Creates a union of two objects within the region.
- **Parameters**:
- `o1` (object): The first object.
- `o2` (object): The second object.
- `name` (str): The name of the union.
- **Returns**: None
- **`cylinder(self, name, dim, c1, c2, radius, lo, hi, beadtype)`**
- **Description**: Adds a cylinder to the region.
- **Parameters**:
- `name` (str): The name of the cylinder.
- `dim` (str): Dimension along which the cylinder is oriented ('x', 'y', or 'z').
- `c1` (float): First coordinate of the center of the cylinder's base.
- `c2` (float): Second coordinate of the center of the cylinder's base.
- `radius` (float): Radius of the cylinder.
- `lo` (float): Lower bound of the cylinder along the specified dimension.
- `hi` (float): Upper bound of the cylinder along the specified dimension.
- `beadtype` (int): Type of beads to use for the cylinder.
- **Returns**: None
- **`delete(self, name)`**
- **Description**: Deletes an object from the region.
- **Parameters**:
- `name` (str): The name of the object to delete.
- **Returns**: None
Examples
--------
Below are some examples demonstrating how to use the REGION module:
1. **Concatenating Two Regions**:
```python
R1 = pizza.regions()
R2 = pizza.regions()
R = R1 + R2
```
2. **Generating Objects**:
```python
R.do()
```
3. **Retrieving the Script**:
```python
script_content = R.script()
```
4. **Deleting an Object**:
```python
R.o1 = []
```
5. **Creating a Union of Objects**:
```python
R.union(o1, o2, name='union_name')
```
6. **Adding a Cylinder**:
```python
R.cylinder(name='cyl1', dim='z', c1=0, c2=0, radius=1.0, lo=0.0, hi=5.0, beadtype=1)
Advanced features
-----------------
Public features (i.e. to be used by the end-user)
Let R1, R2 being pizza.regions()
R = R1 + R2 concatenates two regions (objects of R2 are inherited in R1, higher precedence for R2)
R.do() generate the objects (do() should work as in pizza.script)
R.script returns the script (script() should work as in pizza.script)
Let o1, o2 are objects of R
R.o1 = [] delete o1
R.union(o1,o2,name=...) creates an union of o1 and o2 (in the LAMMPS sense, see region manual)
R.intersect(o1,o2,name=...) creates an intersection of o1 and o2 (in the LAMMPS sense, see region manual)
R.eval(expr,name=)
expr any algebraic expression including +
o1+o2+...
Private features (i.e. to be used inside the code)
Overloading operators +, +=, | for any coregeometry object
Note that coregeometry have four main SECTIONS (scripts)
SECTIONS["variables"]
SECTIONS["region"]
SECTIONS["create"]
SECTIONS["group"]
SECTIONS["move"]
USER, VARIABLES are overdefined as attributes
+, += merge regions (no piping)
| pipe them
Add other geometries: block, sphere, cylinder....
```
Dependencies
------------
- Python 3.x
- LAMMPS
- pizza3.pizza
Installation
------------
To use the REGION module, ensure that you have Python 3.x and LAMMPS installed. You can integrate the module into your project by placing the `region.py` file in your working directory or your Python path.
License
-------
This project is licensed under the terms of the GPLv3 license.
Contact
-------
For any queries or contributions, please contact the maintainer:
- Olivier Vitrac, Han Chen
- Email: olivier.vitrac@agroparistech.fr
"""
__project__ = "Pizza3"
__author__ = "Olivier Vitrac, Han Chen"
__copyright__ = "Copyright 2024"
__credits__ = ["Olivier Vitrac", "Han Chen"]
__license__ = "GPLv3"
__maintainer__ = "Olivier Vitrac"
__email__ = "olivier.vitrac@agroparistech.fr"
__version__ = "0.99991"
# INRAE\Olivier Vitrac - rev. 2024-12-09 (community)
# contact: olivier.vitrac@agroparistech.fr, han.chen@inrae.fr
# Revision history
# 2023-01-04 code initialization
# 2023-01-10 early alpha version
# 2023-01-11 alpha version (many fixes), wrap and align all displays with textwrap.fill, textwrap.shorten
# 2023-01-12 implement methods of keyword(units/rotate/open)
# 2023-01-18 split a script into subscripts to be executed in pipescripts
# 2023-01-19 use of LammpsGeneric.do() and hasvariables flag to manage VARIABLES
# 2023-01-20 app PythonPath management for VScode, add comments region.ellipsoid()
# 2023-01-22 todo list added
# 2023-01-24 implementation (o1 = R.E1, o2 = R.E2): o1+o2+... and o1 | o2 | ...
# 2023-01-25 full implementation of eval(), set(), get(), __setattr__(), __getattr__(), iterators
# 2023-01-26 add an example with for and join
# 2023-01-27 add coregometry.__iadd__, region.pipescript, region.script, region.do()
# 2023-01-27 true alpha version, workable with https://andeplane.github.io/atomify/
# 2023-01-31 fix browser and temporary files on Linux
# 2023-02-06 major update, internal documentation for all objects, livelammps attribute
# 2023-02-16 fix object counters, add width, height, depth to region(), data are stored in live
# 2023-02-16 add a specific >> (__rshift__) method for LammpsVariables, to be used by pipescript.do()
# 2023-02-21 add gel compression exemple, modification of the footer section to account for several beadtypes
# 2023-03-16 add emulsion, collection
# 2023-07-07 fix region.union()
# 2023-07-15 add the preffix "$" to units, fix prism and other minor issues
# 2023-07-15 (code works with the current state of Workshop4)
# 2023-07-17 avoid duplicates if union or intersect is used, early implemeantion of "move"
# 2023-07-19 add region.hasfixmove, region.livelammps.options["static" | "dynamic"]
# 2023-07-19 early design for LammpsGroup class, Group class, region.group()
# 2023-07-20 reimplement, validate and extend the original emulsion example
# 2023-07-25 add group section (not active by default)
# 2023-07-29 symmetric design for coregeometry and collection objects with flag control, implementation in pipescript
# 2023-07-29 fix for the class LammpsCollectionGroup() - previous bug resolved
# 2023-08-11 full implementation of the space-filled model such as in pizza.raster
# 2024-04-18 workshop compatible (i.e., implementation of region.scriptobject(), to be used along with region.do())
# 2024-06-14 add mass, density attributes to all region objects and region (overdefinitions are possible), natoms return the number of atoms
# 2024-06-20 debug the calculation of volume of cylinder
# 2024-07-03 full implementation of scaling in pizza.region()
# 2024-07-04 implementation of scaling with formula (when variables are used), add live attributes to region along with an updated LammpsHeader
# 2024-07-05 full implementation of natoms, geometry
# 2024-07-29 consolidation of the method scriptobject (note that a do() is required before calling scriptobject)
# 2024-08-02 community implementation
# 2024-08-31 add method R.beadtypes(), class headerbox(), method R.headerbox()
# 2024-08-01 more robust implementation via method: scriptHeaders() and headersData object
# 2024-10-08 add lattice_scale
# 2024-12-01 standarize scripting features, automatically call script/pscript methods
# 2024-12-09 fix getattr for region objects to be compatible with inspect, pdoc
# %% Imports and private library
import os, sys, math
from datetime import datetime
from copy import copy as duplicate
from copy import deepcopy as deepduplicate
from textwrap import fill, shorten
from webbrowser import open as livelammps
# update python path if needed (for development only)
# try: pwd = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
# except NameError: pwd = os.getcwd()
# try: sys.path.index(pwd) # sys.path.insert(0, os.getcwd())
# except ValueError: sys.path.append(pwd)
# print('\n>>>',pwd,'\n')
# os.chdir(pwd)
# import struct, param, paramauto, span
from pizza.private.mstruct import *
from pizza.script import pipescript, script, scriptdata, scriptobject, span
from pizza.forcefield import *
__all__ = ['Block', 'Collection', 'Cone', 'Cylinder', 'Ellipsoid', 'Evalgeometry', 'Intersect', 'LammpsCollectionGroup', 'LammpsCreate', 'LammpsFooter', 'LammpsFooterPreview', 'LammpsGeneric', 'LammpsGroup', 'LammpsHeader', 'LammpsHeaderBox', 'LammpsHeaderInit', 'LammpsHeaderLattice', 'LammpsHeaderMass', 'LammpsMove', 'LammpsRegion', 'LammpsSetGroup', 'LammpsSpacefilling', 'LammpsVariables', 'Plane', 'Prism', 'Sphere', 'Union', 'cleanname', 'coregeometry', 'emulsion', 'forcefield', 'headersRegiondata', 'none', 'param', 'paramauto', 'parameterforcefield', 'pipescript', 'pstr', 'region', 'regioncollection', 'regiondata', 'rigidwall', 'saltTLSPH', 'scatter', 'script', 'scriptdata', 'scriptobject', 'smd', 'solidfood', 'span', 'struct', 'tlsph', 'ulsph', 'water', 'wrap']
# protected properties in region
protectedregionkeys = ('name', 'live', 'nbeads' 'volume', 'mass', 'radius', 'contactradius', 'velocities',
'forces', 'filename', 'index', 'objects', 'nobjects', 'counter','_iter_',
'livelammps','copy', 'hasfixmove', 'spacefilling', 'isspacefilled', 'spacefillingbeadtype','mass','density',
'units','center','separationdistance','regionunits',
'lattice_scale','lattice_style','lattice_scale_siunits', 'lattice_spacing',
'geometry', 'natoms', 'headersData'
)
# livelammps
#livelammpsURL = 'https://editor.lammps.org/'
livelammpsURL = "https://andeplane.github.io/atomify/"
livetemplate = {
'mass':'mass %d 1.0',
'pair_coeff':'pair_coeff %d %d 1.0 1.0 2.5',
}
groupprefix = "GRP" # prefix for all group IDs created from a named region
fixmoveprefix = "FM" # prefix for all fix move IDs created from a named region
# %% Low level functions
# wrap and indent text for variables
wrap = lambda k,op,v,indent,width,maxwidth: fill(
shorten(v,width=maxwidth+indent,
fix_sentence_endings=True),
width=width+indent,
initial_indent=" "*(indent-len(k)-len(op)-2)+f'{k} {op} ',
subsequent_indent=' '*(indent+(1 if v[0]=='"' else 0) )
)
# remove $ from variable names
cleanname = lambda name: "".join([x for x in name if x!="$"])
# %% Top generic classes for storing region data and objects
# they are not intended to be used outside script data and objects
class regiondata(paramauto):
"""
class of script parameters
Typical constructor:
DEFINITIONS = regiondata(
var1 = value1,
var2 = value2
)
See script, struct, param to get review all methods attached to it
"""
_type = "RD"
_fulltype = "region data"
_ftype = "definition"
def generatorforlammps(self,verbose=False,hasvariables=False):
"""
generate LAMMPS code from regiondata (struct)
generatorforlammps(verbose,hasvariables)
hasvariables = False is used to prevent a call of generatorforLammps()
for scripts others than LammpsGeneric ones
"""
nk = len(self)
if nk>0:
self.sortdefinitions(raiseerror=False)
s = self.tostruct()
ik = 0
fmt = "variable %s equal %s"
cmd = "\n#"+"_"*40+"\n"+f"#[{str(datetime.now())}]\n" if verbose else ""
cmd += f"\n# Definition of {nk} variables (URL: https://docs.lammps.org/variable.html)\n"
if hasvariables:
for k in s.keys():
ik += 1
end = "\n" if ik<nk else "\n"*2
v = getattr(s,k)
if v is None: v = "NULL"
if isinstance(v,(int,float)) or v == None:
cmd += fmt % (k,v)+end
elif isinstance(v,str):
cmd += fmt % (k,f'{v}')+end
elif isinstance(v,(list,tuple)):
cmd += fmt % (k,span(v))+end
else:
raise TypeError(f"unsupported type for the variable {k} set to {v}")
if verbose: cmd += "#"+"_"*40+"\n"
return cmd
class regioncollection(struct):
""" regioncollection class container (not to be called directly) """
_type = "collect" # object type
_fulltype = "Collections" # full name
_ftype = "collection" # field name
def __init__(self,*obj,**kwobj):
# store the objects with their alias
super().__init__(**kwobj)
# store objects with their real names
for o in obj:
if isinstance(o,region):
s = struct.dict2struct(o.objects)
list_s = s.keys()
for i in range(len(list_s)): self.setattr(list_s[i], s[i].copy())
elif o!=None:
self.setattr(o.name, o.copy())
# Class for headersData (added on 2024-09-01)
class headersRegiondata(regiondata):
"""
class of script parameters
Typical constructor:
DEFINITIONS = headersRegiondata(
var1 = value1,
var2 = value2
)
See script, struct, param to get review all methods attached to it
"""
_type = "HRD"
_fulltype = "Header parameters - helper for scripts"
_ftype = "header definition"
# %% PRIVATE SUB-CLASSES
# Use the equivalent methods of raster() to call these constructors
class LammpsGeneric(script):
"""
common class to override standard do() method from script
LammpsVariables, LammpsRegion, LammpsCreate are LammpsGeneric
note:: the only difference with the common script class is that
LammpsGeneric accepts VARIABLES AND To SHOW THEM
"""
def do(self,printflag=True,verbose=False):
""" generate the LAMMPS code with VARIABLE definitions """
if self.DEFINITIONS.hasvariables and hasattr(self,'VARIABLES'): # attribute VARIABLES checked 2023-08-11
cmd = f"#[{str(datetime.now())}] {self.name} > {self.SECTIONS[0]}" \
if verbose else ""
if len(self.VARIABLES)>0: cmd += \
self.VARIABLES.generatorforlammps(verbose=verbose,hasvariables=True)
else:
cmd = ""
cmd += super().do(printflag=False,verbose=verbose)
if printflag: print(cmd)
return cmd
class LammpsVariables(LammpsGeneric):
"""
script for LAMMPS variables section
myvars = LammpsVariables(regiondata(var1=...),ID='....',style='....')
"""
name = "LammpsVariables"
SECTIONS = ["VARIABLES"]
position = 2
role = "variable command definition"
description = "variable name style args"
userid = "variable"
version = 0.1
verbose = True
# Definitions used in TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
style = "${style}",
hasvariables = True
)
# Template (using % instead of # enables replacements)
TEMPLATE = "% variables to be used for ${ID} ${style}"
def __init__(self,VARIABLES=regiondata(),**userdefinitions):
""" constructor of LammpsVariables """
super().__init__(**userdefinitions)
self.VARIABLES = VARIABLES
# override >>
def __rshift__(self,s):
""" overload right shift operator (keep only the last template) """
if isinstance(s,script):
dup = deepduplicate(self) # instead of duplicate (added 2023-08-11)
dup.DEFINITIONS = dup.DEFINITIONS + s.DEFINITIONS
dup.USER = dup.USER + s.USER
if self.DEFINITIONS.hasvariables and s.DEFINITIONS.hasvariables:
dup.VARIABLES = s.VARIABLES
dup.TEMPLATE = s.TEMPLATE
return dup
else:
raise TypeError("the second operand must a script object")
class LammpsCreate(LammpsGeneric):
""" script for LAMMPS variables section """
name = "LammpsCreate"
SECTIONS = ["create_atoms"]
position = 4
role = "create_atoms command"
description = "create_atoms type style args keyword values ..."
userid = "create"
version = 0.1
verbose = True
# Definitions used in TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
style = "${style}",
hasvariables = False
)
# Template (using % instead of # enables replacements)
TEMPLATE = """
% Create atoms of type ${beadtype} for ${ID} ${style} (https://docs.lammps.org/create_atoms.html)
create_atoms ${beadtype} region ${ID}
"""
class LammpsSetGroup(LammpsGeneric):
""" script for LAMMPS set group section """
name = "LammpsSetGroup"
SECTIONS = ["set group"]
position = 4
role = "create_atoms command"
description = "set group groupID type beadtype"
userid = "setgroup"
version = 0.1
verbose = True
# Definitions used in TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
groupID = "$"+groupprefix+"${ID}", # freeze the interpretation,
hasvariables = False
)
# Template (using % instead of # enables replacements)
TEMPLATE = """
% Reassign atom type to ${beadtype} for the group ${groupID} associated with region ${ID} (https://docs.lammps.org/set.html)
set group ${groupID} type ${beadtype}
"""
class LammpsMove(LammpsGeneric):
""" script for LAMMPS variables section """
name = "LammpsMove"
SECTIONS = ["move_fix"]
position = 6
role = "move along a trajectory"
description = "fix ID group-ID move style args keyword values ..."
userid = "move"
version = 0.2
verbose = True
# Definitions used in TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
moveID = "$"+fixmoveprefix+"${ID}", # freeze the interpretation
groupID = "$"+groupprefix+"${ID}", # freeze the interpretation
style = "${style}",
args = "${args}",
hasvariables = False
)
# Template (using % instead of # enables replacements)
TEMPLATE = """
# Move atoms fix ID group-ID move style args keyword values (https://docs.lammps.org/fix_move.html)
% move_fix for group ${groupID} using ${style}
% prefix "g" added to ${ID} to indicate a group of atoms
% prefix "fm" added to ${ID} to indicate the ID of the fix move
fix ${moveID} ${groupID} move ${style} ${args}
"""
class LammpsRegion(LammpsGeneric):
""" generic region based on script """
name = "LammpsRegion"
SECTIONS = ["REGION"]
position = 3
role = "region command definition"
description = "region ID style args keyword arg"
userid = "region" # user name
version = 0.1 # version
verbose = True
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
style = "${style}",
args = "${args}",
side = "${side}",
units = "${units}",
move = "${move}",
rotate = "${rotate}",
open = "${open}",
hasvariables = False
)
# Template (using % instead of # enables replacements)
TEMPLATE = """
% Create region ${ID} ${style} args ... (URL: https://docs.lammps.org/region.html)
# keywords: side, units, move, rotate, open
# values: in|out, lattice|box, v_x v_y v_z, v_theta Px Py Pz Rx Ry Rz, integer
region ${ID} ${style} ${args} ${side}${units}${move}${rotate}${open}
"""
class LammpsGroup(LammpsGeneric):
""" generic group class based on script """
name = "LammpsGroup"
SECTIONS = ["GROUP"]
position = 5
role = "group command definition"
description = "group ID region regionID"
userid = "region" # user name
version = 0.2 # version
verbose = True
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
groupID = "$"+groupprefix+"${ID}", # freeze the interpretation
countgroupID = "$count"+"${groupID}", # either using $
grouptoshow = ["${groupID}"], # or []
hasvariables = False
)
# Template (using % instead of # enables replacements)
TEMPLATE = """
% Create group ${groupID} region ${ID} (URL: https://docs.lammps.org/group.html)
group ${groupID} region ${ID}
variable ${countgroupID} equal count(${grouptoshow})
print "Number of atoms in ${groupID}: \${{countgroupID}}"
"""
class LammpsCollectionGroup(LammpsGeneric):
""" Collection group class based on script """
name = "LammpsCollection Group"
SECTIONS = ["COLLECTIONGROUP"]
position = 6
role = "group command definition for a collection"
description = "group ID union regionID1 regionID2..."
userid = "collectionregion" # user name
version = 0.3 # version
verbose = True
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
ID = "${ID}",
groupID = "$"+groupprefix+"${ID}", # freeze the interpretation
hasvariables = False
)
# Template (ID is spanned over all regionIDs)
TEMPLATE = """
% Create group ${groupID} region ${ID} (URL: https://docs.lammps.org/group.html)
group ${groupID} union ${ID}
"""
class LammpsHeader(LammpsGeneric):
""" generic header for pizza.region """
name = "LammpsHeader"
SECTIONS = ["HEADER"]
position = 0
role = "header for live view"
description = "To be used with https://editor.lammps.org/"
userid = "header" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
width = 10,
height = 10,
depth = 10,
nbeads = 1,
hasvariables = False
)
# Template
TEMPLATE = """
# --------------[ INIT ]--------------
# assuming generic LJ units and style
units ${live_units}
atom_style ${live_atom_style}
lattice ${live_lattice_style} ${live_lattice_scale}
# ------------------------------------------
# --------------[ B O X ]--------------
variable halfwidth equal ${width}/2
variable halfheight equal ${height}/2
variable halfdepth equal ${depth}/2
region box block -${halfwidth} ${halfwidth} -${halfheight} ${halfheight} -${halfdepth} ${halfdepth}
create_box ${nbeads} box
# ------------------------------------------
"""
class LammpsHeaderInit(LammpsGeneric): # --- helper script ---
"""
Generates an initialization header script for a pizza.region object in LAMMPS.
This class constructs a LAMMPS header based on user-defined properties stored
in `R.headersData` of the pizza.region object. Properties set to `None` or an
empty string will be omitted from the script.
Attributes:
DEFINITIONS: Defines the parameters like dimension, units, boundary, etc.,
that can be set in `R.headersData`.
Methods:
__init__(persistentfile=True, persistentfolder=None, **userdefinitions):
Initializes the header script and sets up the `USER` attribute.
generate_template():
Creates the header template based on the provided `USER` definitions.
Note: This class is primarily intended for internal use within the simulation setup.
"""
name = "LammpsHeaderBox"
SECTIONS = ["HEADER"]
position = -2
role = "initialization header for pizza.region"
description = "helper method"
userid = "headerinit" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
# circular references (the variable is defined by its field in USER of class regiondata)
# are not needed but this explicits the requirements.
# All fields are stored in R.headersData with R a region object.
# Use R.headersData.property = value to assign a value
# Use R.headersData.property = None or "" to prevent the initialization of property
DEFINITIONS = scriptdata(
regionname = "${name}",
dimension = "${dimension}",
units = "${units}",
boundary = "${boundary}",
atom_style = "${atom_style}",
atom_modify = "${atom_modify}",
comm_modify = "${comm_modify}",
neigh_modify = "${neigh_modify}",
newton = "${newton}",
hasvariables = False
)
def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions):
"""Constructor adding instance definitions stored in USER."""
super().__init__(persistentfile, persistentfolder, **userdefinitions)
self.generate_template()
def generate_template(self):
"""Generate the TEMPLATE based on USER definitions."""
self.TEMPLATE = """
% --------------[ Initialization for <${name}:${boxid}> ]--------------
"""
self.TEMPLATE += '# set a parameter to None or "" to remove the definition\n'
if self.USER.dimension: self.TEMPLATE += "dimension ${dimension}\n"
if self.USER.units: self.TEMPLATE += "units ${units}\n"
if self.USER.boundary: self.TEMPLATE += "boundary ${boundary}\n"
if self.USER.atom_style: self.TEMPLATE += "atom_style ${atom_style}\n"
if self.USER.atom_modify: self.TEMPLATE += "atom_modify ${atom_modify}\n"
if self.USER.comm_modify: self.TEMPLATE += "comm_modify ${comm_modify}\n"
if self.USER.neigh_modify:self.TEMPLATE += "neigh_modify ${neigh_modify}\n"
if self.USER.newton: self.TEMPLATE += "newton ${newton}\n"
self.TEMPLATE += "# ------------------------------------------\n"
class LammpsHeaderLattice(LammpsGeneric): # --- helper script ---
"""
Lattice header for pizza.region
Use R.headersData.property = value to assign a value
with R a pizza.region object
"""
name = "LammpsHeaderLattice"
SECTIONS = ["HEADER"]
position = 0
role = "lattice header for pizza.region"
description = "helper method"
userid = "headerlattice" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
# circular references (the variable is defined by its field in USER of class regiondata)
# are not needed but this explicits the requirements.
# All fields are stored in R.headersData with R a region object.
# Use R.headersData.property = value to assign a value
DEFINITIONS = scriptdata(
lattice_style = "${lattice_style}",
lattice_scale = "${lattice_scale}",
hasvariables = False
)
def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions):
"""Constructor adding instance definitions stored in USER."""
super().__init__(persistentfile, persistentfolder, **userdefinitions)
self.generate_template()
def generate_template(self):
"""Generate the TEMPLATE based on USER definitions."""
self.TEMPLATE = "\n% --------------[ Lattice for <${name}:${boxid}>, style=${lattice_style}, scale=${lattice_scale} ]--------------\n"
if self.USER.lattice_spacing is None:
self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale}\n"
else:
self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale} spacing ${lattice_spacing}\n"
self.TEMPLATE += "# ------------------------------------------\n"
class LammpsHeaderBox(LammpsGeneric): # --- helper script ---
"""
Box header for pizza.region
Use R.headersData.property = value to assign a value
with R a pizza.region object
"""
name = "LammpsHeaderBox"
SECTIONS = ["HEADER"]
position = 0
role = "box header for pizza.region"
description = "helper method"
userid = "headerbox" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
# circular references (the variable is defined by its field in USER of class regiondata)
# are not needed but this explicits the requirements.
# All fields are stored in R.headersData with R a region object.
# Use R.headersData.property = value to assign a value
# Extra arguments
# ${boxid_arg} is by default "box"
# ${boxunits_arg} can be "", "units lattice", "units box"
DEFINITIONS = scriptdata(
name = "${name}",
xmin = "${xmin}",
xmax = "${xmax}",
ymin = "${ymin}",
ymax = "${ymax}",
zmin = "${zmin}",
zmax = "${zmax}",
nbeads = "${nbeads}",
boxid = "${boxid}",
boxunits_arg = "", # default units
hasvariables = False
)
# Template
TEMPLATE = """
% --------------[ Box for <${name}:${boxid}> incl. ${nbeads} bead types ]--------------
region ${boxid} block ${xmin} ${xmax} ${ymin} ${ymax} ${zmin} ${zmax} ${boxunits_arg}
create_box ${nbeads} ${boxid}
# ------------------------------------------
"""
class LammpsHeaderMass(LammpsGeneric):
"""
Mass assignment header for pizza.region.
Use R.headersData.property = value to assign a value
with R a pizza.region object.
"""
name = "LammpsHeaderMass"
SECTIONS = ["HEADER"]
position = 2 # Positioned after other headers like Box and Lattice
role = "mass assignment header for pizza.region"
description = "Assigns masses to bead types based on nbeads and default mass."
userid = "headermass" # User identifier
version = 0.1
verbose = False
# DEFINITIONS USED IN TEMPLATE
# All fields are stored in R.headersData with R a region object.
# Use R.headersData.property = value to assign a value.
# Mass overrides are provided via the 'mass' keyword argument as a list or tuple.
DEFINITIONS = scriptdata(
nbeads="${nbeads}", # these default values are not used
mass="${mass}", # but reported for records
hasvariables=False
)
def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions):
"""
Constructor adding instance definitions stored in USER.
Parameters:
persistentfile (bool, optional): Whether to use a persistent file. Defaults to True.
persistentfolder (str, optional): Folder path for persistent files. Defaults to None.
**userdefinitions: Arbitrary keyword arguments for user definitions.
- mass (list or tuple, optional): List or tuple to override masses for specific bead types.
Example: mass=[1.2, 1.0, 0.8] assigns mass 1.2 to bead type 1, 1.0 to bead type 2,
and 0.8 to bead type 3.
"""
super().__init__(persistentfile, persistentfolder, **userdefinitions)
self.generate_template()
def generate_template(self):
"""
Generate the TEMPLATE for mass assignments based on USER definitions.
The method constructs mass assignments for each bead type. If `mass` overrides
are provided as a list or tuple, it assigns the specified mass to the corresponding
bead types. Otherwise, it uses the default `mass` value from `USER.headersData.mass`.
"""
# Retrieve user-defined parameters
nbeads = self.USER.nbeads
mass = self.USER.mass
# Validate mass
if not isinstance(mass, (list, tuple)): mass = [mass] # Convert single value to a list
if len(mass) > nbeads:
mass = mass[:nbeads] # Truncate excess entries
elif len(mass) < nbeads:
last_mass = mass[-1] # Repeat the last value for missing entries
mass += [last_mass] * (nbeads - len(mass))
# Initialize TEMPLATE with header comment
self.TEMPLATE = "\n% --------------[ Mass Assignments for <${name}:${boxid}>" + f" (nbeads={nbeads}) " +" ]--------------\n"
# Iterate over bead types and assign masses
for bead_type in range(1, nbeads + 1):
bead_mass = mass[bead_type - 1]
if isinstance(bead_mass, str):
# If mass is a string (e.g., formula), ensure proper formatting
mass_str = f"({bead_mass})"
else:
# If mass is a numeric value, convert to string
mass_str = f"{bead_mass}"
self.TEMPLATE += f"mass {bead_type} {mass_str}\n"
# Close the TEMPLATE with a comment
self.TEMPLATE += "# ------------------------------------------\n"
class LammpsFooterPreview(LammpsGeneric): # --- helper script ---
"""
Box header for pizza.region
Use R.headersData.property = value to assign a value
with R a pizza.region object
"""
name = "LammpsFooterPreview"
SECTIONS = ["Footer"]
position = 0
role = "box footer for pizza.region"
description = "helper method"
userid = "footerpreview" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
# circular references (the variable is defined by its field in USER of class regiondata)
# are not needed but this explicits the requirements.
# All fields are stored in R.headersData with R a region object.
# Use R.headersData.property = value to assign a value
# Extra arguments
# ${boxid_arg} is by default "box"
# ${boxunits_arg} can be "", "units lattice", "units box"
DEFINITIONS = scriptdata(
filename = "${previewfilename}",
hasvariables = False
)
# Template
TEMPLATE = """
% --------------[ Preview for <${name}:${boxid}> incl. ${nbeads} bead types ]--------------
% Output the initial geometry to a dump file "${previewfilename}" for visualization
dump initial_dump all custom 1 ${previewfilename} id type x y z
run 0
# ------------------------------------------
"""
class LammpsSpacefilling(LammpsGeneric):
""" Spacefilling script: fill space with a block """
name = "LammpsSpacefilling"
SECTIONS = ["SPACEFILLING"]
position = 1
role = "fill space with fillingbeadtype atoms"
description = 'fill the whole space (region "filledspace") with default atoms (beadtype)'
userid = "spacefilling" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
fillingunits = "${fillingunits}",
fillingwidth = "${fillingwidth}",
fillingheight = "${fillingheight}",
fillingdepth = "${fillingdepth}",
fillingxlo = "-${fillingwidth}/2",
fillingxhi = "${fillingwidth}/2",
fillingylo = "-${fillingheight}/2",
fillingyhi = "${fillingheight}/2",
fillingzlo = "-${fillingdepth}/2",
fillingzhi = "${fillingdepth}/2",
fillingbeadtype = "${fillingbeadtype}",
fillingstyle = "${block}",
hasvariables = False
)
# Template
TEMPLATE = """
region filledspace ${fillingstyle} ${fillingxlo} ${fillingxhi} ${fillingylo} ${fillingyhi} ${fillingzlo} ${fillingzhi}
create_atoms ${fillingbeadtype} region filledspace
# ------------------------------------------
"""
class LammpsFooter(LammpsGeneric):
""" generic header for pizza.region """
name = "LammpsFooter"
SECTIONS = ["FOOTER"]
position = 1000
role = "footer for live view"
description = "To be used with https://editor.lammps.org/"
userid = "footer" # user name
version = 0.1 # version
verbose = False
# DEFINITIONS USED IN TEMPLATE
DEFINITIONS = scriptdata(
run = 1,
hasvariables = False
)
# Template
TEMPLATE = """
# --------------[ DYNAMICS ]--------------
${mass}
velocity all create 1.44 87287 loop geom
pair_style lj/cut 2.5
${pair_coeff}
neighbor 0.3 bin
neigh_modify delay 0 every 20 check no
fix 1 all nve
run ${run}
# ------------------------------------------
"""
class coregeometry:
"""
core geometry object
(helper class for attributes, side,units, move, rotate, open)
SECTIONS store scripts (variables, region and create for the geometry)
USER = common USER definitions for the three scripts
VARIABLES = variables definitions (used by variables only)
update() propagate USER to the three scripts
script returns SECTIONS as a pipescript
do() generate the script
Parameters to be used along scriptobject()
style
forcefield
group
They are stored SCRIPTOBJECT_USER
"""
_version = "0.35"
__custom_documentations__ = "pizza.region.coregeometry class"
def __init__(self,USER=regiondata(),VARIABLES=regiondata(),
hasgroup=False, hasmove=False, spacefilling=False,
style="smd",
forcefield=rigidwall(),
group=[],
mass=1, density=1,
lattice_style="sc", lattice_scale=1, lattice_scale_siunits=1 # added on 2024-07-05
):
"""
constructor of the generic core geometry
USER: any definitions requires by the geometry
VARIABLES: variables used to define the geometry (to be used in LAMMPS)
hasgroup, hasmove: flag to force the sections group and move
SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
"""
self.USER = USER
self.SECTIONS = {
'variables': LammpsVariables(VARIABLES,**USER),
'region': LammpsRegion(**USER),
'create': LammpsCreate(**USER),
'group': LammpsGroup(**USER),
'setgroup': LammpsSetGroup(**USER),
'move': LammpsMove(**USER)
}
self.FLAGSECTIONS = {
'variables': True,
'region': True,
'create': not spacefilling,
'group': hasgroup,
'setgroup': spacefilling,
'move': hasmove
}
self.spacefilling = spacefilling
# add comptaibility with scriptobjects
self.SCRIPTOBJECT_USER = {
'style': style,
'forcefield': forcefield,
'group': group
}
# collect information from parent region
self.mass = mass
self.density = density
self.lattice_style = lattice_style
self.lattice_scale = lattice_scale
self.lattice_scale_siunits = lattice_scale_siunits
def update(self):
""" update the USER content for all three scripts """
if isinstance(self.SECTIONS["variables"],script):
self.SECTIONS["variables"].USER += self.USER
if isinstance(self.SECTIONS["region"],script):
self.SECTIONS["region"].USER += self.USER
if isinstance(self.SECTIONS["create"],script):
self.SECTIONS["create"].USER += self.USER
if isinstance(self.SECTIONS["group"],script):
self.SECTIONS["group"].USER += self.USER
if isinstance(self.SECTIONS["setgroup"],script):
self.SECTIONS["setgroup"].USER += self.USER
if isinstance(self.SECTIONS["move"],script):
self.SECTIONS["move"].USER += self.USER
def copy(self,beadtype=None,name=""):
""" returns a copy of the graphical object """
if self.alike != "mixed":
dup = deepduplicate(self)
if beadtype != None: # update beadtype
dup.beadtype = beadtype
if name != "": # update name
dup.name = name
return dup
else:
raise ValueError("collections cannot be copied, regenerate the collection instead")
def creategroup(self):
""" force the group creation in script """
self.FLAGSECTIONS["group"] = True
def setgroup(self):
""" force the group creation in script """
self.FLAGSECTIONS["setgroup"] = True
def createmove(self):
""" force the fix move creation in script """
self.FLAGSECTIONS["move"] = True
def removegroup(self):
""" force the group creation in script """
self.FLAGSECTIONS["group"] = False
def removemove(self):
""" force the fix move creation in script """
self.FLAGSECTIONS["move"] = False
def scriptobject(self, beadtype=None, name=None, fullname=None, group=None, style=None, forcefield=None, USER = scriptdata()):
"""
Method to return a scriptobject based on region instead of an input file
Syntax similar to script.scriptobject
OBJ = scriptobject(...)
Implemented properties:
beadtype=1,2,...
name="short name"
fullname = "comprehensive name"
style = "smd"
forcefield = any valid forcefield instance (default = rigidwall())
"""
# Set defaults using instance attributes if parameters are None
if beadtype is None:
beadtype = self.beadtype
if name is None:
name = f"{self.name} bead"
if fullname is None:
fullname = f"beads of type {self.beadtype} | object {self.name} of kind region.{self.kind}"
if group is None:
group = self.SCRIPTOBJECT_USER["group"]
if style is None:
style = self.SCRIPTOBJECT_USER["style"]
if forcefield is None:
style = self.SCRIPTOBJECT_USER["forcefield"]
return scriptobject(
beadtype=beadtype,
name=name,
fullname=fullname,
style=style,
group=group,
filename=None, # No need for a file
USER = USER
)
@property
def hasvariables(self):
""" return the flag VARIABLES """
return isinstance(self.SECTIONS["variables"],script) \
and self.FLAGSECTIONS["variables"]
@property
def hasregion(self):
""" return the flag REGION """
return isinstance(self.SECTIONS["region"],script) \
and self.FLAGSECTIONS["region"]
@property
def hascreate(self):
""" return the flag CREATE """
return isinstance(self.SECTIONS["create"],script) \
and self.FLAGSECTIONS["create"] \
and (not self.spacefilling)
@property
def hasgroup(self):
""" return the flag GROUP """
return isinstance(self.SECTIONS["group"],script) \
and self.FLAGSECTIONS["group"]
@property
def hassetgroup(self):
""" return the flag GROUP """
return isinstance(self.SECTIONS["setgroup"],script) \
and self.FLAGSECTIONS["setgroup"] \
and self.hasgroup \
and (not self.hascreate)
@property
def hasmove(self):
""" return the flag MOVE """
return isinstance(self.SECTIONS["move"],script) \
and self.FLAGSECTIONS["move"]
@property
def isspacefilled(self):
""" return the flag spacefilling """
return isinstance(self.SECTIONS["spacefilling"],script) \
and self.FLAGSECTIONS["spacefilling"]
@property
def flags(self):
""" return a list of all flags that are currently set """
flag_names = list(self.SECTIONS.keys())
return [flag for flag in flag_names if getattr(self, f"has{flag}")]
@property
def shortflags(self):
""" return a string made from the first letter of each set flag """
return "".join([flag[0] for flag in self.flags])
@property
def VARIABLES(self):
""" return variables """
if isinstance(self.SECTIONS["variables"],script):
return self.SECTIONS["variables"].VARIABLES
else:
v = regiondata()
for i in range(len(self.SECTIONS["variables"].scripts)):
v = v + self.SECTIONS["variables"].scripts[i].VARIABLES
return v
@property
def script(self):
""" generates a pipe script from sections """
self.update()
ptmp = self.SECTIONS["variables"] if self.hasvariables else None
if self.hasregion:
ptmp = self.SECTIONS["region"] if ptmp is None else ptmp | self.SECTIONS["region"]
if self.hascreate:
ptmp = self.SECTIONS["create"] if ptmp is None else ptmp | self.SECTIONS["create"]
if self.hasgroup:
ptmp = self.SECTIONS["group"] if ptmp is None else ptmp | self.SECTIONS["group"]
if self.hassetgroup:
ptmp = self.SECTIONS["setgroup"] if ptmp is None else ptmp | self.SECTIONS["setgroup"]
if self.hasmove:
ptmp = self.SECTIONS["move"] if ptmp is None else ptmp | self.SECTIONS["move"]
return ptmp
# before 2023-07-17
#return self.SECTIONS["variables"] | self.SECTIONS["region"] | self.SECTIONS["create"]
def do(self,printflag=False,verbosity=1):
""" generates a script """
p = self.script # intentional, force script before do(), comment added on 2023-07-17
cmd = p.do(printflag=printflag,verbosity=verbosity)
# if printflag: print(cmd)
return cmd
def __repr__(self):
""" display method"""
nVAR = len(self.VARIABLES)
print("%s - %s object - beadtype=%d " % (self.name, self.kind,self.beadtype))
if hasattr(self,"filename"): print(f'\tfilename: "{self.filename}"')
if nVAR>0:
print(f"\t<-- {nVAR} variables are defined -->")
print(f"\tUse {self.name}.VARIABLES to see details and their evaluation")
for k,v in self.VARIABLES.items():
v0 = '"'+v+'"' if isinstance(v,str) else repr(v)
print(wrap(k,"=",v0,20,40,80))
print("\t<-- keyword arg -->")
haskeys = False
for k in ("side","move","units","rotate","open"):
if k in self.USER:
v = self.USER.getattr(k)
if v != "":
print(wrap(k,":",v[1:],20,60,80))
haskeys = True
if not haskeys: print(wrap("no keywords","<","from side|move|units|rotate|open",20,60,80))
flags = self.flags
if flags: print(f'defined scripts: {span(flags,sep=",")}',"\n")
print("\n"+self.geometry) # added 2024-07-05
return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype)
# ~~~~ validator for region arguments (the implementation is specific and not generic as fix move ones)
def sidearg(self,side):
"""
Validation of side arguments for region command (https://docs.lammps.org/region.html)
side value = in or out
in = the region is inside the specified geometry
out = the region is outside the specified geometry
"""
prefix = "$"
if side is None:
return ""
elif isinstance(side, str):
side = side.lower()
if side in ("in","out"):
return f"{prefix} side {side}"
elif side in ("","none"):
return ""
else:
raise ValueError(f'the value of side: "{side}" is not recognized')
else:
raise ValueError('the parameter side can be "in|out|None"')
def movearg(self,move):
"""
Validation of move arguments for region command (https://docs.lammps.org/region.html)
move args = v_x v_y v_z
v_x,v_y,v_z = equal-style variables for x,y,z displacement of region over time (distance units)
"""
prefix = "$"
if move is None:
return ""
elif isinstance(move, str):
move = move.lower()
if move in("","none"):
return ""
else:
return f"{prefix} move {move}"
elif isinstance(move,(list,tuple)):
if len(move)<3:
print("NULL will be added to move")
elif len(move)>3:
print("move will be truncated to 3 elements")
movevalid = ["NULL","NULL","NULL"]
for i in range(min(3,len(move))):
if isinstance(move[i],str):
if move[i].upper()!="NULL":
if prefix in move[i]:
# we assume a numeric result after evaluation
# Pizza variables will be evaluated
# formateval for the evaluation of ${}
# eval for residual expressions
movevalid[i] = round(eval(self.VARIABLES.formateval(move[i])),6)
else:
# we assume a variable (LAMMPS variable, not Pizza ones)
movevalid[i] = "v_" + move[i]
elif not isinstance(move[i],(int,float)):
if (move[i] is not None):
raise TypeError("move values should be str, int or float")
return f"{prefix} move {span(movevalid)}"
else:
raise TypeError("the parameter move should be a list or tuple")
def unitsarg(self,units):
"""
Validation for units arguments for region command (https://docs.lammps.org/region.html)
units value = lattice or box
lattice = the geometry is defined in lattice units
box = the geometry is defined in simulation box units
"""
prefix = "$"
if units is None:
return ""
elif isinstance(units,str):
units = units.lower()
if units in ("lattice","box"):
return f"{prefix} units {units}"
elif (units=="") or (units=="none"):
return ""
else:
raise ValueError(f'the value of side: "{units}" is not recognized')
else:
raise TypeError('the parameter units can be "lattice|box|None"')
def rotatearg(self,rotate):
"""
Validation of rotate arguments for region command (https://docs.lammps.org/region.html)
rotate args = v_theta Px Py Pz Rx Ry Rz
v_theta = equal-style variable for rotaton of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
"""
prefix = "$"
if rotate is None:
return ""
elif isinstance(rotate, str):
rotate = rotate.lower()
if rotate in ("","none",None):
return ""
else:
return f"{prefix} rotate {rotate}"
elif isinstance(rotate,(list,tuple)):
if len(rotate)<7:
print("NULL will be added to rotate")
elif len(rotate)>7:
print("rotate will be truncated to 7 elements")
rotatevalid = ["NULL"]*7
for i in range(min(7,len(rotate))):
if isinstance(rotate[i],str):
if rotate[i].upper()!="NULL":
if prefix in rotate[i]:
rotatevalid[i] = round(eval(self.VARIABLES.formateval(rotate[i])),6)
else:
rotatevalid[i] = rotate[i]
elif not isinstance(rotate[i],(int,float)):
if (rotate[i] is not None):
raise TypeError("rotate values should be str, int or float")
return f"{prefix} move {span(rotatevalid)}"
else:
raise TypeError("the parameter rotate should be a list or tuple")
def openarg(self,open):
"""
Validation of open arguments for region command (https://docs.lammps.org/region.html)
open value = integer from 1-6 corresponding to face index (see below)
The indices specified as part of the open keyword have the following meanings:
For style block, indices 1-6 correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces of the block.
I.e. 1 is the yz plane at x = xlo, 2 is the yz-plane at x = xhi, 3 is the xz plane at y = ylo,
4 is the xz plane at y = yhi, 5 is the xy plane at z = zlo, 6 is the xy plane at z = zhi).
In the second-to-last example above, the region is a box open at both xy planes.
For style prism, values 1-6 have the same mapping as for style block.
I.e. in an untilted prism, open indices correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces.
For style cylinder, index 1 corresponds to the flat end cap at the low coordinate along the cylinder axis,
index 2 corresponds to the high-coordinate flat end cap along the cylinder axis, and index 3 is the curved
cylinder surface. For example, a cylinder region with open 1 open 2 keywords will be open at both ends
(e.g. a section of pipe), regardless of the cylinder orientation.
"""
prefix = "$"
if open in ("","none",None):
return ""
elif isinstance(open, str):
raise TypeError(" the parameter open should be an integer or a list/tuple of integers from 1-6")
elif isinstance(open, int):
if open in range(1,7):
return f"{prefix} open {open}"
else:
raise TypeError(" open value should be integer from 1-6")
elif isinstance(open, (list,tuple)):
openvalid = [f"{prefix} open {i}" for i in range(1,7) if i in open]
return f"$ {span(openvalid)}"
# ~~~~ end validator for region arguments
# ~~~~ validator for fix move arguments (implemented generically on 2023-07-17)
def fixmoveargvalidator(self, argtype, arg, arglen):
"""
Validation of arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html)
LAMMPS syntax:
fix ID group-ID move style args
- linear args = Vx Vy Vz
- wiggle args = Ax Ay Az period
- rotate args = Px Py Pz Rx Ry Rz period
- transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period
- variable args = v_dx v_dy v_dz v_vx v_vy v_vz
Args:
argtype: Type of the argument (linear, wiggle, rotate, transrot, variable)
arg: The argument to validate
arglen: Expected length of the argument
"""
prefix = "$"
if arg in ("","none",None):
return ""
elif isinstance(arg,(list,tuple)):
if len(arg) < arglen:
print(f"NULL will be added to {argtype}")
elif len(arg) > arglen:
print(f"{argtype} will be truncated to {arglen} elements")
argvalid = ["NULL"]*arglen
for i in range(min(arglen,len(arg))):
if isinstance(arg[i],str):
if arg[i].upper()!="NULL":
if prefix in arg[i]:
argvalid[i] = round(eval(self.VARIABLES.formateval(arg[i])),6)
else:
argvalid[i] = arg[i]
elif not isinstance(arg[i],(int,float)):
if (arg[i] is not None):
raise TypeError(f"{argtype} values should be str, int or float")
return f"{prefix} move {span(argvalid)}"
else:
raise TypeError(f"the parameter {argtype} should be a list or tuple")
def fixmoveargs(self, linear=None, wiggle=None, rotate=None, transrot=None, variable=None):
"""
Validates all arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html)
the result is adictionary, all fixmove can be combined
"""
argsdict = {
"linear": [linear, 3],
"wiggle": [wiggle, 4],
"rotate": [rotate, 7],
"transrot": [transrot, 10],
"variable": [variable, 6]
}
for argtype, arginfo in argsdict.items():
arg, arglen = arginfo
if arg is not None:
argsdict[argtype] = self.fixmoveargvalidator(argtype, arg, arglen)
return argsdict
def get_fixmovesyntax(self, argtype=None):
"""
Returns the syntax for LAMMPS command, or detailed explanation for a specific argument type
Args:
argtype: Optional; Type of the argument (linear, wiggle, rotate, transrot, variable)
"""
syntax = {
"linear": "linear args = Vx Vy Vz\n"
"Vx,Vy,Vz = components of velocity vector (velocity units), any component can be specified as NULL",
"wiggle": "wiggle args = Ax Ay Az period\n"
"Ax,Ay,Az = components of amplitude vector (distance units), any component can be specified as NULL\n"
"period = period of oscillation (time units)",
"rotate": "rotate args = Px Py Pz Rx Ry Rz period\n"
"Px,Py,Pz = origin point of axis of rotation (distance units)\n"
"Rx,Ry,Rz = axis of rotation vector\n"
"period = period of rotation (time units)",
"transrot": "transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n"
"Vx,Vy,Vz = components of velocity vector (velocity units)\n"
"Px,Py,Pz = origin point of axis of rotation (distance units)\n"
"Rx,Ry,Rz = axis of rotation vector\n"
"period = period of rotation (time units)",
"variable": "variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n"
"v_dx,v_dy,v_dz = 3 variable names that calculate x,y,z displacement as function of time, any component can be specified as NULL\n"
"v_vx,v_vy,v_vz = 3 variable names that calculate x,y,z velocity as function of time, any component can be specified as NULL",
}
base_syntax = (
"fix ID group-ID move style args\n"
" - linear args = Vx Vy Vz\n"
" - wiggle args = Ax Ay Az period\n"
" - rotate args = Px Py Pz Rx Ry Rz period\n"
" - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n"
" - variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n\n"
'use get_movesyntax("movemethod") for details'
"manual: https://docs.lammps.org/fix_move.html"
)
return syntax.get(argtype, base_syntax)
# ~~~~ end validator for fix move arguments
def __add__(self,C):
""" overload addition ("+") operator """
if isinstance(C,coregeometry):
dup = deepduplicate(self)
dup.name = cleanname(self.name) +"+"+ cleanname(C.name)
dup.USER = dup.USER + C.USER
dup.USER.ID = "$" + cleanname(self.USER.ID) +"+"+ cleanname(C.USER.ID)
dup.SECTIONS["variables"] = dup.SECTIONS["variables"] + C.SECTIONS["variables"]
dup.SECTIONS["region"] = dup.SECTIONS["region"] + C.SECTIONS["region"]
dup.SECTIONS["create"] = dup.SECTIONS["create"] + C.SECTIONS["create"]
dup.SECTIONS["group"] = dup.SECTIONS["group"] + C.SECTIONS["group"]
dup.SECTIONS["move"] = dup.SECTIONS["move"] + C.SECTIONS["move"]
dup.FLAGSECTIONS["variables"] = dup.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"]
dup.FLAGSECTIONS["region"] = dup.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"]
dup.FLAGSECTIONS["create"] = dup.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"]
dup.FLAGSECTIONS["group"] = dup.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"]
dup.FLAGSECTIONS["move"] = dup.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"]
return dup
raise TypeError("the second operand must a region.coregeometry object")
def __iadd__(self,C):
""" overload iaddition ("+=") operator """
if isinstance(C,coregeometry):
self.USER += C.USER
self.SECTIONS["variables"] += C.SECTIONS["variables"]
self.SECTIONS["region"] += C.SECTIONS["region"]
self.SECTIONS["create"] += C.SECTIONS["create"]
self.SECTIONS["group"] += C.SECTIONS["group"]
self.SECTIONS["move"] += C.SECTIONS["move"]
self.FLAGSECTIONS["variables"] = self.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"]
self.FLAGSECTIONS["region"] = self.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"]
self.FLAGSECTIONS["create"] = self.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"]
self.FLAGSECTIONS["group"] = self.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"]
self.FLAGSECTIONS["move"] = self.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"]
return self
raise TypeError("the operand must a region.coregeometry object")
def __or__(self,C):
""" overload | pipe """
if isinstance(C,coregeometry):
dup = deepduplicate(self)
dup.name = cleanname(self.name) +"|"+ cleanname(C.name)
dup.USER = dup.USER + C.USER
dup.USER.ID = "$" + cleanname(self.USER.ID) +"|"+ cleanname(C.USER.ID)
dup.SECTIONS["variables"] = dup.SECTIONS["variables"] | C.SECTIONS["variables"]
dup.SECTIONS["region"] = dup.SECTIONS["region"] | C.SECTIONS["region"]
dup.SECTIONS["create"] = dup.SECTIONS["create"] | C.SECTIONS["create"]
dup.SECTIONS["group"] = dup.SECTIONS["group"] | C.SECTIONS["group"]
dup.SECTIONS["move"] = dup.SECTIONS["move"] | C.SECTIONS["move"]
self.FLAGSECTIONS["variables"] = self.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"]
self.FLAGSECTIONS["region"] = self.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"]
self.FLAGSECTIONS["create"] = self.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"]
self.FLAGSECTIONS["group"] = self.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"]
self.FLAGSECTIONS["move"] = self.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"]
return dup
raise TypeError("the second operand must a region.coregeometry object")
# copy and deep copy methods for the class (required)
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 __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, deepduplicate(v, memo)) # replace duplicatedeep by deepduplicate (OV: 2023-07-28)
return copie
# Return the number of atoms
@property
def natoms(self):
"""Calculate the number of beads based on density, mass, and volume"""
if hasattr(self, 'volume'):
try:
volume_siunits = self.volume("si")
voxel_volume_siunits = self.lattice_scale**3
number_of_beads = volume_siunits / voxel_volume_siunits
packing_factors = {
'sc': 1.0,
'fcc': 4.0,
'bcc': 2.0,
'hcp': 6.0, # Approximate value, requires specific volume calculation for accuracy
'dia': 8.0,
'bco': 2.0, # Assuming orthorhombic lattice similar to bcc
'fco': 4.0, # Assuming orthorhombic lattice similar to fcc
}
packing_factor = packing_factors.get(self.lattice_style, 1.0) # Default to simple cubic if unknown
number_of_beads *= packing_factor
return round(number_of_beads)
except Exception as e:
print(f"Error calculating number of beads: {e}")
return None
else:
print("Volume attribute is missing.")
return None
# return parent region details
@property
def regiondetails(self):
return "\n".join((
f"\n--- | Region Details | ---",
f"Name: {self.name}",
f"Lattice Style: {self.lattice_style}",
f"Lattice Scale: {self.lattice_scale}",
f"Lattice Scale (SI units): {self.lattice_scale_siunits}",
f"Volume: {self.volume()}",
f"Volume (SI units): {self.volume('si')}",
f"Number of Atoms: {self.natoms}","\n"
))
# return geometry details (2024-07-04)
@property
def geometry(self):
"""Return the geometry details of the object."""
details = self.regiondetails
details += "\n--- | Geometry Details | ---\n"
if hasattr(self.USER, 'geometry'):
details += self.USER.geometry
else:
details = "No geometry available.\n"
return details
class Block(coregeometry):
""" Block class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "block%03d" % counter[1]
self.kind = "block" # kind of object
self.alike = "block" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$block"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
"""Calculate the volume of the block based on USER.args"""
#args = [xlo, xhi, ylo, yhi, zlo, zhi]
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
xlo = float(args[0])
xhi = float(args[1])
ylo = float(args[2])
yhi = float(args[3])
zlo = float(args[4])
zhi = float(args[5])
# Calculate the dimensions of the block
length = xhi - xlo
width = yhi - ylo
height = zhi - zlo
# Calculate the volume of the block
volume = length * width * height
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Cone(coregeometry):
""" Cone class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "cone%03d" % counter[1]
self.kind = "cone" # kind of object
self.alike = "cone" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$cone"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
"""Calculate the volume of the cone based on USER.args"""
#args = [dim, c1, c2, radlo, radhi, lo, hi]
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
radius_low = float(args[3])
radius_high = float(args[4])
lo = float(args[5])
hi = float(args[6])
# Calculate the height of the cone
height = hi - lo
# Calculate the volume of the cone (assuming a conical frustum if radii are different)
if radius_low == radius_high:
volume = (1/3) * 3.141592653589793 * (radius_low ** 2) * height
else:
volume = (1/3) * 3.141592653589793 * height * (radius_low ** 2 + radius_low * radius_high + radius_high ** 2)
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Cylinder(coregeometry):
""" Cylinder class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "cylinder%03d" % counter[1]
self.kind = "cylinder" # kind of object
self.alike = "cylinder" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$cylinder"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
"""Calculate the volume of the cylinder based on USER.args"""
# args = [dim,c1,c2,radius,lo,hi]
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
radius = float(args[3])
lo = float(args[4])
hi = float(args[5])
# Calculate the height of the cylinder
height = hi - lo
# Calculate the volume of the cylinder
volume = 3.141592653589793 * (radius ** 2) * height
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Ellipsoid(coregeometry):
""" Ellipsoid class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "ellipsoid%03d" % counter[1]
self.kind = "ellipsoid" # kind of object
self.alike = "ellipsoid" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$ellipsoid"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
#args = [x, y, z, a, b, c]
"""Calculate the volume of the ellipsoid based on USER.args"""
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
a = float(args[3])
b = float(args[4])
c = float(args[5])
# Calculate the volume of the ellipsoid
volume = (4/3) * 3.141592653589793 * a * b * c
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Plane(coregeometry):
""" Plane class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "plane%03d" % counter[1]
self.kind = "plane" # kind of object
self.alike = "plane" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$plane"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
style=style, group=group, forcefield=forcefield # script object properties
)
@property
def volume(self,units=None):
"""Dummy method returning None for volume"""
#args = [px, py, pz, nx, ny, nz]
return None
class Prism(coregeometry):
""" Prism class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "prism%03d" % counter[1]
self.kind = "prism" # kind of object
self.alike = "prism" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$prism"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
"""Calculate the volume of the prism based on USER.args"""
#args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz]
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
xlo = float(args[0])
xhi = float(args[1])
ylo = float(args[2])
yhi = float(args[3])
zlo = float(args[4])
zhi = float(args[5])
# Calculate the dimensions of the prism
length = xhi - xlo
width = yhi - ylo
height = zhi - zlo
# Calculate the volume of the prism
volume = length * width * height
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Sphere(coregeometry):
""" Sphere class """
def __init__(self,counter,index=None,subindex=None, mass=1, density=1,
lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1,
hasgroup=False,hasmove=False,spacefilling=False,
style=None, group=None, forcefield=None, **variables):
self.name = "sphere%03d" % counter[1]
self.kind = "sphere" # kind of object
self.alike = "ellipsoid" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
self.mass = mass
self.density = density
# call the generic constructor
super().__init__(
USER = regiondata(style="$sphere"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling,
mass=mass, density=density,
lattice_style=lattice_style,
lattice_scale=lattice_scale,
lattice_scale_siunits=lattice_scale_siunits,
style=style, group=group, forcefield=forcefield # script object properties
)
def volume(self,units=None):
"""Calculate the volume of the sphere based on USER.args"""
#args = [x, y, z, radius]
try:
# Extract the arguments from USER.args
args = self.USER.args_siunits if units=="si" else self.USER.args
radius = float(args[3])
# Calculate the volume of the sphere
volume = (4/3) * 3.141592653589793 * (radius ** 3)
return volume
except Exception as e:
print(f"Error calculating volume: {e}")
return None
class Union(coregeometry):
""" Union class """
def __init__(self,counter,index=None,subindex=None,
hasgroup=False,hasmove=False,spacefilling=False,**variables):
self.name = "union%03d" % counter[1]
self.kind = "union" # kind of object
self.alike = "operator" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
# call the generic constructor
super().__init__(
USER = regiondata(style="$union"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling
)
class Intersect(coregeometry):
""" Intersect class """
def __init__(self,counter,index=None,subindex=None,
hasgroup=False,hasmove=False,spacefilling=False,**variables):
self.name = "intersect%03d" % counter[1]
self.kind = "intersect" # kind of object
self.alike = "operator" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
# call the generic constructor
super().__init__(
USER = regiondata(style="$intersect"),
VARIABLES = regiondata(**variables),
hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling
)
class Evalgeometry(coregeometry):
""" generic class to store evaluated objects with region.eval() """
def __init__(self,counter,index=None,subindex=None,
hasgroup=False,hasmove=False,spacefilling=False):
self.name = "eval%03d" % counter[1]
self.kind = "eval" # kind of object
self.alike = "eval" # similar object for plotting
self.beadtype = 1 # bead type
self.index = counter[0] if index is None else index
self.subindex = subindex
super().__init__(hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling)
class Collection:
"""
Collection class (including many objects)
"""
_version = "0.31"
__custom_documentations__ = "pizza.region.Collection class"
# CONSTRUCTOR
def __init__(self,counter,
name=None,
index = None,
subindex = None,
hasgroup = False,
USER = regiondata()):
if (name is None) or (name==""):
self.name = "collect%03d" % counter[1]
elif name in self:
raise KeyError(f'the name "{name}" already exist')
else:
self.name = name
if not isinstance(USER,regiondata):
raise TypeError("USER should be a regiondata object")
USER.groupID = "$"+self.name # the content is frozen
USER.ID = ""
self.USER = USER
self.kind = "collection" # kind of object
self.alike = "mixed" # similar object for plotting
self.index = counter[0] if index is None else index
self.subindex = counter[1]
self.collection = regioncollection()
self.SECTIONS = {
'group': LammpsCollectionGroup(**USER)
}
self.FLAGSECTIONS = {"group": hasgroup}
def update(self):
""" update the USER content for the script """
if isinstance(self.SECTIONS["group"],script):
self.USER.ID = "$"\
+span([groupprefix+x for x in self.list()]) # the content is frozen
self.SECTIONS["group"].USER += self.USER
def creategroup(self):
""" force the group creation in script """
for o in self.collection: o.creategroup()
self.update()
self.FLAGSECTIONS["group"] = True
def removegroup(self,recursive=True):
""" force the group creation in script """
if recursive:
for o in self.collection: o.removegroup()
self.FLAGSECTIONS["group"] = False
@property
def hasgroup(self):
""" return the flag hasgroup """
return self.FLAGSECTIONS["group"]
@property
def flags(self):
""" return a list of all flags that are currently set """
flag_names = list(self.SECTIONS.keys())
return [flag for flag in flag_names if getattr(self, f"has{flag}")]
@property
def shortflags(self):
""" return a string made from the first letter of each set flag """
return "".join([flag[0] for flag in self.flags])
@property
def script(self):
""" generates a pipe script from SECTIONS """
self.update()
return self.SECTIONS["group"]
def __repr__(self):
keylengths = [len(key) for key in self.collection.keys()]
width = max(10,max(keylengths)+2)
fmt = "%%%ss:" % width
line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) )
print(line," %s - %s object" % (self.name, self.kind), line,sep="\n")
for key,value in self.collection.items():
flags = "("+self.collection[key].shortflags+")" if self.collection[key].flags else "(no script)"
print(fmt % key,value.kind,
'"%s"' % value.name," > ",flags)
flags = self.flags
if flags: print(line,f'defined scripts: {span(flags,sep=",")}',sep="\n")
print(line)
return "%s object: %s (beadtype=[%s])" % (self.kind,self.name,", ".join(map(str,self.beadtype)))
# GET -----------------------------
def get(self,name):
""" returns the object """
if name in self.collection:
return self.collection.getattr(name)
elif name in ["collection","hasgroup","flags","shortflags","script"]:
return getattr(self,name)
else:
raise ValueError('the object "%s" does not exist, use list()' % name)
# GETATTR --------------------------
def __getattr__(self,key):
""" get attribute override """
return self.get(key)
@property
def beadtype(self):
""" returns the beadtypes used in the collection """
b = []
for o in self.collection:
if o.beadtype not in b:
b.append(o.beadtype)
if len(b)==0:
return 1
else:
return b
# GROUP -------------------------------
def group(self):
""" return the grouped coregeometry object """
if len(self) == 0:return pipescript()
# execute all objects
for i in range(len(self)): self.collection[i].do()
# concatenate all objects into a pipe script
liste = [x.SECTIONS["variables"] for x in self.collection if x.hasvariables] + \
[x.SECTIONS["region"] for x in self.collection if x.hasregion] + \
[x.SECTIONS["create"] for x in self.collection if x.hascreate] + \
[x.SECTIONS["group"] for x in self.collection if x.hasgroup] + \
[x.SECTIONS["setgroup"] for x in self.collection if x.hassetgroup] + \
[x.SECTIONS["move"] for x in self.collection if x.hasmove]
return pipescript.join(liste)
# LEN ---------------------------------
def __len__(self):
""" return length of collection """
return len(self.collection)
# LIST ---------------------------------
def list(self):
""" return the list of objects """
return self.collection.keys()
# %% region class (main class)
class region:
"""
The `region` class represents a simulation region, centered at the origin (0, 0, 0) by default,
and is characterized by its physical dimensions, properties, and boundary conditions. It supports
setting up lattice structures, particle properties, and options for live previews.
Attributes:
----------
name : str, optional
Name of the region (default is 'region container').
dimension : int, optional
Number of spatial dimensions for the simulation (either 2 or 3, default is 3).
boundary : list of str or None, optional
Boundary conditions for each dimension. If None, defaults to ["sm"] * dimension.
Must be a list of length `dimension`, where "s" indicates shrink-wrapped, and "m" indicates a non-periodic boundary.
nbeads : int, optional
Number of beads in the region (default is 1).
units : str, optional
Units for the simulation box (default is "").
Particle Properties:
-------------------
mass : float, optional
Mass of particles in the region (default is 1).
volume : float, optional
Volume of the region (default is 1).
density : float, optional
Density of the region (default is 1).
radius : float, optional
Radius of the particles (default is 1.5).
contactradius : float, optional
Contact radius of the particles (default is 0.5).
velocities : list of floats, optional
Initial velocities of particles (default is [0, 0, 0]).
forces : list of floats, optional
External forces acting on the particles (default is [0, 0, 0]).
Other Properties:
----------------
filename : str, optional
Name of the output file (default is an empty string, which will auto-generate a name based on the region name).
index : int, optional
Index or identifier for the region.
run : int, optional
Run configuration parameter (default is 1).
Box Properties:
---------------
center : list of floats, optional
Center of the simulation box for coordinate scaling (default is [0, 0, 0]).
width : float, optional
Width of the region (default is 10).
height : float, optional
Height of the region (default is 10).
depth : float, optional
Depth of the region (default is 10).
hasfixmove : bool, optional
Indicates whether the region has a fixed movement (default is False).
Spacefilling Design:
-------------------
spacefilling : bool, optional
Indicates whether the design is space-filling (default is False).
fillingbeadtype : int, optional
Type of bead used for space filling (default is 1).
Lattice Properties:
------------------
regionunits : str, optional
Defines the units of the region. Can be either "lattice" (default) or "si".
separationdistance : float, optional
Separation distance between atoms in SI units (default is 5e-6).
lattice_scale : float, optional
Scaling factor for the lattice, used mainly in visualization (default is 0.8442).
lattice_spacing : list or None, optional
Specifies the spacing between lattice points. If None, the default spacing is used. Can be a list of [dx, dy, dz].
lattice_style : str, optional
Specifies the lattice structure style (default is "fcc"). Accepts any LAMMPS valid style, e.g., "sc" for simple cubic.
Atom Properties:
----------------
atom_style : str, optional
Defines the atom style for the region (default is "smd").
atom_modify : list of str, optional
LAMMPS command for atom modification (default is ["map", "array"]).
comm_modify : list of str, optional
LAMMPS command for communication modification (default is ["vel", "yes"]).
neigh_modify : list, optional
LAMMPS command for neighbor list modification (default is ["every", 10, "delay", 0, "check", "yes"]).
newton : str, optional
Specifies the Newton flag (default is "off").
Live Preview:
------------
live_units : str, optional
Units for live preview (default is "lj", for Lennard-Jones units).
live_atom_style : str, optional
Atom style used specifically for live LAMMPS sessions (default is "atomic").
livepreview_options : dict, optional
Contains options for live preview. The dictionary includes 'static' (default: run = 1) and 'dynamic' (default: run = 100) options.
Methods:
-------
__init__ :
Constructor method to initialize all the attributes of the `region` class.
"""
_version = "0.9997"
__custom_documentations__ = "pizza.region.region class"
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#
# CONSTRUCTOR METHOD
#
#
# The constructor include
# the main container: objects (a dictionnary)
# several attributes covering current and future use of PIZZA.REGION()
#
# The original constructor is derived from PIZZA.RASTER() with
# an intent to allow at some point some forward and backward port between
# objects of the class PIZZA.RASTER() and PIZZA.REGION().
#
# The code will evolve according to the needs, please come back regularly.
#
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# CONSTRUCTOR ----------------------------
def __init__(self,
# container properties
name="region container",
dimension = 3,
boundary = None,
nbeads=1,
units = "",
# particle properties
mass=1.0,
volume=1.0,
density=1.0,
radius=1.5,
contactradius=0.5,
velocities=[0.0,0.0,0.0],
forces=[0.0,0.0,0.0],
# other properties
filename="",
previewfilename="",
index = None,
run=1,
# Box lengths
center = [0.0,0.0,0.0], # center of the box for coordinates scaling
width = 10.0, # along x
height = 10.0, # along y
depth = 10.0, # along z
hasfixmove = False, # by default no fix move
# Spacefilling design (added on 2023-08-10)
spacefilling = False,
fillingbeadtype = 1,
# Lattice properties
boxid = "box", # default value for ${boxid_arg}
regionunits = "lattice", # units ("lattice" or "si")
separationdistance = 5e-6, # SI units
lattice_scale = 0.8442, # LJ units (for visualization)
lattice_spacing = None, # lattice spacing is not used by default (set [dx dy dz] if needed)
lattice_style = "fcc" , # any valid lattice style accepted by LAMMPS (sc=simple cubic)
# Atom properties
atom_style = "smd",
atom_modify = ["map","array"],
comm_modify = ["vel","yes"],
neigh_modify = ["every",10,"delay",0,"check","yes"],
newton ="off",
# Live preview
live_units = "lj", # units to be used ONLY with livelammps (https://andeplane.github.io/atomify/)
live_atom_style = "atomic",# atom style to be used ONLY with livelammps (https://andeplane.github.io/atomify/)
# livepreview options
livepreview_options = {
'static':{'run':1},
'dynamic':{'run':100}
},
# common flags (for scripting)
printflag = False,
verbose = True,
verbosity = None
):
""" constructor """
self.name = name
# Ensure dimension is an integer (must be 2 or 3 for LAMMPS)
if not isinstance(dimension, int) or dimension not in (2, 3):
raise ValueError("dimension must be either 2 or 3.")
# Handle boundary input
if boundary is None:
boundary = ["sm"] * dimension
elif isinstance(boundary, list):
if len(boundary) != dimension:
raise ValueError(f"The length of boundary ({len(boundary)}) must match the dimension ({dimension}).")
else:
raise ValueError("boundary must be a list of strings or None.")
# Validate regionunits
if regionunits not in ("lattice", "si"):
raise ValueError("regionunits can only be 'lattice' or 'si'.")
# Lattice scaling logic
lattice_scale_siunits = lattice_scale if regionunits == "si" else separationdistance
if lattice_scale_siunits is None or lattice_scale_siunits=="":
lattice_scale_siunits = separationdistance
if lattice_spacing == "":
lattice_spacing = None
elif isinstance(lattice_spacing, (int, float)):
lattice_spacing = [lattice_spacing] * dimension
elif isinstance(lattice_spacing, list):
lattice_spacing = lattice_spacing + [lattice_spacing[-1]] * (dimension - len(lattice_spacing)) if len(lattice_spacing) < dimension else lattice_spacing[:dimension]
# live data (updated 2024-07-04)
live_lattice_scale = lattice_scale/separationdistance if regionunits == "si" else lattice_scale
live_box_scale = 1/lattice_scale_siunits if regionunits == "si" else 1
self.live = regiondata(nbeads=nbeads,
run=run,
width=math.ceil(width*live_box_scale), # live_box_scale force lattice units for live visualization
height=math.ceil(height*live_box_scale), # live_box_scale force lattice units for live visualization
depth=math.ceil(depth*live_box_scale), # live_box_scale force lattice units for live visualization
live_units = "$"+live_units,
live_atom_style = "$"+live_atom_style,
live_lattice_style="$"+lattice_style,
live_lattice_scale=live_lattice_scale)
# generic SMD properties (to be rescaled)
self.volume = volume
self.mass = mass
self.density = density
self.radius = radius
self.contactradius = contactradius
self.velocities = velocities
self.forces = forces
if filename == "":
self.filename = f"region_{self.name}"
else:
self.filename = filename
self.index = index
self.objects = {} # object container
self.nobjects = 0 # total number of objects (alive)
# count objects per type
self.counter = {
"ellipsoid":0,
"block":0,
"sphere":0,
"cone":0,
"cylinder":0,
"prism":0,
"plane":0,
"union":0,
"intersect":0,
"eval":0,
"collection":0,
"all":0
}
# fix move flag
self.hasfixmove = hasfixmove
# livelammps (for live sessions) - added 2023-02-06
self.livelammps = {
"URL": livelammpsURL,
"active": False,
"file": None,
"options": livepreview_options
}
# space filling (added 2023-08-10)
self.spacefilling = {
"flag": spacefilling,
"fillingstyle": "$block",
"fillingbeadtype": fillingbeadtype,
"fillingwidth": width,
"fillingheight": height,
"fillingdepth": depth,
"fillingunits": units
}
# region object units
self.regionunits = regionunits
# lattice
self.units = units
self.center = center
self.separationdistance = separationdistance
self.lattice_scale = lattice_scale
self.lattice_spacing = lattice_spacing
self.lattice_scale_siunits = lattice_scale_siunits
self.lattice_style = lattice_style
# headers for header scripts (added 2024-09-01)
# geometry is assumed to be units set by ${boxunits_arg} (new standard 2024-11-26)
self.headersData = headersRegiondata(
# use $ and [] to prevent execution
name = "$"+name,
previewfilename = "$dump.initial."+self.filename if previewfilename=="" else "$"+previewfilename,
# Initialize Lammps
dimension = dimension,
units = "$"+units,
boundary = boundary,
atom_style = "$" + atom_style,
atom_modify = atom_modify,
comm_modify = comm_modify,
neigh_modify = neigh_modify,
newton ="$" + newton,
# Box (added 2024-11-26)
boxid = "$"+boxid,
boxunits_arg = "$units box" if regionunits=="si" else "", # standard on 2025-11-26
# Lattice
lattice_style = "$"+lattice_style,
lattice_scale = lattice_scale,
lattice_spacing = lattice_spacing,
# Box
xmin = -(width/2) +center[0],
xmax = +(width/2) +center[0],
ymin = -(height/2) +center[1],
ymax = +(height/2) +center[1],
zmin = -(depth/2) +center[2],
zmax = +(depth/2) +center[2],
nbeads = nbeads,
mass = mass
)
self.printflag = printflag
self.verbose = verbose if verbosity is None else verbosity>0
self.verbosity = 0 if not verbose else verbosity
# Method for coordinate/length scaling and translation including with formula embedded strings (updated 2024-07-03, fixed 2024-07-04)
# Note that the translation is not fully required since the scaling applies also to full coordinates.
# However, an implementation is provided for arbitrary offset.
def scale_and_translate(self, value, offset=0):
"""
Scale and translate a value or encapsulate the formula within a string.
If self.regionunits is "si", only the offset is applied without scaling.
Otherwise, scaling and translation are performed based on self.units ("si" or "lattice").
Parameters:
value (str or float): The value or formula to be scaled and translated.
offset (float, optional): The offset to apply. Defaults to 0.
Returns:
str or float: The scaled and translated value or formula.
"""
if self.regionunits == "si":
# Only apply offset without scaling
if isinstance(value, str):
if offset:
translated = f"({value}) - {offset}"
else:
translated = f"{value}"
return translated
else:
if offset:
return value - offset
else:
return value
else:
# Existing behavior based on self.units
if isinstance(value, str):
if offset:
translated = f"({value}) - {offset}"
else:
translated = f"{value}"
if self.units == "si":
return f"({translated}) / {self.lattice_scale} + {offset / self.lattice_scale}"
else: # "lattice"
return f"({translated}) * {self.lattice_scale} + {offset * self.lattice_scale}"
else:
if offset:
translated = value - offset
else:
translated = value
if self.units == "si":
return translated / self.lattice_scale + (offset / self.lattice_scale)
else: # "lattice"
return translated * self.lattice_scale + (offset * self.lattice_scale)
# space filling attributes (cannot be changed)
@property
def isspacefilled(self):
return self.spacefilling["flag"]
@property
def spacefillingbeadtype(self):
return self.spacefilling["fillingbeadtype"]
# total number of atoms in the region
@property
def natoms(self):
"""Count the total number of atoms in all objects within the region."""
total_atoms = 0
for eachobj in self:
total_atoms += eachobj.natoms
return total_atoms
# details if the geometry of the region
@property
def geometry(self):
"""Display the dimensions and characteristics of the region and its objects."""
details = f"Region: {self.name}\n"
details += f"Total atoms: {self.natoms}\n"
details += f"Span: width={self.spacefilling['fillingwidth']}, height={self.spacefilling['fillingheight']}, depth={self.spacefilling['fillingdepth']}\n"
details += f"Box center: {self.center}\n"
details += "Objects in the region:\n\n"
for obj in self:
details += "\n\n"+"-"*32+"\n"
details += f"\nObject: {obj.name}\n"
details += f"Type: {type(obj).__name__}\n"
if hasattr(obj, 'geometry'):
details += "\n"+"-"*32+"\n"
details += obj.geometry
else:
details += "No geometry information available.\n"
print(details)
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#
# REGION.GEOMETRY constructors
#
#
# These methods create the 3D geometry objects (at least their code)
# A geometry is a collection of PIZZA.SCRIPT() objects (LAMMPS codelet)
# not a real geometry. The distinction between the creation (definition)
# and the execution (generation) of the gometry object existed already
# in PIZZA.RASTER(), but here they remain codelets as ONLY LAMMPS can
# generate the real object.
#
# This level of abstraction makes it possible to mix PIZZA variables
# (USER, PIZZA.SCRIPT.USER, PIZZA.PIPESCRIPT.USER) with LAMMPS variables.
# The same object template can be used in different LAMMPS scripts with
# different values and without writting additional Python code.
# In shorts: USER fields store PIZZA.SCRIPT() like variables
# (they are compiled [statically] before LAMMPS execution)
# VARIABLES are defined in the generated LAMMPS script but
# created [dynamically] in LAMMPS. Note that these variables
# are defined explicitly with the LAMMPS variable command:
# variable name style args ...
# Note: static variables can have only one single value for LAMMPS, which
# is known before LAMMPS is launched. The others can be assigned
# at runtime when LAMMPS is running.
# Example with complex definitions
# R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",
# move=["left","${up}*3",None],
# up=0.1)
# R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})'
# R.E2.VARIABLES.a="${b}-5"
# R.E2.VARIABLES.b=5
# R.E2.VARIABLES.c=100
#
# The methods PIZZA.REGION.DO(), PIZZA.REGION.DOLIVE() compiles
# (statically) and generate the corresponding LAMMPS code. The static
# compiler accepts hybrid constructions where USER and VARIABLES are
# mixed. Any undefined variables will be assumed to be defined elsewhere
# in the LAMMPS code.
#
# Current attributes of PIZZA.REGION.OBJECT cover current and future use
# of these objects and will allow some point some forward and backward
# compatibility with the same PIZZA.RASTER.OBJECT.
#
#
# References:
# https://docs.lammps.org/region.html
# https://docs.lammps.org/variable.html
# https://docs.lammps.org/create_atoms.html
# https://docs.lammps.org/create_box.html
#
#
# List of implemented geometries (shown here with the LAMMPS syntax)
# block args = xlo xhi ylo yhi zlo zhi
# cone args = dim c1 c2 radlo radhi lo hi
# cylinder args = dim c1 c2 radius lo hi
# ellipsoid args = x y z a b c <-- first method to be implemented
# plane args = px py pz nx ny n
# prism args = xlo xhi ylo yhi zlo zhi xy xz yz
# sphere args = x y z radius
# union args = N reg-ID1 reg-ID2 ..
# intersect args = N reg-ID1 reg-ID2 ...
#
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# BLOCK method ---------------------------
# block args = xlo xhi ylo yhi zlo zhi
# xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units)
def block(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a block region
xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "block001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "block"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object B with B for block
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
B = Block((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density, # added on 2024-06-14
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): B.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: B.beadtype = beadtype # bead type (if not defined, default index will apply)
B.USER.ID = "$"+B.name # add $ to prevent its execution
# geometry args (2024-07-04) -------------------------------------
args = [xlo, xhi, ylo, yhi, zlo, zhi] # args = [....] as defined in the class Block
args_scaled = [
self.scale_and_translate(xlo, self.center[0]),
self.scale_and_translate(xhi, self.center[0]),
self.scale_and_translate(ylo, self.center[1]),
self.scale_and_translate(yhi, self.center[1]),
self.scale_and_translate(zlo, self.center[2]),
self.scale_and_translate(zhi, self.center[2])
]
if self.units == "si":
B.USER.args = args_scaled
B.USER.args_siunits = args
else: # "lattice"
B.USER.args = args
B.USER.args_siunits = args_scaled
# geometry
B.USER.geometry = (
f"Block Region: {B.name}\n"
"Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi] = bounds of block in all dimensions"
f"Coordinates (scaled): {B.USER.args}\n"
f"Coordinates (SI units): {B.USER.args_siunits}\n"
f"\talong x: [{B.USER.args[0]}, {B.USER.args[1]}]\n"
f"\talong y: [{B.USER.args[2]}, {B.USER.args[3]}]\n"
f"\talong z: [{B.USER.args[4]}, {B.USER.args[5]}]"
)
# other attributes -------------------------------------
B.USER.beadtype = B.beadtype # beadtype to be used for create_atoms
B.USER.side = B.sidearg(side) # extra parameter side
B.USER.move = B.movearg(move) # move arg
B.USER.units = B.unitsarg(units) # units
B.USER.rotate = B.rotatearg(rotate) # rotate
B.USER.open = B.openarg(open) # open
# Create the object if not fake
if fake:
return B
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = B
self.nobjects += 1
return None
# CONE method ---------------------------
# cone args = dim c1 c2 radlo radhi lo hi
# dim = x or y or z = axis of cone
# c1,c2 = coords of cone axis in other 2 dimensions (distance units)
# radlo,radhi = cone radii at lo and hi end (distance units)
# lo,hi = bounds of cone in dim (distance units)
def cone(self,dim="z",c1=0,c2=0,radlo=2,radhi=5,lo=-10,hi=10,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a cone region
dim = "x" or "y" or "z" = axis of the cone
note: USER, LAMMPS variables are not authorized here
c1,c2 = coords of cone axis in other 2 dimensions (distance units)
radlo,radhi = cone radii at lo and hi end (distance units)
lo,hi = bounds of cone in dim (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "cone001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "cone"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object C with C for cone
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
C = Cone((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density, # added on 2024-06-14
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): C.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply)
C.USER.ID = "$"+C.name # add $ to prevent its execution
# geometry args (2024-07-04) -------------------------------------
args = [dim, c1, c2, radlo, radhi, lo, hi] # args = [....] as defined in the class Cone
if dim == "x": # x-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[1]),
self.scale_and_translate(c2, self.center[2]),
self.scale_and_translate(radlo, 0),
self.scale_and_translate(radhi, 0),
self.scale_and_translate(lo, self.center[0]),
self.scale_and_translate(hi, self.center[0])
]
elif dim == "y": # y-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[0]),
self.scale_and_translate(c2, self.center[2]),
self.scale_and_translate(radlo, 0),
self.scale_and_translate(radhi, 0),
self.scale_and_translate(lo, self.center[1]),
self.scale_and_translate(hi, self.center[1])
]
else: # z-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[0]),
self.scale_and_translate(c2, self.center[1]),
self.scale_and_translate(radlo, 0),
self.scale_and_translate(radhi, 0),
self.scale_and_translate(lo, self.center[2]),
self.scale_and_translate(hi, self.center[2])
]
if self.units == "si":
C.USER.args = args_scaled
C.USER.args_siunits = args
else: # "lattice"
C.USER.args = args
C.USER.args_siunits = args_scaled
# geometry
C.USER.geometry = (
f"Cone Region: {C.name}\n"
"Coordinates: [dim,c1,c2,radlo,radhi,lo,hi] = dimensions of cone\n"
f"Coordinates (scaled): {C.USER.args}\n"
f"Coordinates (SI units): {C.USER.args_siunits}\n"
f"\tdim: {C.USER.args[0]}\n"
f"\tc1: {C.USER.args[1]}\n"
f"\tc2: {C.USER.args[2]}\n"
f"\tradlo: {C.USER.args[3]}\n"
f"\tradhi: {C.USER.args[4]}\n"
f"\tlo: {C.USER.args[5]}\n"
f"\thi: {C.USER.args[6]}"
)
# other attributes -------------------------------------
C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms
C.USER.side = C.sidearg(side) # extra parameter side
C.USER.move = C.movearg(move) # move arg
C.USER.units = C.unitsarg(units) # units
C.USER.rotate = C.rotatearg(rotate) # rotate
C.USER.open = C.openarg(open) # open
# Create the object if not fake
if fake:
return C
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = C
self.nobjects += 1
return None
# CYLINDER method ---------------------------
# cylinder args = dim c1 c2 radius lo hi
# dim = x or y or z = axis of cylinder
# c1,c2 = coords of cylinder axis in other 2 dimensions (distance units)
# radius = cylinder radius (distance units)
# c1,c2, and radius can be a variable (see below)
# lo,hi = bounds of cylinder in dim (distance units)
def cylinder(self,dim="z",c1=0,c2=0,radius=4,lo=-10,hi=10,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a cylinder region
dim = x or y or z = axis of cylinder
c1,c2 = coords of cylinder axis in other 2 dimensions (distance units)
radius = cylinder radius (distance units)
c1,c2, and radius can be a LAMMPS variable
lo,hi = bounds of cylinder in dim (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "cylinder001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "cylinder"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object C with C for cylinder
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
C = Cylinder((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density,
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): C.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply)
C.USER.ID = "$"+C.name # add $ to prevent its execution
# geometry args (2024-07-04) -------------------------------------
args = [dim, c1, c2, radius, lo, hi] # args = [....] as defined in the class Cylinder
if dim == "x": # x-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[1]),
self.scale_and_translate(c2, self.center[2]),
self.scale_and_translate(radius, 0),
self.scale_and_translate(lo, self.center[0]),
self.scale_and_translate(hi, self.center[0])
]
elif dim == "y": # y-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[0]),
self.scale_and_translate(c2, self.center[2]),
self.scale_and_translate(radius, 0),
self.scale_and_translate(lo, self.center[1]),
self.scale_and_translate(hi, self.center[1])
]
else: # z-axis
args_scaled = [
dim,
self.scale_and_translate(c1, self.center[0]),
self.scale_and_translate(c2, self.center[1]),
self.scale_and_translate(radius, 0),
self.scale_and_translate(lo, self.center[2]),
self.scale_and_translate(hi, self.center[2])
]
if self.units == "si":
C.USER.args = args_scaled
C.USER.args_siunits = args
else: # "lattice"
C.USER.args = args
C.USER.args_siunits = args_scaled
# geometry
C.USER.geometry = (
f"Cylinder Region: {C.name}\n"
"Coordinates: [dim,c1,c2,radius,lo,hi] = dimensions of cylinder\n"
f"Coordinates (scaled): {C.USER.args}\n"
f"Coordinates (SI units): {C.USER.args_siunits}\n"
f"\tdim: {C.USER.args[0]}\n"
f"\tc1: {C.USER.args[1]}\n"
f"\tc2: {C.USER.args[2]}\n"
f"\tradius: {C.USER.args[3]}\n"
f"\tlo: {C.USER.args[4]}\n"
f"\thi: {C.USER.args[5]}"
)
# other attributes -------------------------------------
C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms
C.USER.side = C.sidearg(side) # extra parameter side
C.USER.move = C.movearg(move) # move arg
C.USER.units = C.unitsarg(units) # units
C.USER.rotate = C.rotatearg(rotate) # rotate
C.USER.open = C.openarg(open) # open
# Create the object if not fake
if fake:
return C
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = C
self.nobjects += 1
return None
# ELLIPSOID method ---------------------------
# ellipsoid args = x y z a b c
# x,y,z = center of ellipsoid (distance units)
# a,b,c = half the length of the principal axes of the ellipsoid (distance units)
# x,y,z,a,b,c can be variables
def ellipsoid(self,x=0,y=0,z=0,a=5,b=3,c=2,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates an ellipsoid region
ellipsoid(x,y,z,a,b,c [,name=None,beadtype=None,property=value,...])
x,y,z = center of ellipsoid (distance units)
a,b,c = half the length of the principal axes of the ellipsoid (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "ellipsoid001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
Examples:
# example with variables created either at creation or later
R = region(name="my region")
R.ellipsoid(0, 0, 0, 1, 1, 1,name="E1",toto=3)
repr(R.E1)
R.E1.VARIABLES.a=1
R.E1.VARIABLES.b=2
R.E1.VARIABLES.c="(${a},${b},100)"
R.E1.VARIABLES.d = '"%s%s" %("test",${c}) # note that test could be replaced by any function'
# example with extra parameters
R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",move=["left","${up}*3",None],up=0.1)
R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})'
R.E2.VARIABLES.a="${b}-5"
R.E2.VARIABLES.b=5
R.E2.VARIABLES.c=100
"""
# prepare object creation
kind = "ellipsoid"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object E with E for Ellipsoid
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
E = Ellipsoid((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density,
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): E.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply)
E.USER.ID = "$"+E.name # add $ to prevent its execution
# geometry args (2024-07-04) -------------------------------------
args = [x, y, z, a, b, c] # args = [....] as defined in the class Ellipsoid
args_scaled = [
self.scale_and_translate(x, self.center[0]),
self.scale_and_translate(y, self.center[1]),
self.scale_and_translate(z, self.center[2]),
self.scale_and_translate(a, 0),
self.scale_and_translate(b, 0),
self.scale_and_translate(c, 0)
]
if self.units == "si":
E.USER.args = args_scaled
E.USER.args_siunits = args
else: # "lattice"
E.USER.args = args
E.USER.args_siunits = args_scaled
# geometry
E.USER.geometry = (
f"Ellipsoid Region: {E.name}\n"
"Coordinates: [x,y,z,a,b,c] = center and radii of ellipsoid\n"
f"Coordinates (scaled): {E.USER.args}\n"
f"Coordinates (SI units): {E.USER.args_siunits}\n"
f"\tcenter: [{E.USER.args[0]}, {E.USER.args[1]}, {E.USER.args[2]}]\n"
f"\ta: {E.USER.args[3]}\n"
f"\tb: {E.USER.args[4]}\n"
f"\tc: {E.USER.args[5]}"
)
# other attributes -------------------------------------
E.USER.beadtype = E.beadtype # beadtype to be used for create_atoms
E.USER.side = E.sidearg(side) # extra parameter side
E.USER.move = E.movearg(move) # move arg
E.USER.units = E.unitsarg(units) # units
E.USER.rotate = E.rotatearg(rotate) # rotate
E.USER.open = E.openarg(open) # open
# Create the object if not fake
if fake:
return E
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = E
self.nobjects += 1
return None
# PLANE method ---------------------------
# plane args = px py pz nx ny nz
# px,py,pz = point on the plane (distance units)
# nx,ny,nz = direction normal to plane (distance units)
def plane(self,px=0,py=0,pz=0,nx=0,ny=0,nz=1,
name=None,beadtype=None,fake=False,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a plane region
px,py,pz = point on the plane (distance units)
nx,ny,nz = direction normal to plane (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "plane001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "plane"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object P with P for plane
P = Plane((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=self.mass, density=self.density, # added on 2024-06-14
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): P.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply)
P.USER.ID = "$"+P.name # add $ to prevent its execution
# geometry args (2024-07-04) ---------------------------
args = [px, py, pz, nx, ny, nz] # args = [....] as defined in the class Plane
args_scaled = [
self.scale_and_translate(px, self.center[0]),
self.scale_and_translate(py, self.center[1]),
self.scale_and_translate(pz, self.center[2]),
self.scale_and_translate(nx, 0),
self.scale_and_translate(ny, 0),
self.scale_and_translate(nz, 0)
]
if self.units == "si":
P.USER.args = args_scaled
P.USER.args_siunits = args
else: # "lattice"
P.USER.args = args
P.USER.args_siunits = args_scaled
# geometry
P.USER.geometry = (
f"Plane Region: {P.name}\n"
"Coordinates: [px,py,pz,nx,ny,nz] = point and normal vector of plane\n"
f"Coordinates (scaled): {P.USER.args}\n"
f"Coordinates (SI units): {P.USER.args_siunits}\n"
f"\tpoint: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}]\n"
f"\tnormal: [{P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]"
)
# other attributes ---------------------------
P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms
P.USER.side = P.sidearg(side) # extra parameter side
P.USER.move = P.movearg(move) # move arg
P.USER.units = P.unitsarg(units) # units
P.USER.rotate = P.rotatearg(rotate) # rotate
P.USER.open = P.openarg(open) # open
# Create the object if not fake
if fake:
return P
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = P
self.nobjects += 1
return None
# PRISM method ---------------------------
# prism args = xlo xhi ylo yhi zlo zhi xy xz yz
# xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units)
# xy = distance to tilt y in x direction (distance units)
# xz = distance to tilt z in x direction (distance units)
# yz = distance to tilt z in y direction (distance units)
def prism(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5,xy=1,xz=1,yz=1,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a prism region
xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units)
xy = distance to tilt y in x direction (distance units)
xz = distance to tilt z in x direction (distance units)
yz = distance to tilt z in y direction (distance units)
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "prism001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "prism"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object P with P for prism
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
P = Prism((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density, # added on 2024-06-14
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): P.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply)
P.USER.ID = "$"+P.name # add $ to prevent its execution
# geometry args (2024-07-04) ---------------------------
args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz] # args = [....] as defined in the class Prism
args_scaled = [
self.scale_and_translate(xlo, self.center[0]),
self.scale_and_translate(xhi, self.center[0]),
self.scale_and_translate(ylo, self.center[1]),
self.scale_and_translate(yhi, self.center[1]),
self.scale_and_translate(zlo, self.center[2]),
self.scale_and_translate(zhi, self.center[2]),
self.scale_and_translate(xy, 0),
self.scale_and_translate(xz, 0),
self.scale_and_translate(yz, 0)
]
if self.units == "si":
P.USER.args = args_scaled
P.USER.args_siunits = args
else: # "lattice"
P.USER.args = args
P.USER.args_siunits = args_scaled
# geometry
P.USER.geometry = (
f"Prism Region: {P.name}\n"
"Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi,xy,xz,yz] = bounds and tilts of prism\n"
f"Coordinates (scaled): {P.USER.args}\n"
f"Coordinates (SI units): {P.USER.args_siunits}\n"
f"\tbounds: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}, {P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]\n"
f"\ttilts: [{P.USER.args[6]}, {P.USER.args[7]}, {P.USER.args[8]}]"
)
# other attributes ---------------------------
P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms
P.USER.side = P.sidearg(side) # extra parameter side
P.USER.move = P.movearg(move) # move arg
P.USER.units = P.unitsarg(units) # units
P.USER.rotate = P.rotatearg(rotate) # rotate
P.USER.open = P.openarg(open) # open
# Create the object if not fake
if fake:
return P
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = P
self.nobjects += 1
return None
# SPHERE method ---------------------------
# sphere args = x y z radius
# x,y,z = center of sphere (distance units)
# radius = radius of sphere (distance units)
# x,y,z, and radius can be a variable (see below)
def sphere(self,x=0,y=0,z=0,radius=3,
name=None,beadtype=None,fake=False,
mass=None, density=None,
side=None,units=None,move=None,rotate=None,open=None,
index = None,subindex = None,
**variables
):
"""
creates a sphere region
x,y,z = center of sphere (distance units)
radius = radius of sphere (distance units)
x,y,z, and radius can be a variable
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "sphere001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties
side = "in|out"
units = "lattice|box" ("box" is forced if regionunits=="si")
move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list
with v1,v2,v3 equal-style variables for x,y,z displacement
of region over time (distance units)
rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz
vtheta = equal-style variable for rotation of region over time (in radians)
Px,Py,Pz = origin for axis of rotation (distance units)
Rx,Ry,Rz = axis of rotation vector
open = integer from 1-6 corresponding to face index
See examples for elliposid()
"""
# prepare object creation
kind = "sphere"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si"
# create the object S with S for sphere
obj_mass = mass if mass is not None else self.mass
obj_density = density if density is not None else self.density
S = Sphere((self.counter["all"]+1,self.counter[kind]+1),
spacefilling=self.isspacefilled, # added on 2023-08-11
mass=obj_mass, density=obj_density, # added on 2024-06-14
index=index,subindex=subindex,
lattice_style=self.lattice_style,
lattice_scale=self.lattice_scale,
lattice_scale_siunits=self.lattice_scale_siunits,
**variables)
# feed USER fields
if name not in (None,""): S.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: S.beadtype = beadtype # bead type (if not defined, default index will apply)
S.USER.ID = "$"+S.name # add $ to prevent its execution
# geometry args (2024-07-04) ---------------------------
args = [x, y, z, radius] # args = [....] as defined in the class Sphere
args_scaled = [
self.scale_and_translate(x, self.center[0]),
self.scale_and_translate(y, self.center[1]),
self.scale_and_translate(z, self.center[2]),
self.scale_and_translate(radius, 0)
]
if self.units == "si":
S.USER.args = args_scaled
S.USER.args_siunits = args
else: # "lattice"
S.USER.args = args
S.USER.args_siunits = args_scaled
# geometry
S.USER.geometry = (
f"Sphere Region: {S.name}\n"
"Coordinates: [x,y,z,radius] = center and radius of sphere\n"
f"Coordinates (scaled): {S.USER.args}\n"
f"Coordinates (SI units): {S.USER.args_siunits}\n"
f"\tcenter: [{S.USER.args[0]}, {S.USER.args[1]}, {S.USER.args[2]}]\n"
f"\tradius: {S.USER.args[3]}"
)
# other attributes ---------------------------
S.USER.beadtype = S.beadtype # beadtype to be used for create_atoms
S.USER.side = S.sidearg(side) # extra parameter side
S.USER.move = S.movearg(move) # move arg
S.USER.units = S.unitsarg(units) # units
S.USER.rotate = S.rotatearg(rotate) # rotate
S.USER.open = S.openarg(open) # open
# Create the object if not fake
if fake:
return S
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = S
self.nobjects += 1
return None
# UNION method ---------------------------
# union args = N reg-ID1 reg-ID2
def union(self,*regID,
name=None,beadtype=1,fake=False,
index = None,subindex = None,
**variables):
"""
creates a union region
union("reg-ID1","reg-ID2",name="myname",beadtype=1,...)
reg-ID1,reg-ID2, ... = IDs of regions to join together
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "union001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
"""
kind = "union"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
# create the object U with U for union
U = Union((self.counter["all"]+1,self.counter[kind]+1),
index=index,subindex=subindex,**variables)
# feed USER fields
if name not in (None,""): U.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: U.beadtype = beadtype # bead type (if not defined, default index will apply)
U.USER.ID = "$"+U.name # add $ to prevent its execution
U.USER.side, U.USER.move, U.USER.units, U.USER.rotate, U.USER.open = "","","","",""
# build arguments based on regID
nregID = len(regID)
if nregID<2: raise ValueError('two objects must be given at least for an union')
args = [None] # the number of arguments is not known yet
validID = range(nregID)
for ireg in validID:
if isinstance(regID[ireg],int):
if regID[ireg] in validID:
args.append(self.names[regID[ireg]])
else:
raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}")
elif isinstance(regID[ireg],str):
if regID[ireg] in self:
args.append(regID[ireg])
else:
raise KeyError(f'the object "{regID[ireg]}" does not exist')
else:
raise KeyError(f"the {ireg+1}th object should be given as a string or an index")
# prevent the creation of atoms merged (avoid duplicates)
self.objects[regID[ireg]].FLAGSECTIONS["create"] = False
args[0] = len(regID)
U.USER.args = args # args = [....] as defined in the class Union
# Create the object if not fake
if fake:
return U
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = U
self.nobjects += 1
return None
# UNION method ---------------------------
# union args = N reg-ID1 reg-ID2
def intersect(self,*regID,
name=None,beadtype=1,fake=False,
index = None,subindex = None,
**variables):
"""
creates an intersection region
intersect("reg-ID1","reg-ID2",name="myname",beadtype=1,...)
reg-ID1,reg-ID2, ... = IDs of regions to join together
URL: https://docs.lammps.org/region.html
Main properties = default value
name = "intersect001"
beadtype = 1
fake = False (use True to test the execution)
index, subindex = object index and subindex
"""
kind = "intersect"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
# create the object I with I for intersect
I = Intersect((self.counter["all"]+1,self.counter[kind]+1),
index=index,subindex=subindex,**variables)
# feed USER fields
if name not in (None,""): I.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: I.beadtype = beadtype # bead type (if not defined, default index will apply)
I.USER.ID = "$"+I.name # add $ to prevent its execution
I.USER.side, I.USER.move, I.USER.units, I.USER.rotate, I.USER.open = "","","","",""
# build arguments based on regID
nregID = len(regID)
if nregID<2: raise ValueError('two objects must be given at least for an intersection')
args = [None] # the number of arguments is not known yet
validID = range(nregID)
for ireg in validID:
if isinstance(regID[ireg],int):
if regID[ireg] in validID:
args.append(self.names[regID[ireg]])
else:
raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}")
elif isinstance(regID[ireg],str):
if regID[ireg] in self:
args.append(regID[ireg])
else:
raise KeyError(f'the object "{regID[ireg]}" does not exist')
else:
raise KeyError(f"the {ireg+1}th object should be given as a string or an index")
# prevent the creation of atoms (avoid duplicates)
self.objects[regID[ireg]].FLAGSECTIONS["create"] = False
args[0] = len(regID)
I.USER.args = args # args = [....] as defined in the class Union
# Create the object if not fake
if fake:
return I
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = I
self.nobjects += 1
return None
# Group method ---------------------------
def group(self,obj,name=None,fake=False):
pass
# COLLECTION method ---------------------------
def collection(self,*obj,name=None,beadtype=None,fake=False,
index = None,subindex = None,
**kwobj):
kind = "collection"
if index is None: index = self.counter["all"]+1
if subindex is None: subindex = self.counter[kind]+1
# create the object C with C for collection
C = Collection((index,subindex))
if name not in (None,""): C.name = name # object name (if not defined, default name will be used)
if name in self.name: raise NameError('the name "%s" is already used' % name)
if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply)
# add objects
C.collection = regioncollection(*obj,**kwobj)
# apply modifications (beadtype, ismask)
for o in C.collection.keys():
tmp = C.collection.getattr(o)
if beadtype != None: tmp.beadtype = beadtype
C.collection.setattr(o,tmp)
# Create the object if not fake
if fake:
return C
else:
self.counter["all"] += 1
self.counter[kind] +=1
self.objects[name] = C
self.nobjects += 1
return None
def scatter(self,
E,
name="emulsion",
beadtype=None,
):
"""
Parameters
----------
E : scatter or emulsion object
codes for x,y,z and r.
name : string, optional
name of the collection. The default is "emulsion".
beadtype : integer, optional
for all objects. The default is 1.
Raises
------
TypeError
Return an error of the object is not a scatter type.
Returns
-------
None.
"""
if isinstance(E,scatter):
collect = {}
for i in range(E.n):
b = E.beadtype[i] if beadtype==None else beadtype
nameobj = "glob%02d" % i
collect[nameobj] = self.sphere(E.x[i],E.y[i],E.z[i],E.r[i],
name=nameobj,beadtype=b,fake=True)
self.collection(**collect,name=name)
else:
raise TypeError("the first argument must be an emulsion object")
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#
# LOW-LEVEL METHODS
#
#
# Low-level methods to manipulate and operate region objects (e.g., R).
# They implement essentially some Python standards with the following
# shortcut: R[i] or R[objecti] and R.objecti and R.objects[objecti] are
# the same ith object where R.objects is the original container
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# repr() method ----------------------------
def __repr__(self):
""" display method """
spacefillingstr = f"\n(space filled with beads of type {self.spacefillingbeadtype})" \
if self.isspacefilled else ""
print("-"*40)
print('REGION container "%s" with %d objects %s\n(units="%s", lattice="%s", scale=%0.4g [m])' \
% (self.name,self.nobjects,spacefillingstr,self.units,self.lattice_style,self.lattice_scale_siunits))
if self.nobjects>0:
names = self.names
l = [len(n) for n in names]
width = max(10,max(l)+2)
fmt = "%%%ss:" % width
for i in range(self.nobjects):
flags = "("+self.objects[names[i]].shortflags+")" if self.objects[names[i]].flags else "(no script)"
if isinstance(self.objects[names[i]],Collection):
print(fmt % names[i]," %s region (%d beadtypes)" % \
(self.objects[names[i]].kind,len(self.objects[names[i]].beadtype))," > ",flags)
else:
print(fmt % names[i]," %s region (beadtype=%d)" % \
(self.objects[names[i]].kind,self.objects[names[i]].beadtype)," > ",flags)
print(wrap("they are",":",", ".join(self.names),10,60,80))
print("-"*40)
return "REGION container %s with %d objects (%s)" % \
(self.name,self.nobjects,",".join(self.names))
# str() method ----------------------------
def __str__(self):
""" string representation of a region """
return "REGION container %s with %d objects (%s)" % \
(self.name,self.nobjects,",".join(self.names))
# generic GET method ----------------------------
def get(self,name):
""" returns the object """
if name in self.objects:
return self.objects[name]
else:
raise NameError('the object "%s" does not exist, use list()' % name)
# getattr() method ----------------------------
def __getattr__(self,name):
""" getattr attribute override """
if (name in self.__dict__) or (name in protectedregionkeys):
return self.__dict__[name] # higher precedence for root attributes
if name in protectedregionkeys:
return getattr(type(self), name).__get__(self) # for methods decorated as properties (@property)
# Handle special cases like __wrapped__ explicitly
if name == "__wrapped__":
return None # Default value or appropriate behavior
# Leave legitimate __dunder__ attributes to the default mechanism
if name.startswith("__") and name.endswith("__"):
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
# Default
return self.get(name)
# generic SET method ----------------------------
def set(self,name,value):
""" set field and value """
if isinstance(value,list) and len(value)==0:
if name not in self.objects:
raise NameError('the object "%s" does not exist, use list()' % name)
self.delete(name)
elif isinstance(value,coregeometry):
if name in self.objects: self.delete(name)
if isinstance(value.SECTIONS,pipescript) or isinstance(value,Evalgeometry):
self.eval(deepduplicate(value),name) # not a scalar
else: # scalar
self.objects[name] = deepduplicate(value)
self.objects[name].name = name
self.nobjects += 1
self.counter["all"] += 1
self.objects[name].index = self.counter["all"]
self.counter[value.kind] += 1
# setattr() method ----------------------------
def __setattr__(self,name,value):
""" setattr override """
if name in protectedregionkeys: # do not forget to increment protectedregionkeys
self.__dict__[name] = value # if not, you may enter in infinite loops
else:
self.set(name,value)
# generic HASATTR method ----------------------------
def hasattr(self,name):
""" return true if the object exist """
if not isinstance(name,str): raise TypeError("please provide a string")
return name in self.objects
# IN operator ----------------------------
def __contains__(self,obj):
""" in override """
return self.hasattr(obj)
# len() method ----------------------------
def __len__(self):
""" len method """
return len(self.objects)
# indexing [int] and ["str"] method ----------------------------
def __getitem__(self,idx):
"""
R[i] returns the ith element of the structure
R[:4] returns a structure with the four first fields
R[[1,3]] returns the second and fourth elements
"""
if isinstance(idx,int):
if idx<len(self):
return self.get(self.names[idx])
raise IndexError(f"the index should be comprised between 0 and {len(self)-1}")
elif isinstance(idx,str):
if idx in self:
return self.get(idx)
raise NameError(f'{idx} does not exist, use list() to list objects')
elif isinstance(idx,list):
pass
elif isinstance(idx,slice):
return self.__getitem__(self,list(range(*idx.indices(len(self)))))
else:
raise IndexError("not implemented yet")
# duplication GET method based on DICT ----------------------------
def __getstate__(self):
""" getstate for cooperative inheritance / duplication """
return self.__dict__.copy()
# duplication SET method based on DICT ----------------------------
def __setstate__(self,state):
""" setstate for cooperative inheritance / duplication """
self.__dict__.update(state)
# iterator method ----------------------------
def __iter__(self):
""" region iterator """
# note that in the original object _iter_ is a static property not in dup
dup = duplicate(self)
dup._iter_ = 0
return dup
# next iterator method ----------------------------
def __next__(self):
""" region iterator """
self._iter_ += 1
if self._iter_<=len(self):
return self[self._iter_-1]
self._iter_ = 0
raise StopIteration(f"Maximum region.objects iteration reached {len(self)}")
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#
# MIDDLE-LEVEL METHODS
#
#
# These methods are specific to PIZZA.REGION() objects.
# They bring useful methods for the user and developer.
# Similar methods exist in PIZZA.RASTER()
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# LIST method ----------------------------
def list(self):
""" list objects """
fmt = "%%%ss:" % max(10,max([len(n) for n in self.names])+2)
print('REGION container "%s" with %d objects' % (self.name,self.nobjects))
for o in self.objects.keys():
print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind,
"(beadtype=%d,object index=[%d,%d])" % \
(self.objects[o].beadtype,
self.objects[o].index,
self.objects[o].subindex))
# NAMES method set as an attribute ----------------------------
@property
def names(self):
""" return the names of objects sorted as index """
namesunsorted=namessorted=list(self.objects.keys())
nobj = len(namesunsorted)
if nobj<1:
return []
elif nobj<2:
return namessorted
else:
for iobj in range(nobj):
namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj]
return namessorted
# NBEADS method set as an attribute
@property
def nbeads(self):
"return the number of beadtypes used"
if len(self)>0:
guess = max(len(self.count()),self.live.nbeads)
return guess+1 if self.isspacefilled else guess
else:
return self.live.nbeads
# COUNT method
def count(self):
""" count objects by type """
typlist = []
for o in self.names:
if isinstance(self.objects[o].beadtype,list):
typlist += self.objects[o].beadtype
else:
typlist.append(self.objects[o].beadtype)
utypes = list(set(typlist))
c = []
for t in utypes:
c.append((t,typlist.count(t)))
return c
# BEADTYPES property
@property
def beadtypes(self):
""" list the beadtypes """
return [ x[0] for x in self.count() ]
# DELETE method
def delete(self,name):
""" delete object """
if name in self.objects:
kind = self.objects[name].kind
del self.objects[name]
self.nobjects -= 1
self.counter[kind] -= 1
self.counter["all"] -= 1
else:
raise NameError("%s does not exist (use list()) to list valid objects" % name)
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#
# HIGH-LEVEL METHODS
#
#
# These methods are connect PIZZA.REGION() objects with their equivalent
# as PIZZA.SCRIPT() and PIZZA.PIPESCRIPT() objects and methods.
#
# They are essential to PIZZA.REGION(). They do not have equivalent in
# PIZZA.RASTER(). They use extensively the methods attached to :
# PIZZA.REGION.LAMMPSGENERIC()
# PIZZA.REGION.COREGEOMETRY()
#
# Current real-time rendering relies on
# https://andeplane.github.io/atomify/
# which gives better results than
# https://editor.lammps.org/
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# EVALUATE algebraic operation on PIZZA.REGION() objects (operation on codes)
def eval(self,expression,name=None,beadtype = None,
fake=False,index = None,subindex = None):
"""
evaluates (i.e, combine scripts) an expression combining objects
R= region(name="my region")
R.eval(o1+o2+...,name='obj')
R.eval(o1|o2|...,name='obj')
R.name will be the resulting object of class region.eval (region.coregeometry)
"""
if not isinstance(expression, coregeometry): raise TypeError("the argument should be a region.coregeometry")
# prepare object creation
kind = "eval"
self.counter["all"] += 1
self.counter[kind] +=1
if index is None: index = self.counter["all"]
if subindex is None: subindex = self.counter[kind]
# create the object E with E for Ellipsoid
E = Evalgeometry((self.counter["all"],self.counter[kind]),
index=index,subindex=subindex)
# link expression to E
if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply)
if name is None: name = expression.name
if name in self.name: raise NameError('the name "%s" is already used' % name)
E.name = name
E.SECTIONS = expression.SECTIONS
E.USER = expression.USER
if isinstance(E.SECTIONS,pipescript):
# set beadtypes for all sections and scripts in the pipeline
for i in E.SECTIONS.keys():
for j in range(len(E.SECTIONS[i])):
E.SECTIONS[i].USER[j].beadtype = E.beadtype
E.USER.beadtype = beadtype
# Create the object if not fake
if fake:
self.counter["all"] -= 1
self.counter[kind] -= 1
return E
else:
self.objects[name] = E
self.nobjects += 1
return None
# PIPESCRIPT method generates a pipe for all objects and sections
def pipescript(self,printflag=False,verbose=False,verbosity=0):
printflag = self.printflag if printflag is None else printflag
verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose)
verbosity = 0 if not verbose else verbosity
""" pipescript all objects in the region """
if len(self)<1: return pipescript()
# execute all objects
for myobj in self:
if not isinstance(myobj,Collection): myobj.do(printflag=printflag,verbosity=verbosity)
# concatenate all objects into a pipe script
# for collections, only group is accepted
liste = [x.SECTIONS["variables"] for x in self if not isinstance(x,Collection) and x.hasvariables] + \
[x.SECTIONS["region"] for x in self if not isinstance(x,Collection) and x.hasregion] + \
[x.SECTIONS["create"] for x in self if not isinstance(x,Collection) and x.hascreate] + \
[x.SECTIONS["group"] for x in self if not isinstance(x,Collection) and x.hasgroup] + \
[x.SECTIONS["setgroup"] for x in self if not isinstance(x,Collection) and x.hassetgroup] + \
[x.SECTIONS["move"] for x in self if not isinstance(x,Collection) and x.hasmove]
# add the objects within the collection
for x in self:
if isinstance(x,Collection): liste += x.group()
# add the eventual group for the collection
liste += [x.SECTIONS["group"] for x in self if isinstance(x,Collection) and x.hasgroup]
# chain all scripts
return pipescript.join(liste)
# SCRIPT add header and footer to PIPECRIPT
def script(self,live=False, printflag=None, verbose=None, verbosity=None):
""" script all objects in the region """
printflag = self.printflag if printflag is None else printflag
verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose)
verbosity = 0 if not verbose else verbosity
s = self.pipescript(printflag=printflag,verbose=verbose,verbosity=verbosity).script(printflag=printflag,verbose=verbose,verbosity=verbosity)
if self.isspacefilled:
USERspacefilling =regiondata(**self.spacefilling)
s = LammpsSpacefilling(**USERspacefilling)+s
if live:
beadtypes = self.beadtypes
USER = regiondata(**self.live)
USER.nbeads = self.nbeads
USER.mass = "$"
USER.pair_coeff = "$"
# list beadtype and prepare mass, pair_coeff
beadtypes = [ x[0] for x in self.count() ]
if self.isspacefilled and self.spacefillingbeadtype not in beadtypes:
beadtypes = [self.spacefillingbeadtype]+beadtypes
for b in beadtypes:
USER.mass += livetemplate["mass"] % b +"\n"
USER.pair_coeff += livetemplate["pair_coeff"] %(b,b) +"\n"
for b1 in beadtypes:
for b2 in beadtypes:
if b2>b1:
USER.pair_coeff += livetemplate["pair_coeff"] %(b1,b2) +"\n"
livemode = "dynamic" if self.hasfixmove else "static"
USER.run =self.livelammps["options"][livemode]["run"]
s = LammpsHeader(**USER)+s+LammpsFooter(**USER)
return s
# SCRIPTHEADERS add header scripts for initializing script, lattice, box for region
def scriptHeaders(self, what=["init", "lattice", "box"], pipescript=False, **userdefinitions):
"""
Generate and return LAMMPS header scripts for initializing the simulation, defining the lattice,
and specifying the simulation box for all region objects.
Parameters:
- what (list of str): Specifies which scripts to generate. Options are "init", "lattice", "box", "mass" and "preview".
Multiple scripts can be generated by passing a list of these options.
Default is ["init", "lattice", "box"].
- pipescript (bool): If True, the generated scripts are combined with `|` instead of `+`. Default is False.
Property/pair value
- nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`.
Default is 1.
- mass (real value or list): Sets the mass for each bead, overrriding `self.mass`
Default is 1.0.
Returns:
- object: The combined header scripts as a single object.
Header values can be overridden by updating `self.headersData`.
Raises:
- Exception: If no valid script options are provided in `what`.
Example usage:
sRheader = R.scriptHeaders("box").do() # Generate the box header script.
sRallheaders = R.scriptHeaders(["init", "lattice", "box"]) # Generate all headers.
Example usage without naming parameters:
sRheader = R.scriptHeaders("box") # "what" specified as "box", nbeads defaults to 1.
Example of overriding values
sRheader = R.scriptHeaders("lattice",lattice_style = "$sq") # Generate the lattice header script with the overridden value.
"""
# handle overrides
USERregion = self.headersData + regiondata(**userdefinitions)
# Fix singletons
if not isinstance(what, list):
what = [what]
# Generate the initialization script
scripts = [] # Store all generated script objects here
if "init" in what:
scripts.append(LammpsHeaderInit(**USERregion))
# Generate the lattice script
if "lattice" in what:
scripts.append(LammpsHeaderLattice(**USERregion))
# Generate the box script
if "box" in what:
scripts.append(LammpsHeaderBox(**USERregion))
if self.isspacefilled:
scripts.append(LammpsSpacefilling(**self.spacefilling))
# Generate the mass script
if "mass" in what:
scripts.append(LammpsHeaderMass(**USERregion))
# Generate the preview script
if "preview" in what:
scripts.append(LammpsFooterPreview(**USERregion))
if not scripts:
raise Exception('nothing to do (use: "init", "lattice", "box", "mass" or "preview" within [ ])')
# Combine the scripts based on the pipescript flag
combined_script = scripts[0] # Initialize the combined script with the first element
for script in scripts[1:]:
if pipescript:
# Combine scripts using the | operator, maintaining pipescript format
combined_script = combined_script | script # p_ab = s_a | s_b or p_ab = s_a | p_b
else:
# Combine scripts using the + operator, maintaining regular script format
combined_script = combined_script + script # s_ab = s_a + s_b
return combined_script
def pscriptHeaders(self, what=["init", "lattice", "box"], **userdefinitions):
"""
Surrogate method for generating LAMMPS pipescript headers.
Calls the `scriptHeaders` method with `pipescript=True`.
Parameters:
- what (list of str): Specifies which scripts to generate. Options are "init", "lattice", and "box".
Multiple scripts can be generated by passing a list of these options.
Default is ["init", "lattice", "box"].
Property/pair value
- nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`.
Default is 1.
- mass (real value or list): Sets the mass for each bead, overrriding `self.mass`
Default is 1.0.
Returns:
- object: The combined pipescript header scripts as a single object.
"""
# Call scriptHeaders with pipescript=True
return self.scriptHeaders(what=what, pipescript=True, **userdefinitions)
# DO METHOD = main static compiler
def do(self, printflag=False, verbosity=1):
""" execute the entire script """
return self.pipescript().do(printflag=printflag, verbosity=verbosity)
# DOLIVE = fast code generation for online rendering
def dolive(self):
"""
execute the entire script for online testing
see: https://editor.lammps.org/
"""
self.livelammps["file"] = self.script(live=True).tmpwrite()
if not self.livelammps["active"]:
livelammps(self.livelammps["URL"],new=0)
self.livelammps["active"] = True
return self.livelammps["file"]
# %% scatter class and emulsion class
# Simplified scatter and emulsion generator
# generalized from its 2D version in raster.scatter and raster.emulsion
# added on 2023-03-10
class scatter():
""" generic top scatter class """
def __init__(self):
"""
The scatter class provides an easy constructor
to distribute in space objects according to their
positions x,y,z size r (radius) and beadtype.
The class is used to derive emulsions.
Returns
-------
None.
"""
self.x = np.array([],dtype=int)
self.y = np.array([],dtype=int)
self.z = np.array([],dtype=int)
self.r = np.array([],dtype=int)
self.beadtype = []
@property
def n(self):
return len(self.x)
def pairdist(self,x,y,z):
""" pair distance to the surface of all disks/spheres """
if self.n==0:
return np.Inf
else:
return np.sqrt((x-self.x)**2+(y-self.y)**2+(z-self.z)**2)-self.r
class emulsion(scatter):
""" emulsion generator """
def __init__(self, xmin=10, ymin=10, zmin=10, xmax=90, ymax=90, zmax=90,
maxtrials=1000, beadtype=1, forcedinsertion=True):
"""
Parameters
----------
The insertions are performed between xmin,ymin and xmax,ymax
xmin : int64 or real, optional
x left corner. The default is 10.
ymin : int64 or real, optional
y bottom corner. The default is 10.
zmin : int64 or real, optional
z bottom corner. The default is 10.
xmax : int64 or real, optional
x right corner. The default is 90.
ymax : int64 or real, optional
y top corner. The default is 90.
zmax : int64 or real, optional
z top corner. The default is 90.
beadtype : default beadtype to apply if not precised at insertion
maxtrials : integer, optional
Maximum of attempts for an object. The default is 1000.
forcedinsertion : logical, optional
Set it to true to force the next insertion. The default is True.
Returns
-------
None.
"""
super().__init__()
self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax = xmin, xmax, ymin, ymax, zmin, zmax
self.lastinsertion = (None,None,None,None,None) # x,y,z,r, beadtype
self.length = xmax-xmin
self.width = ymax-ymin
self.height = zmax-zmin
self.defautbeadtype = beadtype
self.maxtrials = maxtrials
self.forcedinsertion = forcedinsertion
def __repr__(self):
print(f" Emulsion object\n\t{self.length}x{self.width}x{self.height} starting at x={self.xmin}, y={self.ymin}, z={self.zmin}")
print(f"\tcontains {self.n} insertions")
print("\tmaximum insertion trials:", self.maxtrials)
print("\tforce next insertion if previous fails:", self.forcedinsertion)
return f"emulsion with {self.n} insertions"
def walldist(self,x,y,z):
""" shortest distance to the wall """
return min(abs(x-self.xmin),abs(y-self.ymin),abs(z-self.zmin),abs(x-self.xmax),abs(y-self.ymax),abs(z-self.zmax))
def dist(self,x,y,z):
""" shortest distance of the center (x,y) to the wall or any object"""
return np.minimum(np.min(self.pairdist(x,y,z)),self.walldist(x,y,z))
def accepted(self,x,y,z,r):
""" acceptation criterion """
return self.dist(x,y,z)>r
def rand(self):
""" random position x,y """
return np.random.uniform(low=self.xmin,high=self.xmax), \
np.random.uniform(low=self.ymin,high=self.ymax),\
np.random.uniform(low=self.zmin,high=self.zmax)
def setbeadtype(self,beadtype):
""" set the default or the supplied beadtype """
if beadtype == None:
self.beadtype.append(self.defautbeadtype)
return self.defautbeadtype
else:
self.beadtype.append(beadtype)
return beadtype
def insertone(self,x=None,y=None,z=None,r=None,beadtype=None,overlap=False):
"""
insert one object of radius r
properties:
x,y,z coordinates (if missing, picked randomly from uniform distribution)
r radius (default = 2% of diagonal)
beadtype (default = defautbeadtype)
overlap = False (accept only if no overlap)
"""
attempt, success = 0, False
random = (x==None) or (y==None) or (z==None)
if r==None:
r = 0.02*np.sqrt(self.length**2+self.width**2+self.height**2)
while not success and attempt<self.maxtrials:
attempt += 1
if random: x,y,z = self.rand()
if overlap:
success = True
else:
success = self.accepted(x,y,z,r)
if success:
self.x = np.append(self.x,x)
self.y = np.append(self.y,y)
self.z = np.append(self.z,z)
self.r = np.append(self.r,r)
b=self.setbeadtype(beadtype)
self.lastinsertion = (x,y,z,r,b)
return success
def insertion(self,rlist,beadtype=None):
"""
insert a list of objects
nsuccess=insertion(rlist,beadtype=None)
beadtype=b forces the value b
if None, defaultbeadtype is used instead
"""
rlist.sort(reverse=True)
ntodo = len(rlist)
n = nsuccess = 0
stop = False
while not stop:
n += 1
success = self.insertone(r=rlist[n-1],beadtype=beadtype)
if success: nsuccess += 1
stop = (n==ntodo) or (not success and not self.forcedinsertion)
if nsuccess==ntodo:
print(f"{nsuccess} objects inserted successfully")
else:
print(f"partial success: {nsuccess} of {ntodo} objects inserted")
return nsuccess
# %% debug section - generic code to test methods (press F5)
# ===================================================
# main()
# ===================================================
# for debugging purposes (code called as a script)
# the code is called from here
# ===================================================
if __name__ == '__main__':
R = region(name="my region", mass=2, density=5)
# Create a Block object using the block method of the region container with specific dimensions
R.block(xlo=0, xhi=10, ylo=0, yhi=10, zlo=0, zhi=10, name="B1",mass=3)
# Access the natoms property of the Block object
print("Number of atoms in the block:", R.B1.natoms)
# early example
a=region(name="region A")
b=region(name="region B")
c = [a,b]
# step 1
R = region(name="my region")
R.ellipsoid(0, 0, 0, 1, 1, 1,name="E1",toto=3)
R
repr(R.E1)
R.E1.VARIABLES.a=1
R.E1.VARIABLES.b=2
R.E1.VARIABLES.c="(${a},${b},100)"
R.E1.VARIABLES.d = '"%s%s" %("test",${c}) # note that test could be replaced by any function'
R.E1
code1 = R.E1.do()
print(code1)
# step 2
R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",move=["left","${up}*3",None],up=0.1)
R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})'
R.E2.VARIABLES.a="${b}-5"
R.E2.VARIABLES.b=5
R.E2.VARIABLES.c=100
code2 = R.E2.do()
print(R)
repr(R.E2)
print(code2)
print(R.names)
R.list()
# eval objects
R.set('E3',R.E2)
R.E3.beadtype = 2
R.set('add',R.E1 + R.E2)
R.addd2 = R.E1 + R.E2
R.eval(R.E1 | R.E2,'E12')
# How to manage pipelines
print("\n","-"*20,"pipeline","-"*20)
p = R.E2.script
s = p.script() # first execution
s = p.script() # do nothing
s # check
# reorganize scripts
print("\n","-"*20,"change order","-"*20)
p.clear() # undo executions first
q = p[[0,2,1]]
sq = q.script()
print(q.do())
# join sections
liste = [x.SECTIONS["variables"] for x in R]
pliste = pipescript.join(liste)
# Example closer to production
P = region(name="live test",width = 20)
P.ellipsoid(0, 0, 0, "${Ra}", "${Rb}", "${Rc}",
name="E1", Ra=5,Rb=2,Rc=3)
P.sphere(7,0,0,radius="${R}",name = "S1", R=2)
cmd = P.do()
print(cmd)
#outputfile = P.dolive()
# EXAMPLE: gel compression
scale = 1
name = ['top','food','tongue','bottom']
radius = [10,5,8,10]
height = [1,4,3,1]
spacer = 2 * scale
radius = [r*scale for r in radius]
height = [h*scale for h in height]
position_original = [spacer+height[1]+height[2]+height[3],
height[2]+height[3],
height[3],
0]
beadtype = [1,2,3,1]
total_height = sum(height) +spacer
position = [x-total_height/2 for x in position_original]
B = region(name = 'region container',
width=2*max(radius),
height=total_height,
depth=2*max(radius))
for i in range(len(name)):
B.cylinder(name = name[i],
c1=0,
c2=0,
radius=radius[i],
lo=position[i],
hi=position[i]+height[i],
beadtype=beadtype[i])
B.dolive()
# Draft for workshop
sB = B.do()
b1 = B[0].scriptobject()
b2 = B[1].scriptobject()
b3 = B[2].scriptobject()
b4 = B[3].scriptobject()
collection = b1 + b2 + b3 + b4;
# # emulsion example
scale = 1 # tested up to scale = 10 to reach million of beads
mag = 3
e = emulsion(xmin=-5*mag, ymin=-5*mag, zmin=-5*mag,xmax=5*mag, ymax=5*mag, zmax=5*mag)
e.insertion([2,2,2,1,1.6,1.2,1.4,1.3],beadtype=3)
e.insertion([0.6,0.3,2,1.5,1.5,1,2,1.2,1.1,1.3],beadtype=1)
e.insertion([3,1,2,2,4,1,1.2,2,2.5,1.2,1.4,1.6,1.7],beadtype=2)
e.insertion([3,1,2,2,4,1,5.2,2,4.5,1.2,1.4,1.6,1.7],beadtype=4)
# b = region()
# a = region()
# a.sphere(1,1,1,1,name='sphere1')
# a.sphere(1,2,2,1,name='sphere2')
# b.collection(a, name='acollection')
C = region(name='cregion',width=11*mag,height=11*mag,depth=11*mag)
C.scatter(e)
C.script()
g = C.emulsion.group()
C.dolive()
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# F O R P R O D U C T I O N
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# History: 2024-07-04 (first version), 2024-07-29 (update), 2024-09-01 (community, per request)
"""
=== [ S Y N O P S I S ] ===
This script provides a detailed example of simulating gel compression using cylindrical objects
within a defined region, employing SI units. The example is designed for production and includes
steps to create, script, and visualize the simulation setup using LAMMPS-compatible scripts.
Key Features:
1. **Geometry Setup**:
- Four cylindrical objects ('top', 'food', 'tongue', 'bottom') are defined with specific radii
and heights.
- The cylinders are positioned within a central container, with spacing determined by a spacer
element.
- The total height of the system is calculated, and the objects are centered within the region.
2. **Forcefield Assignment**:
- Each object is assigned a bead type and grouped with attributes such as rigidity or softness.
- Custom forcefields are applied to each object, simulating different physical properties like
rigid walls or soft materials.
3. **Region Definition**:
- A simulation region is created with specific dimensions, accounting for the maximum radius of
the cylinders and the total height of the system.
- The region is defined in SI units, with additional parameters like separation distance and
lattice scale.
4. **Script Generation**:
- The script converts the defined region and objects into LAMMPS-compatible code.
- Header scripts for initialization, lattice, and the bounding box are generated.
- The example emphasizes the flexibility in scripting, allowing dynamic reordering and
combination of scripts.
5. **Execution and Visualization**:
- The region setup is executed for visualization purposes, enabling control and inspection of
the geometry.
- The geometry details, including an estimation of the number of atoms, are provided for
further analysis.
This example showcases how to effectively set up a gel compression simulation, highlighting key
aspects of geometry definition, forcefield application, and scripting for simulation execution.
"""
# EXAMPLE: gel compression with SI units
name = ['top', 'food', 'tongue', 'bottom']
radius = [10e-3, 5e-3, 8e-3, 10e-3] # in m
height = [1e-3, 4e-3, 3e-3, 1e-3] # in m
spacer = 2e-3 # in m
# Calculate positions in SI units (meters)
position_original = [
spacer + height[1] + height[2] + height[3],
height[2] + height[3],
height[3],
0
]
total_height = sum(height) + spacer * 1e-3 # converting spacer to meters
# Center positions around the middle of the container
position = [x - total_height / 2 for x in position_original]
# information for beads
# add attributes to forcefields to match your needs or derive new forcefields
beadtypes = [1, 2, 3, 1]
groups = [["rigid","wall1"],["food1","soft"],["food2","soft"],["rigid","wall2"]]
forcefields = [rigidwall(),solidfood(),solidfood(),rigidwall()]
# Create the region container with SI units
R = region(
name='region container',
width=2 * max(radius),
height=total_height,
depth=2 * max(radius),
regionunits="si",
separationdistance=100e-6, # 50 µm
lattice_scale=100e-6 # 50 µm
)
# Add cylinders to the region R
# the objects are added "statically"
# since they contain variables a do() is required to make them a script
nobjects = len(name)
for i in range(nobjects):
R.cylinder(
name=name[i],
dim="z", # Assuming z-axis as the dimension
c1=0,
c2=0,
radius=radius[i],
lo=position[i],
hi=position[i] + height[i],
beadtype=beadtypes[i],
style="smd", # the script oject properties
group=groups[i], # can be defined in the geometry or
forcefield=forcefields[i] # when scriptoject() is called
)
# Compile statically all objects
# sR contains the LAMMPS code to generate all region objects and their atoms
# sR is a string, all variables have been executed
sR = R.do() # this line force the execution of R
# Header Scripts facilitate the deployment and initialization of region objects.
# ------------- Summary ---------------
# Available scripts include "init", "lattice", and "box".
# Multiple scripts can be generated simultaneously by specifying them in a list.
# For example: ["init", "lattice", "box"] will generate all three scripts.
# Script parameters and variables can be customized via R.headersData.
# For instance: R.headersData.lattice_style = "$sq"
# This overrides the lattice style, which was originally set in the region object.
# The "$" prefix indicates that lattice_style is a static value.
# Alternatively, R.headersData.lattice_style = ["sq"] can also be used.
# --------------------------------------
# use help(R.scriptHeaders) to get a full help
# Note: sRheader is a string since a do()
sRheader = R.scriptHeaders("box").do() # generate the box that contains R
print(sRheader)
# To generate all header scripts in the specified order, use R.scriptHeaders.
# Note: sRallheaders is a script object. Use sRallheaders.do() to convert it into a string.
# Scripts can be dynamically combined using the + operator or statically with the & operator.
# Scripts can also be combined with pipescripts using the + or | (piped) operator.
# Region and collection objects are considered pipescripts.
#
# Comment on the differences between scripts and pipescripts:
# - Scripts operate within a single variable space and cannot be reordered once combined.
# - Pipescripts, however, include both global and local variable spaces and can be reordered,
# and indexed, offering greater flexibility in complex simulations.
#
# A property can be removed from the initialization process by setting it to None or ""
# In this example, atom_style is removed as it also set with forcefields
R.headersData.atom_style = None
sRallheaders = R.scriptHeaders(["init", "lattice", "box"] )
# Generate information on beads from the scripted objects
# note that scriptobject is a method of script extended to region
# the region must have been preallably scripted, which has been done with "sR = R.do()"
# Note that the current implementation include also style definitions in init
b = []
for i in range(nobjects):
# style, group and forcefield can be overdefined if needed
b.append(R[i].scriptobject(style="smd"))
collection = b[0] + b[1] + b[2] + b[3]
# The script corresponding to the collection is given by:
# scollection is an object of the class script
# its final execution can be still affected by variables
scollection = collection.script.do()
# Execute the region setup only for visualization (control only)
R.dolive()
# The detail of the geometry with an estimation of the number of atoms (control only)
R.geometry
# to be continued as in the previous workshops
# sRheader, sR and scollection can be concatenated (they are strings)
# Note that scripts can be concatenated before do()
Functions
def cleanname(name)
-
Expand source code
cleanname = lambda name: "".join([x for x in name if x!="$"])
def span(vector, sep=' ', left='', right='')
-
Expand source code
def span(vector,sep=" ",left="",right=""): return left + (vector if isinstance(vector, str) else sep.join(map(str, vector))) + right if vector is not None else ""
def wrap(k, op, v, indent, width, maxwidth)
-
Expand source code
wrap = lambda k,op,v,indent,width,maxwidth: fill( shorten(v,width=maxwidth+indent, fix_sentence_endings=True), width=width+indent, initial_indent=" "*(indent-len(k)-len(op)-2)+f'{k} {op} ', subsequent_indent=' '*(indent+(1 if v[0]=='"' else 0) ) )
Classes
class Block (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Block class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Block(coregeometry): """ Block class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "block%03d" % counter[1] self.kind = "block" # kind of object self.alike = "block" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$block"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): """Calculate the volume of the block based on USER.args""" #args = [xlo, xhi, ylo, yhi, zlo, zhi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args xlo = float(args[0]) xhi = float(args[1]) ylo = float(args[2]) yhi = float(args[3]) zlo = float(args[4]) zhi = float(args[5]) # Calculate the dimensions of the block length = xhi - xlo width = yhi - ylo height = zhi - zlo # Calculate the volume of the block volume = length * width * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the block based on USER.args
Expand source code
def volume(self,units=None): """Calculate the volume of the block based on USER.args""" #args = [xlo, xhi, ylo, yhi, zlo, zhi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args xlo = float(args[0]) xhi = float(args[1]) ylo = float(args[2]) yhi = float(args[3]) zlo = float(args[4]) zhi = float(args[5]) # Calculate the dimensions of the block length = xhi - xlo width = yhi - ylo height = zhi - zlo # Calculate the volume of the block volume = length * width * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Collection (counter, name=None, index=None, subindex=None, hasgroup=False, USER=region data (RD object) with 0 definitions)
-
Collection class (including many objects)
Expand source code
class Collection: """ Collection class (including many objects) """ _version = "0.31" __custom_documentations__ = "pizza.region.Collection class" # CONSTRUCTOR def __init__(self,counter, name=None, index = None, subindex = None, hasgroup = False, USER = regiondata()): if (name is None) or (name==""): self.name = "collect%03d" % counter[1] elif name in self: raise KeyError(f'the name "{name}" already exist') else: self.name = name if not isinstance(USER,regiondata): raise TypeError("USER should be a regiondata object") USER.groupID = "$"+self.name # the content is frozen USER.ID = "" self.USER = USER self.kind = "collection" # kind of object self.alike = "mixed" # similar object for plotting self.index = counter[0] if index is None else index self.subindex = counter[1] self.collection = regioncollection() self.SECTIONS = { 'group': LammpsCollectionGroup(**USER) } self.FLAGSECTIONS = {"group": hasgroup} def update(self): """ update the USER content for the script """ if isinstance(self.SECTIONS["group"],script): self.USER.ID = "$"\ +span([groupprefix+x for x in self.list()]) # the content is frozen self.SECTIONS["group"].USER += self.USER def creategroup(self): """ force the group creation in script """ for o in self.collection: o.creategroup() self.update() self.FLAGSECTIONS["group"] = True def removegroup(self,recursive=True): """ force the group creation in script """ if recursive: for o in self.collection: o.removegroup() self.FLAGSECTIONS["group"] = False @property def hasgroup(self): """ return the flag hasgroup """ return self.FLAGSECTIONS["group"] @property def flags(self): """ return a list of all flags that are currently set """ flag_names = list(self.SECTIONS.keys()) return [flag for flag in flag_names if getattr(self, f"has{flag}")] @property def shortflags(self): """ return a string made from the first letter of each set flag """ return "".join([flag[0] for flag in self.flags]) @property def script(self): """ generates a pipe script from SECTIONS """ self.update() return self.SECTIONS["group"] def __repr__(self): keylengths = [len(key) for key in self.collection.keys()] width = max(10,max(keylengths)+2) fmt = "%%%ss:" % width line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) ) print(line," %s - %s object" % (self.name, self.kind), line,sep="\n") for key,value in self.collection.items(): flags = "("+self.collection[key].shortflags+")" if self.collection[key].flags else "(no script)" print(fmt % key,value.kind, '"%s"' % value.name," > ",flags) flags = self.flags if flags: print(line,f'defined scripts: {span(flags,sep=",")}',sep="\n") print(line) return "%s object: %s (beadtype=[%s])" % (self.kind,self.name,", ".join(map(str,self.beadtype))) # GET ----------------------------- def get(self,name): """ returns the object """ if name in self.collection: return self.collection.getattr(name) elif name in ["collection","hasgroup","flags","shortflags","script"]: return getattr(self,name) else: raise ValueError('the object "%s" does not exist, use list()' % name) # GETATTR -------------------------- def __getattr__(self,key): """ get attribute override """ return self.get(key) @property def beadtype(self): """ returns the beadtypes used in the collection """ b = [] for o in self.collection: if o.beadtype not in b: b.append(o.beadtype) if len(b)==0: return 1 else: return b # GROUP ------------------------------- def group(self): """ return the grouped coregeometry object """ if len(self) == 0:return pipescript() # execute all objects for i in range(len(self)): self.collection[i].do() # concatenate all objects into a pipe script liste = [x.SECTIONS["variables"] for x in self.collection if x.hasvariables] + \ [x.SECTIONS["region"] for x in self.collection if x.hasregion] + \ [x.SECTIONS["create"] for x in self.collection if x.hascreate] + \ [x.SECTIONS["group"] for x in self.collection if x.hasgroup] + \ [x.SECTIONS["setgroup"] for x in self.collection if x.hassetgroup] + \ [x.SECTIONS["move"] for x in self.collection if x.hasmove] return pipescript.join(liste) # LEN --------------------------------- def __len__(self): """ return length of collection """ return len(self.collection) # LIST --------------------------------- def list(self): """ return the list of objects """ return self.collection.keys()
Instance variables
var beadtype
-
returns the beadtypes used in the collection
Expand source code
@property def beadtype(self): """ returns the beadtypes used in the collection """ b = [] for o in self.collection: if o.beadtype not in b: b.append(o.beadtype) if len(b)==0: return 1 else: return b
var flags
-
return a list of all flags that are currently set
Expand source code
@property def flags(self): """ return a list of all flags that are currently set """ flag_names = list(self.SECTIONS.keys()) return [flag for flag in flag_names if getattr(self, f"has{flag}")]
var hasgroup
-
return the flag hasgroup
Expand source code
@property def hasgroup(self): """ return the flag hasgroup """ return self.FLAGSECTIONS["group"]
var script
-
generates a pipe script from SECTIONS
Expand source code
@property def script(self): """ generates a pipe script from SECTIONS """ self.update() return self.SECTIONS["group"]
var shortflags
-
return a string made from the first letter of each set flag
Expand source code
@property def shortflags(self): """ return a string made from the first letter of each set flag """ return "".join([flag[0] for flag in self.flags])
Methods
def creategroup(self)
-
force the group creation in script
Expand source code
def creategroup(self): """ force the group creation in script """ for o in self.collection: o.creategroup() self.update() self.FLAGSECTIONS["group"] = True
def get(self, name)
-
returns the object
Expand source code
def get(self,name): """ returns the object """ if name in self.collection: return self.collection.getattr(name) elif name in ["collection","hasgroup","flags","shortflags","script"]: return getattr(self,name) else: raise ValueError('the object "%s" does not exist, use list()' % name)
def group(self)
-
return the grouped coregeometry object
Expand source code
def group(self): """ return the grouped coregeometry object """ if len(self) == 0:return pipescript() # execute all objects for i in range(len(self)): self.collection[i].do() # concatenate all objects into a pipe script liste = [x.SECTIONS["variables"] for x in self.collection if x.hasvariables] + \ [x.SECTIONS["region"] for x in self.collection if x.hasregion] + \ [x.SECTIONS["create"] for x in self.collection if x.hascreate] + \ [x.SECTIONS["group"] for x in self.collection if x.hasgroup] + \ [x.SECTIONS["setgroup"] for x in self.collection if x.hassetgroup] + \ [x.SECTIONS["move"] for x in self.collection if x.hasmove] return pipescript.join(liste)
def list(self)
-
return the list of objects
Expand source code
def list(self): """ return the list of objects """ return self.collection.keys()
def removegroup(self, recursive=True)
-
force the group creation in script
Expand source code
def removegroup(self,recursive=True): """ force the group creation in script """ if recursive: for o in self.collection: o.removegroup() self.FLAGSECTIONS["group"] = False
def update(self)
-
update the USER content for the script
Expand source code
def update(self): """ update the USER content for the script """ if isinstance(self.SECTIONS["group"],script): self.USER.ID = "$"\ +span([groupprefix+x for x in self.list()]) # the content is frozen self.SECTIONS["group"].USER += self.USER
class Cone (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Cone class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Cone(coregeometry): """ Cone class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "cone%03d" % counter[1] self.kind = "cone" # kind of object self.alike = "cone" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$cone"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): """Calculate the volume of the cone based on USER.args""" #args = [dim, c1, c2, radlo, radhi, lo, hi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius_low = float(args[3]) radius_high = float(args[4]) lo = float(args[5]) hi = float(args[6]) # Calculate the height of the cone height = hi - lo # Calculate the volume of the cone (assuming a conical frustum if radii are different) if radius_low == radius_high: volume = (1/3) * 3.141592653589793 * (radius_low ** 2) * height else: volume = (1/3) * 3.141592653589793 * height * (radius_low ** 2 + radius_low * radius_high + radius_high ** 2) return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the cone based on USER.args
Expand source code
def volume(self,units=None): """Calculate the volume of the cone based on USER.args""" #args = [dim, c1, c2, radlo, radhi, lo, hi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius_low = float(args[3]) radius_high = float(args[4]) lo = float(args[5]) hi = float(args[6]) # Calculate the height of the cone height = hi - lo # Calculate the volume of the cone (assuming a conical frustum if radii are different) if radius_low == radius_high: volume = (1/3) * 3.141592653589793 * (radius_low ** 2) * height else: volume = (1/3) * 3.141592653589793 * height * (radius_low ** 2 + radius_low * radius_high + radius_high ** 2) return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Cylinder (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Cylinder class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Cylinder(coregeometry): """ Cylinder class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "cylinder%03d" % counter[1] self.kind = "cylinder" # kind of object self.alike = "cylinder" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$cylinder"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): """Calculate the volume of the cylinder based on USER.args""" # args = [dim,c1,c2,radius,lo,hi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius = float(args[3]) lo = float(args[4]) hi = float(args[5]) # Calculate the height of the cylinder height = hi - lo # Calculate the volume of the cylinder volume = 3.141592653589793 * (radius ** 2) * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the cylinder based on USER.args
Expand source code
def volume(self,units=None): """Calculate the volume of the cylinder based on USER.args""" # args = [dim,c1,c2,radius,lo,hi] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius = float(args[3]) lo = float(args[4]) hi = float(args[5]) # Calculate the height of the cylinder height = hi - lo # Calculate the volume of the cylinder volume = 3.141592653589793 * (radius ** 2) * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Ellipsoid (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Ellipsoid class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Ellipsoid(coregeometry): """ Ellipsoid class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "ellipsoid%03d" % counter[1] self.kind = "ellipsoid" # kind of object self.alike = "ellipsoid" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$ellipsoid"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): #args = [x, y, z, a, b, c] """Calculate the volume of the ellipsoid based on USER.args""" try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args a = float(args[3]) b = float(args[4]) c = float(args[5]) # Calculate the volume of the ellipsoid volume = (4/3) * 3.141592653589793 * a * b * c return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the ellipsoid based on USER.args
Expand source code
def volume(self,units=None): #args = [x, y, z, a, b, c] """Calculate the volume of the ellipsoid based on USER.args""" try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args a = float(args[3]) b = float(args[4]) c = float(args[5]) # Calculate the volume of the ellipsoid volume = (4/3) * 3.141592653589793 * a * b * c return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Evalgeometry (counter, index=None, subindex=None, hasgroup=False, hasmove=False, spacefilling=False)
-
generic class to store evaluated objects with region.eval()
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Evalgeometry(coregeometry): """ generic class to store evaluated objects with region.eval() """ def __init__(self,counter,index=None,subindex=None, hasgroup=False,hasmove=False,spacefilling=False): self.name = "eval%03d" % counter[1] self.kind = "eval" # kind of object self.alike = "eval" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex super().__init__(hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling)
Ancestors
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Intersect (counter, index=None, subindex=None, hasgroup=False, hasmove=False, spacefilling=False, **variables)
-
Intersect class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Intersect(coregeometry): """ Intersect class """ def __init__(self,counter,index=None,subindex=None, hasgroup=False,hasmove=False,spacefilling=False,**variables): self.name = "intersect%03d" % counter[1] self.kind = "intersect" # kind of object self.alike = "operator" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex # call the generic constructor super().__init__( USER = regiondata(style="$intersect"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling )
Ancestors
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class LammpsCollectionGroup (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
Collection group class based on script
constructor adding instance definitions stored in USER
Expand source code
class LammpsCollectionGroup(LammpsGeneric): """ Collection group class based on script """ name = "LammpsCollection Group" SECTIONS = ["COLLECTIONGROUP"] position = 6 role = "group command definition for a collection" description = "group ID union regionID1 regionID2..." userid = "collectionregion" # user name version = 0.3 # version verbose = True # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", groupID = "$"+groupprefix+"${ID}", # freeze the interpretation hasvariables = False ) # Template (ID is spanned over all regionIDs) TEMPLATE = """ % Create group ${groupID} region ${ID} (URL: https://docs.lammps.org/group.html) group ${groupID} union ${ID} """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsCreate (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
script for LAMMPS variables section
constructor adding instance definitions stored in USER
Expand source code
class LammpsCreate(LammpsGeneric): """ script for LAMMPS variables section """ name = "LammpsCreate" SECTIONS = ["create_atoms"] position = 4 role = "create_atoms command" description = "create_atoms type style args keyword values ..." userid = "create" version = 0.1 verbose = True # Definitions used in TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", style = "${style}", hasvariables = False ) # Template (using % instead of # enables replacements) TEMPLATE = """ % Create atoms of type ${beadtype} for ${ID} ${style} (https://docs.lammps.org/create_atoms.html) create_atoms ${beadtype} region ${ID} """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
-
generic header for pizza.region
constructor adding instance definitions stored in USER
Expand source code
class LammpsFooter(LammpsGeneric): """ generic header for pizza.region """ name = "LammpsFooter" SECTIONS = ["FOOTER"] position = 1000 role = "footer for live view" description = "To be used with https://editor.lammps.org/" userid = "footer" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( run = 1, hasvariables = False ) # Template TEMPLATE = """ # --------------[ DYNAMICS ]-------------- ${mass} velocity all create 1.44 87287 loop geom pair_style lj/cut 2.5 ${pair_coeff} neighbor 0.3 bin neigh_modify delay 0 every 20 check no fix 1 all nve run ${run} # ------------------------------------------ """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
Inherited members
-
Box header for pizza.region
Use R.headersData.property = value to assign a value with R a pizza.region object
constructor adding instance definitions stored in USER
Expand source code
class LammpsFooterPreview(LammpsGeneric): # --- helper script --- """ Box header for pizza.region Use R.headersData.property = value to assign a value with R a pizza.region object """ name = "LammpsFooterPreview" SECTIONS = ["Footer"] position = 0 role = "box footer for pizza.region" description = "helper method" userid = "footerpreview" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE # circular references (the variable is defined by its field in USER of class regiondata) # are not needed but this explicits the requirements. # All fields are stored in R.headersData with R a region object. # Use R.headersData.property = value to assign a value # Extra arguments # ${boxid_arg} is by default "box" # ${boxunits_arg} can be "", "units lattice", "units box" DEFINITIONS = scriptdata( filename = "${previewfilename}", hasvariables = False ) # Template TEMPLATE = """ % --------------[ Preview for <${name}:${boxid}> incl. ${nbeads} bead types ]-------------- % Output the initial geometry to a dump file "${previewfilename}" for visualization dump initial_dump all custom 1 ${previewfilename} id type x y z run 0 # ------------------------------------------ """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
Inherited members
class LammpsGeneric (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
common class to override standard do() method from script LammpsVariables, LammpsRegion, LammpsCreate are LammpsGeneric note:: the only difference with the common script class is that LammpsGeneric accepts VARIABLES AND To SHOW THEM
constructor adding instance definitions stored in USER
Expand source code
class LammpsGeneric(script): """ common class to override standard do() method from script LammpsVariables, LammpsRegion, LammpsCreate are LammpsGeneric note:: the only difference with the common script class is that LammpsGeneric accepts VARIABLES AND To SHOW THEM """ def do(self,printflag=True,verbose=False): """ generate the LAMMPS code with VARIABLE definitions """ if self.DEFINITIONS.hasvariables and hasattr(self,'VARIABLES'): # attribute VARIABLES checked 2023-08-11 cmd = f"#[{str(datetime.now())}] {self.name} > {self.SECTIONS[0]}" \ if verbose else "" if len(self.VARIABLES)>0: cmd += \ self.VARIABLES.generatorforlammps(verbose=verbose,hasvariables=True) else: cmd = "" cmd += super().do(printflag=False,verbose=verbose) if printflag: print(cmd) return cmd
Ancestors
- pizza.script.script
Subclasses
- LammpsCollectionGroup
- LammpsCreate
- LammpsFooter
- LammpsFooterPreview
- LammpsGroup
- LammpsHeader
- LammpsHeaderBox
- LammpsHeaderInit
- LammpsHeaderLattice
- LammpsHeaderMass
- LammpsMove
- LammpsRegion
- LammpsSetGroup
- LammpsSpacefilling
- LammpsVariables
Methods
def do(self, printflag=True, verbose=False)
-
generate the LAMMPS code with VARIABLE definitions
Expand source code
def do(self,printflag=True,verbose=False): """ generate the LAMMPS code with VARIABLE definitions """ if self.DEFINITIONS.hasvariables and hasattr(self,'VARIABLES'): # attribute VARIABLES checked 2023-08-11 cmd = f"#[{str(datetime.now())}] {self.name} > {self.SECTIONS[0]}" \ if verbose else "" if len(self.VARIABLES)>0: cmd += \ self.VARIABLES.generatorforlammps(verbose=verbose,hasvariables=True) else: cmd = "" cmd += super().do(printflag=False,verbose=verbose) if printflag: print(cmd) return cmd
class LammpsGroup (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
generic group class based on script
constructor adding instance definitions stored in USER
Expand source code
class LammpsGroup(LammpsGeneric): """ generic group class based on script """ name = "LammpsGroup" SECTIONS = ["GROUP"] position = 5 role = "group command definition" description = "group ID region regionID" userid = "region" # user name version = 0.2 # version verbose = True # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", groupID = "$"+groupprefix+"${ID}", # freeze the interpretation countgroupID = "$count"+"${groupID}", # either using $ grouptoshow = ["${groupID}"], # or [] hasvariables = False ) # Template (using % instead of # enables replacements) TEMPLATE = """ % Create group ${groupID} region ${ID} (URL: https://docs.lammps.org/group.html) group ${groupID} region ${ID} variable ${countgroupID} equal count(${grouptoshow}) print "Number of atoms in ${groupID}: \${{countgroupID}}" """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsHeader (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
generic header for pizza.region
constructor adding instance definitions stored in USER
Expand source code
class LammpsHeader(LammpsGeneric): """ generic header for pizza.region """ name = "LammpsHeader" SECTIONS = ["HEADER"] position = 0 role = "header for live view" description = "To be used with https://editor.lammps.org/" userid = "header" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( width = 10, height = 10, depth = 10, nbeads = 1, hasvariables = False ) # Template TEMPLATE = """ # --------------[ INIT ]-------------- # assuming generic LJ units and style units ${live_units} atom_style ${live_atom_style} lattice ${live_lattice_style} ${live_lattice_scale} # ------------------------------------------ # --------------[ B O X ]-------------- variable halfwidth equal ${width}/2 variable halfheight equal ${height}/2 variable halfdepth equal ${depth}/2 region box block -${halfwidth} ${halfwidth} -${halfheight} ${halfheight} -${halfdepth} ${halfdepth} create_box ${nbeads} box # ------------------------------------------ """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsHeaderBox (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
Box header for pizza.region
Use R.headersData.property = value to assign a value with R a pizza.region object
constructor adding instance definitions stored in USER
Expand source code
class LammpsHeaderBox(LammpsGeneric): # --- helper script --- """ Box header for pizza.region Use R.headersData.property = value to assign a value with R a pizza.region object """ name = "LammpsHeaderBox" SECTIONS = ["HEADER"] position = 0 role = "box header for pizza.region" description = "helper method" userid = "headerbox" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE # circular references (the variable is defined by its field in USER of class regiondata) # are not needed but this explicits the requirements. # All fields are stored in R.headersData with R a region object. # Use R.headersData.property = value to assign a value # Extra arguments # ${boxid_arg} is by default "box" # ${boxunits_arg} can be "", "units lattice", "units box" DEFINITIONS = scriptdata( name = "${name}", xmin = "${xmin}", xmax = "${xmax}", ymin = "${ymin}", ymax = "${ymax}", zmin = "${zmin}", zmax = "${zmax}", nbeads = "${nbeads}", boxid = "${boxid}", boxunits_arg = "", # default units hasvariables = False ) # Template TEMPLATE = """ % --------------[ Box for <${name}:${boxid}> incl. ${nbeads} bead types ]-------------- region ${boxid} block ${xmin} ${xmax} ${ymin} ${ymax} ${zmin} ${zmax} ${boxunits_arg} create_box ${nbeads} ${boxid} # ------------------------------------------ """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsHeaderInit (persistentfile=True, persistentfolder=None, **userdefinitions)
-
Generates an initialization header script for a pizza.region object in LAMMPS.
This class constructs a LAMMPS header based on user-defined properties stored in
R.headersData
of the pizza.region object. Properties set toNone
or an empty string will be omitted from the script.Attributes
DEFINITIONS
- Defines the parameters like dimension, units, boundary, etc.,
that can be set in
R.headersData
.Methods
init(persistentfile=True, persistentfolder=None, **userdefinitions): Initializes the header script and sets up the
USER
attribute.generate_template(): Creates the header template based on the provided
USER
definitions.Note: This class is primarily intended for internal use within the simulation setup.
Constructor adding instance definitions stored in USER.
Expand source code
class LammpsHeaderInit(LammpsGeneric): # --- helper script --- """ Generates an initialization header script for a pizza.region object in LAMMPS. This class constructs a LAMMPS header based on user-defined properties stored in `R.headersData` of the pizza.region object. Properties set to `None` or an empty string will be omitted from the script. Attributes: DEFINITIONS: Defines the parameters like dimension, units, boundary, etc., that can be set in `R.headersData`. Methods: __init__(persistentfile=True, persistentfolder=None, **userdefinitions): Initializes the header script and sets up the `USER` attribute. generate_template(): Creates the header template based on the provided `USER` definitions. Note: This class is primarily intended for internal use within the simulation setup. """ name = "LammpsHeaderBox" SECTIONS = ["HEADER"] position = -2 role = "initialization header for pizza.region" description = "helper method" userid = "headerinit" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE # circular references (the variable is defined by its field in USER of class regiondata) # are not needed but this explicits the requirements. # All fields are stored in R.headersData with R a region object. # Use R.headersData.property = value to assign a value # Use R.headersData.property = None or "" to prevent the initialization of property DEFINITIONS = scriptdata( regionname = "${name}", dimension = "${dimension}", units = "${units}", boundary = "${boundary}", atom_style = "${atom_style}", atom_modify = "${atom_modify}", comm_modify = "${comm_modify}", neigh_modify = "${neigh_modify}", newton = "${newton}", hasvariables = False ) def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions): """Constructor adding instance definitions stored in USER.""" super().__init__(persistentfile, persistentfolder, **userdefinitions) self.generate_template() def generate_template(self): """Generate the TEMPLATE based on USER definitions.""" self.TEMPLATE = """ % --------------[ Initialization for <${name}:${boxid}> ]-------------- """ self.TEMPLATE += '# set a parameter to None or "" to remove the definition\n' if self.USER.dimension: self.TEMPLATE += "dimension ${dimension}\n" if self.USER.units: self.TEMPLATE += "units ${units}\n" if self.USER.boundary: self.TEMPLATE += "boundary ${boundary}\n" if self.USER.atom_style: self.TEMPLATE += "atom_style ${atom_style}\n" if self.USER.atom_modify: self.TEMPLATE += "atom_modify ${atom_modify}\n" if self.USER.comm_modify: self.TEMPLATE += "comm_modify ${comm_modify}\n" if self.USER.neigh_modify:self.TEMPLATE += "neigh_modify ${neigh_modify}\n" if self.USER.newton: self.TEMPLATE += "newton ${newton}\n" self.TEMPLATE += "# ------------------------------------------\n"
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var description
var name
var position
var role
var userid
var verbose
var version
Methods
def generate_template(self)
-
Generate the TEMPLATE based on USER definitions.
Expand source code
def generate_template(self): """Generate the TEMPLATE based on USER definitions.""" self.TEMPLATE = """ % --------------[ Initialization for <${name}:${boxid}> ]-------------- """ self.TEMPLATE += '# set a parameter to None or "" to remove the definition\n' if self.USER.dimension: self.TEMPLATE += "dimension ${dimension}\n" if self.USER.units: self.TEMPLATE += "units ${units}\n" if self.USER.boundary: self.TEMPLATE += "boundary ${boundary}\n" if self.USER.atom_style: self.TEMPLATE += "atom_style ${atom_style}\n" if self.USER.atom_modify: self.TEMPLATE += "atom_modify ${atom_modify}\n" if self.USER.comm_modify: self.TEMPLATE += "comm_modify ${comm_modify}\n" if self.USER.neigh_modify:self.TEMPLATE += "neigh_modify ${neigh_modify}\n" if self.USER.newton: self.TEMPLATE += "newton ${newton}\n" self.TEMPLATE += "# ------------------------------------------\n"
Inherited members
class LammpsHeaderLattice (persistentfile=True, persistentfolder=None, **userdefinitions)
-
Lattice header for pizza.region
Use R.headersData.property = value to assign a value with R a pizza.region object
Constructor adding instance definitions stored in USER.
Expand source code
class LammpsHeaderLattice(LammpsGeneric): # --- helper script --- """ Lattice header for pizza.region Use R.headersData.property = value to assign a value with R a pizza.region object """ name = "LammpsHeaderLattice" SECTIONS = ["HEADER"] position = 0 role = "lattice header for pizza.region" description = "helper method" userid = "headerlattice" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE # circular references (the variable is defined by its field in USER of class regiondata) # are not needed but this explicits the requirements. # All fields are stored in R.headersData with R a region object. # Use R.headersData.property = value to assign a value DEFINITIONS = scriptdata( lattice_style = "${lattice_style}", lattice_scale = "${lattice_scale}", hasvariables = False ) def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions): """Constructor adding instance definitions stored in USER.""" super().__init__(persistentfile, persistentfolder, **userdefinitions) self.generate_template() def generate_template(self): """Generate the TEMPLATE based on USER definitions.""" self.TEMPLATE = "\n% --------------[ Lattice for <${name}:${boxid}>, style=${lattice_style}, scale=${lattice_scale} ]--------------\n" if self.USER.lattice_spacing is None: self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale}\n" else: self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale} spacing ${lattice_spacing}\n" self.TEMPLATE += "# ------------------------------------------\n"
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var description
var name
var position
var role
var userid
var verbose
var version
Methods
def generate_template(self)
-
Generate the TEMPLATE based on USER definitions.
Expand source code
def generate_template(self): """Generate the TEMPLATE based on USER definitions.""" self.TEMPLATE = "\n% --------------[ Lattice for <${name}:${boxid}>, style=${lattice_style}, scale=${lattice_scale} ]--------------\n" if self.USER.lattice_spacing is None: self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale}\n" else: self.TEMPLATE += "lattice ${lattice_style} ${lattice_scale} spacing ${lattice_spacing}\n" self.TEMPLATE += "# ------------------------------------------\n"
Inherited members
class LammpsHeaderMass (persistentfile=True, persistentfolder=None, **userdefinitions)
-
Mass assignment header for pizza.region.
Use R.headersData.property = value to assign a value with R a pizza.region object.
Constructor adding instance definitions stored in USER.
Parameters
persistentfile (bool, optional): Whether to use a persistent file. Defaults to True. persistentfolder (str, optional): Folder path for persistent files. Defaults to None. **userdefinitions: Arbitrary keyword arguments for user definitions. - mass (list or tuple, optional): List or tuple to override masses for specific bead types. Example: mass=[1.2, 1.0, 0.8] assigns mass 1.2 to bead type 1, 1.0 to bead type 2, and 0.8 to bead type 3.
Expand source code
class LammpsHeaderMass(LammpsGeneric): """ Mass assignment header for pizza.region. Use R.headersData.property = value to assign a value with R a pizza.region object. """ name = "LammpsHeaderMass" SECTIONS = ["HEADER"] position = 2 # Positioned after other headers like Box and Lattice role = "mass assignment header for pizza.region" description = "Assigns masses to bead types based on nbeads and default mass." userid = "headermass" # User identifier version = 0.1 verbose = False # DEFINITIONS USED IN TEMPLATE # All fields are stored in R.headersData with R a region object. # Use R.headersData.property = value to assign a value. # Mass overrides are provided via the 'mass' keyword argument as a list or tuple. DEFINITIONS = scriptdata( nbeads="${nbeads}", # these default values are not used mass="${mass}", # but reported for records hasvariables=False ) def __init__(self, persistentfile=True, persistentfolder=None, **userdefinitions): """ Constructor adding instance definitions stored in USER. Parameters: persistentfile (bool, optional): Whether to use a persistent file. Defaults to True. persistentfolder (str, optional): Folder path for persistent files. Defaults to None. **userdefinitions: Arbitrary keyword arguments for user definitions. - mass (list or tuple, optional): List or tuple to override masses for specific bead types. Example: mass=[1.2, 1.0, 0.8] assigns mass 1.2 to bead type 1, 1.0 to bead type 2, and 0.8 to bead type 3. """ super().__init__(persistentfile, persistentfolder, **userdefinitions) self.generate_template() def generate_template(self): """ Generate the TEMPLATE for mass assignments based on USER definitions. The method constructs mass assignments for each bead type. If `mass` overrides are provided as a list or tuple, it assigns the specified mass to the corresponding bead types. Otherwise, it uses the default `mass` value from `USER.headersData.mass`. """ # Retrieve user-defined parameters nbeads = self.USER.nbeads mass = self.USER.mass # Validate mass if not isinstance(mass, (list, tuple)): mass = [mass] # Convert single value to a list if len(mass) > nbeads: mass = mass[:nbeads] # Truncate excess entries elif len(mass) < nbeads: last_mass = mass[-1] # Repeat the last value for missing entries mass += [last_mass] * (nbeads - len(mass)) # Initialize TEMPLATE with header comment self.TEMPLATE = "\n% --------------[ Mass Assignments for <${name}:${boxid}>" + f" (nbeads={nbeads}) " +" ]--------------\n" # Iterate over bead types and assign masses for bead_type in range(1, nbeads + 1): bead_mass = mass[bead_type - 1] if isinstance(bead_mass, str): # If mass is a string (e.g., formula), ensure proper formatting mass_str = f"({bead_mass})" else: # If mass is a numeric value, convert to string mass_str = f"{bead_mass}" self.TEMPLATE += f"mass {bead_type} {mass_str}\n" # Close the TEMPLATE with a comment self.TEMPLATE += "# ------------------------------------------\n"
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var description
var name
var position
var role
var userid
var verbose
var version
Methods
def generate_template(self)
-
Generate the TEMPLATE for mass assignments based on USER definitions.
The method constructs mass assignments for each bead type. If
mass
overrides are provided as a list or tuple, it assigns the specified mass to the corresponding bead types. Otherwise, it uses the defaultmass
value fromUSER.headersData.mass
.Expand source code
def generate_template(self): """ Generate the TEMPLATE for mass assignments based on USER definitions. The method constructs mass assignments for each bead type. If `mass` overrides are provided as a list or tuple, it assigns the specified mass to the corresponding bead types. Otherwise, it uses the default `mass` value from `USER.headersData.mass`. """ # Retrieve user-defined parameters nbeads = self.USER.nbeads mass = self.USER.mass # Validate mass if not isinstance(mass, (list, tuple)): mass = [mass] # Convert single value to a list if len(mass) > nbeads: mass = mass[:nbeads] # Truncate excess entries elif len(mass) < nbeads: last_mass = mass[-1] # Repeat the last value for missing entries mass += [last_mass] * (nbeads - len(mass)) # Initialize TEMPLATE with header comment self.TEMPLATE = "\n% --------------[ Mass Assignments for <${name}:${boxid}>" + f" (nbeads={nbeads}) " +" ]--------------\n" # Iterate over bead types and assign masses for bead_type in range(1, nbeads + 1): bead_mass = mass[bead_type - 1] if isinstance(bead_mass, str): # If mass is a string (e.g., formula), ensure proper formatting mass_str = f"({bead_mass})" else: # If mass is a numeric value, convert to string mass_str = f"{bead_mass}" self.TEMPLATE += f"mass {bead_type} {mass_str}\n" # Close the TEMPLATE with a comment self.TEMPLATE += "# ------------------------------------------\n"
Inherited members
class LammpsMove (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
script for LAMMPS variables section
constructor adding instance definitions stored in USER
Expand source code
class LammpsMove(LammpsGeneric): """ script for LAMMPS variables section """ name = "LammpsMove" SECTIONS = ["move_fix"] position = 6 role = "move along a trajectory" description = "fix ID group-ID move style args keyword values ..." userid = "move" version = 0.2 verbose = True # Definitions used in TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", moveID = "$"+fixmoveprefix+"${ID}", # freeze the interpretation groupID = "$"+groupprefix+"${ID}", # freeze the interpretation style = "${style}", args = "${args}", hasvariables = False ) # Template (using % instead of # enables replacements) TEMPLATE = """ # Move atoms fix ID group-ID move style args keyword values (https://docs.lammps.org/fix_move.html) % move_fix for group ${groupID} using ${style} % prefix "g" added to ${ID} to indicate a group of atoms % prefix "fm" added to ${ID} to indicate the ID of the fix move fix ${moveID} ${groupID} move ${style} ${args} """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsRegion (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
generic region based on script
constructor adding instance definitions stored in USER
Expand source code
class LammpsRegion(LammpsGeneric): """ generic region based on script """ name = "LammpsRegion" SECTIONS = ["REGION"] position = 3 role = "region command definition" description = "region ID style args keyword arg" userid = "region" # user name version = 0.1 # version verbose = True # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", style = "${style}", args = "${args}", side = "${side}", units = "${units}", move = "${move}", rotate = "${rotate}", open = "${open}", hasvariables = False ) # Template (using % instead of # enables replacements) TEMPLATE = """ % Create region ${ID} ${style} args ... (URL: https://docs.lammps.org/region.html) # keywords: side, units, move, rotate, open # values: in|out, lattice|box, v_x v_y v_z, v_theta Px Py Pz Rx Ry Rz, integer region ${ID} ${style} ${args} ${side}${units}${move}${rotate}${open} """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsSetGroup (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
script for LAMMPS set group section
constructor adding instance definitions stored in USER
Expand source code
class LammpsSetGroup(LammpsGeneric): """ script for LAMMPS set group section """ name = "LammpsSetGroup" SECTIONS = ["set group"] position = 4 role = "create_atoms command" description = "set group groupID type beadtype" userid = "setgroup" version = 0.1 verbose = True # Definitions used in TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", groupID = "$"+groupprefix+"${ID}", # freeze the interpretation, hasvariables = False ) # Template (using % instead of # enables replacements) TEMPLATE = """ % Reassign atom type to ${beadtype} for the group ${groupID} associated with region ${ID} (https://docs.lammps.org/set.html) set group ${groupID} type ${beadtype} """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsSpacefilling (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
Spacefilling script: fill space with a block
constructor adding instance definitions stored in USER
Expand source code
class LammpsSpacefilling(LammpsGeneric): """ Spacefilling script: fill space with a block """ name = "LammpsSpacefilling" SECTIONS = ["SPACEFILLING"] position = 1 role = "fill space with fillingbeadtype atoms" description = 'fill the whole space (region "filledspace") with default atoms (beadtype)' userid = "spacefilling" # user name version = 0.1 # version verbose = False # DEFINITIONS USED IN TEMPLATE DEFINITIONS = scriptdata( fillingunits = "${fillingunits}", fillingwidth = "${fillingwidth}", fillingheight = "${fillingheight}", fillingdepth = "${fillingdepth}", fillingxlo = "-${fillingwidth}/2", fillingxhi = "${fillingwidth}/2", fillingylo = "-${fillingheight}/2", fillingyhi = "${fillingheight}/2", fillingzlo = "-${fillingdepth}/2", fillingzhi = "${fillingdepth}/2", fillingbeadtype = "${fillingbeadtype}", fillingstyle = "${block}", hasvariables = False ) # Template TEMPLATE = """ region filledspace ${fillingstyle} ${fillingxlo} ${fillingxhi} ${fillingylo} ${fillingyhi} ${fillingzlo} ${fillingzhi} create_atoms ${fillingbeadtype} region filledspace # ------------------------------------------ """
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class LammpsVariables (VARIABLES=region data (RD object) with 0 definitions, **userdefinitions)
-
script for LAMMPS variables section myvars = LammpsVariables(regiondata(var1=…),ID='....',style='....')
constructor of LammpsVariables
Expand source code
class LammpsVariables(LammpsGeneric): """ script for LAMMPS variables section myvars = LammpsVariables(regiondata(var1=...),ID='....',style='....') """ name = "LammpsVariables" SECTIONS = ["VARIABLES"] position = 2 role = "variable command definition" description = "variable name style args" userid = "variable" version = 0.1 verbose = True # Definitions used in TEMPLATE DEFINITIONS = scriptdata( ID = "${ID}", style = "${style}", hasvariables = True ) # Template (using % instead of # enables replacements) TEMPLATE = "% variables to be used for ${ID} ${style}" def __init__(self,VARIABLES=regiondata(),**userdefinitions): """ constructor of LammpsVariables """ super().__init__(**userdefinitions) self.VARIABLES = VARIABLES # override >> def __rshift__(self,s): """ overload right shift operator (keep only the last template) """ if isinstance(s,script): dup = deepduplicate(self) # instead of duplicate (added 2023-08-11) dup.DEFINITIONS = dup.DEFINITIONS + s.DEFINITIONS dup.USER = dup.USER + s.USER if self.DEFINITIONS.hasvariables and s.DEFINITIONS.hasvariables: dup.VARIABLES = s.VARIABLES dup.TEMPLATE = s.TEMPLATE return dup else: raise TypeError("the second operand must a script object")
Ancestors
- LammpsGeneric
- pizza.script.script
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var name
var position
var role
var userid
var verbose
var version
Inherited members
class Plane (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Plane class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Plane(coregeometry): """ Plane class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "plane%03d" % counter[1] self.kind = "plane" # kind of object self.alike = "plane" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$plane"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, style=style, group=group, forcefield=forcefield # script object properties ) @property def volume(self,units=None): """Dummy method returning None for volume""" #args = [px, py, pz, nx, ny, nz] return None
Ancestors
Instance variables
var volume
-
Dummy method returning None for volume
Expand source code
@property def volume(self,units=None): """Dummy method returning None for volume""" #args = [px, py, pz, nx, ny, nz] return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Prism (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Prism class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Prism(coregeometry): """ Prism class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "prism%03d" % counter[1] self.kind = "prism" # kind of object self.alike = "prism" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$prism"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): """Calculate the volume of the prism based on USER.args""" #args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args xlo = float(args[0]) xhi = float(args[1]) ylo = float(args[2]) yhi = float(args[3]) zlo = float(args[4]) zhi = float(args[5]) # Calculate the dimensions of the prism length = xhi - xlo width = yhi - ylo height = zhi - zlo # Calculate the volume of the prism volume = length * width * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the prism based on USER.args
Expand source code
def volume(self,units=None): """Calculate the volume of the prism based on USER.args""" #args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args xlo = float(args[0]) xhi = float(args[1]) ylo = float(args[2]) yhi = float(args[3]) zlo = float(args[4]) zhi = float(args[5]) # Calculate the dimensions of the prism length = xhi - xlo width = yhi - ylo height = zhi - zlo # Calculate the volume of the prism volume = length * width * height return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Sphere (counter, index=None, subindex=None, mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1, hasgroup=False, hasmove=False, spacefilling=False, style=None, group=None, forcefield=None, **variables)
-
Sphere class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Sphere(coregeometry): """ Sphere class """ def __init__(self,counter,index=None,subindex=None, mass=1, density=1, lattice_style="sc",lattice_scale=1,lattice_scale_siunits=1, hasgroup=False,hasmove=False,spacefilling=False, style=None, group=None, forcefield=None, **variables): self.name = "sphere%03d" % counter[1] self.kind = "sphere" # kind of object self.alike = "ellipsoid" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex self.mass = mass self.density = density # call the generic constructor super().__init__( USER = regiondata(style="$sphere"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling, mass=mass, density=density, lattice_style=lattice_style, lattice_scale=lattice_scale, lattice_scale_siunits=lattice_scale_siunits, style=style, group=group, forcefield=forcefield # script object properties ) def volume(self,units=None): """Calculate the volume of the sphere based on USER.args""" #args = [x, y, z, radius] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius = float(args[3]) # Calculate the volume of the sphere volume = (4/3) * 3.141592653589793 * (radius ** 3) return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Ancestors
Methods
def volume(self, units=None)
-
Calculate the volume of the sphere based on USER.args
Expand source code
def volume(self,units=None): """Calculate the volume of the sphere based on USER.args""" #args = [x, y, z, radius] try: # Extract the arguments from USER.args args = self.USER.args_siunits if units=="si" else self.USER.args radius = float(args[3]) # Calculate the volume of the sphere volume = (4/3) * 3.141592653589793 * (radius ** 3) return volume except Exception as e: print(f"Error calculating volume: {e}") return None
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class Union (counter, index=None, subindex=None, hasgroup=False, hasmove=False, spacefilling=False, **variables)
-
Union class
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class Union(coregeometry): """ Union class """ def __init__(self,counter,index=None,subindex=None, hasgroup=False,hasmove=False,spacefilling=False,**variables): self.name = "union%03d" % counter[1] self.kind = "union" # kind of object self.alike = "operator" # similar object for plotting self.beadtype = 1 # bead type self.index = counter[0] if index is None else index self.subindex = subindex # call the generic constructor super().__init__( USER = regiondata(style="$union"), VARIABLES = regiondata(**variables), hasgroup=hasgroup,hasmove=hasmove,spacefilling=spacefilling )
Ancestors
Inherited members
coregeometry
:VARIABLES
copy
creategroup
createmove
do
fixmoveargs
fixmoveargvalidator
flags
geometry
get_fixmovesyntax
hascreate
hasgroup
hasmove
hasregion
hassetgroup
hasvariables
isspacefilled
movearg
natoms
openarg
removegroup
removemove
rotatearg
script
scriptobject
setgroup
shortflags
sidearg
unitsarg
update
class coregeometry (USER=region data (RD object) with 0 definitions, VARIABLES=region data (RD object) with 0 definitions, hasgroup=False, hasmove=False, spacefilling=False, style='smd', forcefield=LAMMPS:SMD:none:walls, group=[], mass=1, density=1, lattice_style='sc', lattice_scale=1, lattice_scale_siunits=1)
-
core geometry object (helper class for attributes, side,units, move, rotate, open)
SECTIONS store scripts (variables, region and create for the geometry) USER = common USER definitions for the three scripts VARIABLES = variables definitions (used by variables only) update() propagate USER to the three scripts script returns SECTIONS as a pipescript do() generate the script
Parameters to be used along scriptobject() style forcefield group They are stored SCRIPTOBJECT_USER
constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script
The flag spacefilling is true of the container of objects (class region) is filled with beads
Expand source code
class coregeometry: """ core geometry object (helper class for attributes, side,units, move, rotate, open) SECTIONS store scripts (variables, region and create for the geometry) USER = common USER definitions for the three scripts VARIABLES = variables definitions (used by variables only) update() propagate USER to the three scripts script returns SECTIONS as a pipescript do() generate the script Parameters to be used along scriptobject() style forcefield group They are stored SCRIPTOBJECT_USER """ _version = "0.35" __custom_documentations__ = "pizza.region.coregeometry class" def __init__(self,USER=regiondata(),VARIABLES=regiondata(), hasgroup=False, hasmove=False, spacefilling=False, style="smd", forcefield=rigidwall(), group=[], mass=1, density=1, lattice_style="sc", lattice_scale=1, lattice_scale_siunits=1 # added on 2024-07-05 ): """ constructor of the generic core geometry USER: any definitions requires by the geometry VARIABLES: variables used to define the geometry (to be used in LAMMPS) hasgroup, hasmove: flag to force the sections group and move SECTIONS: they must be PIZZA.script The flag spacefilling is true of the container of objects (class region) is filled with beads """ self.USER = USER self.SECTIONS = { 'variables': LammpsVariables(VARIABLES,**USER), 'region': LammpsRegion(**USER), 'create': LammpsCreate(**USER), 'group': LammpsGroup(**USER), 'setgroup': LammpsSetGroup(**USER), 'move': LammpsMove(**USER) } self.FLAGSECTIONS = { 'variables': True, 'region': True, 'create': not spacefilling, 'group': hasgroup, 'setgroup': spacefilling, 'move': hasmove } self.spacefilling = spacefilling # add comptaibility with scriptobjects self.SCRIPTOBJECT_USER = { 'style': style, 'forcefield': forcefield, 'group': group } # collect information from parent region self.mass = mass self.density = density self.lattice_style = lattice_style self.lattice_scale = lattice_scale self.lattice_scale_siunits = lattice_scale_siunits def update(self): """ update the USER content for all three scripts """ if isinstance(self.SECTIONS["variables"],script): self.SECTIONS["variables"].USER += self.USER if isinstance(self.SECTIONS["region"],script): self.SECTIONS["region"].USER += self.USER if isinstance(self.SECTIONS["create"],script): self.SECTIONS["create"].USER += self.USER if isinstance(self.SECTIONS["group"],script): self.SECTIONS["group"].USER += self.USER if isinstance(self.SECTIONS["setgroup"],script): self.SECTIONS["setgroup"].USER += self.USER if isinstance(self.SECTIONS["move"],script): self.SECTIONS["move"].USER += self.USER def copy(self,beadtype=None,name=""): """ returns a copy of the graphical object """ if self.alike != "mixed": dup = deepduplicate(self) if beadtype != None: # update beadtype dup.beadtype = beadtype if name != "": # update name dup.name = name return dup else: raise ValueError("collections cannot be copied, regenerate the collection instead") def creategroup(self): """ force the group creation in script """ self.FLAGSECTIONS["group"] = True def setgroup(self): """ force the group creation in script """ self.FLAGSECTIONS["setgroup"] = True def createmove(self): """ force the fix move creation in script """ self.FLAGSECTIONS["move"] = True def removegroup(self): """ force the group creation in script """ self.FLAGSECTIONS["group"] = False def removemove(self): """ force the fix move creation in script """ self.FLAGSECTIONS["move"] = False def scriptobject(self, beadtype=None, name=None, fullname=None, group=None, style=None, forcefield=None, USER = scriptdata()): """ Method to return a scriptobject based on region instead of an input file Syntax similar to script.scriptobject OBJ = scriptobject(...) Implemented properties: beadtype=1,2,... name="short name" fullname = "comprehensive name" style = "smd" forcefield = any valid forcefield instance (default = rigidwall()) """ # Set defaults using instance attributes if parameters are None if beadtype is None: beadtype = self.beadtype if name is None: name = f"{self.name} bead" if fullname is None: fullname = f"beads of type {self.beadtype} | object {self.name} of kind region.{self.kind}" if group is None: group = self.SCRIPTOBJECT_USER["group"] if style is None: style = self.SCRIPTOBJECT_USER["style"] if forcefield is None: style = self.SCRIPTOBJECT_USER["forcefield"] return scriptobject( beadtype=beadtype, name=name, fullname=fullname, style=style, group=group, filename=None, # No need for a file USER = USER ) @property def hasvariables(self): """ return the flag VARIABLES """ return isinstance(self.SECTIONS["variables"],script) \ and self.FLAGSECTIONS["variables"] @property def hasregion(self): """ return the flag REGION """ return isinstance(self.SECTIONS["region"],script) \ and self.FLAGSECTIONS["region"] @property def hascreate(self): """ return the flag CREATE """ return isinstance(self.SECTIONS["create"],script) \ and self.FLAGSECTIONS["create"] \ and (not self.spacefilling) @property def hasgroup(self): """ return the flag GROUP """ return isinstance(self.SECTIONS["group"],script) \ and self.FLAGSECTIONS["group"] @property def hassetgroup(self): """ return the flag GROUP """ return isinstance(self.SECTIONS["setgroup"],script) \ and self.FLAGSECTIONS["setgroup"] \ and self.hasgroup \ and (not self.hascreate) @property def hasmove(self): """ return the flag MOVE """ return isinstance(self.SECTIONS["move"],script) \ and self.FLAGSECTIONS["move"] @property def isspacefilled(self): """ return the flag spacefilling """ return isinstance(self.SECTIONS["spacefilling"],script) \ and self.FLAGSECTIONS["spacefilling"] @property def flags(self): """ return a list of all flags that are currently set """ flag_names = list(self.SECTIONS.keys()) return [flag for flag in flag_names if getattr(self, f"has{flag}")] @property def shortflags(self): """ return a string made from the first letter of each set flag """ return "".join([flag[0] for flag in self.flags]) @property def VARIABLES(self): """ return variables """ if isinstance(self.SECTIONS["variables"],script): return self.SECTIONS["variables"].VARIABLES else: v = regiondata() for i in range(len(self.SECTIONS["variables"].scripts)): v = v + self.SECTIONS["variables"].scripts[i].VARIABLES return v @property def script(self): """ generates a pipe script from sections """ self.update() ptmp = self.SECTIONS["variables"] if self.hasvariables else None if self.hasregion: ptmp = self.SECTIONS["region"] if ptmp is None else ptmp | self.SECTIONS["region"] if self.hascreate: ptmp = self.SECTIONS["create"] if ptmp is None else ptmp | self.SECTIONS["create"] if self.hasgroup: ptmp = self.SECTIONS["group"] if ptmp is None else ptmp | self.SECTIONS["group"] if self.hassetgroup: ptmp = self.SECTIONS["setgroup"] if ptmp is None else ptmp | self.SECTIONS["setgroup"] if self.hasmove: ptmp = self.SECTIONS["move"] if ptmp is None else ptmp | self.SECTIONS["move"] return ptmp # before 2023-07-17 #return self.SECTIONS["variables"] | self.SECTIONS["region"] | self.SECTIONS["create"] def do(self,printflag=False,verbosity=1): """ generates a script """ p = self.script # intentional, force script before do(), comment added on 2023-07-17 cmd = p.do(printflag=printflag,verbosity=verbosity) # if printflag: print(cmd) return cmd def __repr__(self): """ display method""" nVAR = len(self.VARIABLES) print("%s - %s object - beadtype=%d " % (self.name, self.kind,self.beadtype)) if hasattr(self,"filename"): print(f'\tfilename: "{self.filename}"') if nVAR>0: print(f"\t<-- {nVAR} variables are defined -->") print(f"\tUse {self.name}.VARIABLES to see details and their evaluation") for k,v in self.VARIABLES.items(): v0 = '"'+v+'"' if isinstance(v,str) else repr(v) print(wrap(k,"=",v0,20,40,80)) print("\t<-- keyword arg -->") haskeys = False for k in ("side","move","units","rotate","open"): if k in self.USER: v = self.USER.getattr(k) if v != "": print(wrap(k,":",v[1:],20,60,80)) haskeys = True if not haskeys: print(wrap("no keywords","<","from side|move|units|rotate|open",20,60,80)) flags = self.flags if flags: print(f'defined scripts: {span(flags,sep=",")}',"\n") print("\n"+self.geometry) # added 2024-07-05 return "%s object: %s (beadtype=%d)" % (self.kind,self.name,self.beadtype) # ~~~~ validator for region arguments (the implementation is specific and not generic as fix move ones) def sidearg(self,side): """ Validation of side arguments for region command (https://docs.lammps.org/region.html) side value = in or out in = the region is inside the specified geometry out = the region is outside the specified geometry """ prefix = "$" if side is None: return "" elif isinstance(side, str): side = side.lower() if side in ("in","out"): return f"{prefix} side {side}" elif side in ("","none"): return "" else: raise ValueError(f'the value of side: "{side}" is not recognized') else: raise ValueError('the parameter side can be "in|out|None"') def movearg(self,move): """ Validation of move arguments for region command (https://docs.lammps.org/region.html) move args = v_x v_y v_z v_x,v_y,v_z = equal-style variables for x,y,z displacement of region over time (distance units) """ prefix = "$" if move is None: return "" elif isinstance(move, str): move = move.lower() if move in("","none"): return "" else: return f"{prefix} move {move}" elif isinstance(move,(list,tuple)): if len(move)<3: print("NULL will be added to move") elif len(move)>3: print("move will be truncated to 3 elements") movevalid = ["NULL","NULL","NULL"] for i in range(min(3,len(move))): if isinstance(move[i],str): if move[i].upper()!="NULL": if prefix in move[i]: # we assume a numeric result after evaluation # Pizza variables will be evaluated # formateval for the evaluation of ${} # eval for residual expressions movevalid[i] = round(eval(self.VARIABLES.formateval(move[i])),6) else: # we assume a variable (LAMMPS variable, not Pizza ones) movevalid[i] = "v_" + move[i] elif not isinstance(move[i],(int,float)): if (move[i] is not None): raise TypeError("move values should be str, int or float") return f"{prefix} move {span(movevalid)}" else: raise TypeError("the parameter move should be a list or tuple") def unitsarg(self,units): """ Validation for units arguments for region command (https://docs.lammps.org/region.html) units value = lattice or box lattice = the geometry is defined in lattice units box = the geometry is defined in simulation box units """ prefix = "$" if units is None: return "" elif isinstance(units,str): units = units.lower() if units in ("lattice","box"): return f"{prefix} units {units}" elif (units=="") or (units=="none"): return "" else: raise ValueError(f'the value of side: "{units}" is not recognized') else: raise TypeError('the parameter units can be "lattice|box|None"') def rotatearg(self,rotate): """ Validation of rotate arguments for region command (https://docs.lammps.org/region.html) rotate args = v_theta Px Py Pz Rx Ry Rz v_theta = equal-style variable for rotaton of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector """ prefix = "$" if rotate is None: return "" elif isinstance(rotate, str): rotate = rotate.lower() if rotate in ("","none",None): return "" else: return f"{prefix} rotate {rotate}" elif isinstance(rotate,(list,tuple)): if len(rotate)<7: print("NULL will be added to rotate") elif len(rotate)>7: print("rotate will be truncated to 7 elements") rotatevalid = ["NULL"]*7 for i in range(min(7,len(rotate))): if isinstance(rotate[i],str): if rotate[i].upper()!="NULL": if prefix in rotate[i]: rotatevalid[i] = round(eval(self.VARIABLES.formateval(rotate[i])),6) else: rotatevalid[i] = rotate[i] elif not isinstance(rotate[i],(int,float)): if (rotate[i] is not None): raise TypeError("rotate values should be str, int or float") return f"{prefix} move {span(rotatevalid)}" else: raise TypeError("the parameter rotate should be a list or tuple") def openarg(self,open): """ Validation of open arguments for region command (https://docs.lammps.org/region.html) open value = integer from 1-6 corresponding to face index (see below) The indices specified as part of the open keyword have the following meanings: For style block, indices 1-6 correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces of the block. I.e. 1 is the yz plane at x = xlo, 2 is the yz-plane at x = xhi, 3 is the xz plane at y = ylo, 4 is the xz plane at y = yhi, 5 is the xy plane at z = zlo, 6 is the xy plane at z = zhi). In the second-to-last example above, the region is a box open at both xy planes. For style prism, values 1-6 have the same mapping as for style block. I.e. in an untilted prism, open indices correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces. For style cylinder, index 1 corresponds to the flat end cap at the low coordinate along the cylinder axis, index 2 corresponds to the high-coordinate flat end cap along the cylinder axis, and index 3 is the curved cylinder surface. For example, a cylinder region with open 1 open 2 keywords will be open at both ends (e.g. a section of pipe), regardless of the cylinder orientation. """ prefix = "$" if open in ("","none",None): return "" elif isinstance(open, str): raise TypeError(" the parameter open should be an integer or a list/tuple of integers from 1-6") elif isinstance(open, int): if open in range(1,7): return f"{prefix} open {open}" else: raise TypeError(" open value should be integer from 1-6") elif isinstance(open, (list,tuple)): openvalid = [f"{prefix} open {i}" for i in range(1,7) if i in open] return f"$ {span(openvalid)}" # ~~~~ end validator for region arguments # ~~~~ validator for fix move arguments (implemented generically on 2023-07-17) def fixmoveargvalidator(self, argtype, arg, arglen): """ Validation of arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html) LAMMPS syntax: fix ID group-ID move style args - linear args = Vx Vy Vz - wiggle args = Ax Ay Az period - rotate args = Px Py Pz Rx Ry Rz period - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period - variable args = v_dx v_dy v_dz v_vx v_vy v_vz Args: argtype: Type of the argument (linear, wiggle, rotate, transrot, variable) arg: The argument to validate arglen: Expected length of the argument """ prefix = "$" if arg in ("","none",None): return "" elif isinstance(arg,(list,tuple)): if len(arg) < arglen: print(f"NULL will be added to {argtype}") elif len(arg) > arglen: print(f"{argtype} will be truncated to {arglen} elements") argvalid = ["NULL"]*arglen for i in range(min(arglen,len(arg))): if isinstance(arg[i],str): if arg[i].upper()!="NULL": if prefix in arg[i]: argvalid[i] = round(eval(self.VARIABLES.formateval(arg[i])),6) else: argvalid[i] = arg[i] elif not isinstance(arg[i],(int,float)): if (arg[i] is not None): raise TypeError(f"{argtype} values should be str, int or float") return f"{prefix} move {span(argvalid)}" else: raise TypeError(f"the parameter {argtype} should be a list or tuple") def fixmoveargs(self, linear=None, wiggle=None, rotate=None, transrot=None, variable=None): """ Validates all arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html) the result is adictionary, all fixmove can be combined """ argsdict = { "linear": [linear, 3], "wiggle": [wiggle, 4], "rotate": [rotate, 7], "transrot": [transrot, 10], "variable": [variable, 6] } for argtype, arginfo in argsdict.items(): arg, arglen = arginfo if arg is not None: argsdict[argtype] = self.fixmoveargvalidator(argtype, arg, arglen) return argsdict def get_fixmovesyntax(self, argtype=None): """ Returns the syntax for LAMMPS command, or detailed explanation for a specific argument type Args: argtype: Optional; Type of the argument (linear, wiggle, rotate, transrot, variable) """ syntax = { "linear": "linear args = Vx Vy Vz\n" "Vx,Vy,Vz = components of velocity vector (velocity units), any component can be specified as NULL", "wiggle": "wiggle args = Ax Ay Az period\n" "Ax,Ay,Az = components of amplitude vector (distance units), any component can be specified as NULL\n" "period = period of oscillation (time units)", "rotate": "rotate args = Px Py Pz Rx Ry Rz period\n" "Px,Py,Pz = origin point of axis of rotation (distance units)\n" "Rx,Ry,Rz = axis of rotation vector\n" "period = period of rotation (time units)", "transrot": "transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n" "Vx,Vy,Vz = components of velocity vector (velocity units)\n" "Px,Py,Pz = origin point of axis of rotation (distance units)\n" "Rx,Ry,Rz = axis of rotation vector\n" "period = period of rotation (time units)", "variable": "variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n" "v_dx,v_dy,v_dz = 3 variable names that calculate x,y,z displacement as function of time, any component can be specified as NULL\n" "v_vx,v_vy,v_vz = 3 variable names that calculate x,y,z velocity as function of time, any component can be specified as NULL", } base_syntax = ( "fix ID group-ID move style args\n" " - linear args = Vx Vy Vz\n" " - wiggle args = Ax Ay Az period\n" " - rotate args = Px Py Pz Rx Ry Rz period\n" " - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n" " - variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n\n" 'use get_movesyntax("movemethod") for details' "manual: https://docs.lammps.org/fix_move.html" ) return syntax.get(argtype, base_syntax) # ~~~~ end validator for fix move arguments def __add__(self,C): """ overload addition ("+") operator """ if isinstance(C,coregeometry): dup = deepduplicate(self) dup.name = cleanname(self.name) +"+"+ cleanname(C.name) dup.USER = dup.USER + C.USER dup.USER.ID = "$" + cleanname(self.USER.ID) +"+"+ cleanname(C.USER.ID) dup.SECTIONS["variables"] = dup.SECTIONS["variables"] + C.SECTIONS["variables"] dup.SECTIONS["region"] = dup.SECTIONS["region"] + C.SECTIONS["region"] dup.SECTIONS["create"] = dup.SECTIONS["create"] + C.SECTIONS["create"] dup.SECTIONS["group"] = dup.SECTIONS["group"] + C.SECTIONS["group"] dup.SECTIONS["move"] = dup.SECTIONS["move"] + C.SECTIONS["move"] dup.FLAGSECTIONS["variables"] = dup.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"] dup.FLAGSECTIONS["region"] = dup.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"] dup.FLAGSECTIONS["create"] = dup.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"] dup.FLAGSECTIONS["group"] = dup.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"] dup.FLAGSECTIONS["move"] = dup.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"] return dup raise TypeError("the second operand must a region.coregeometry object") def __iadd__(self,C): """ overload iaddition ("+=") operator """ if isinstance(C,coregeometry): self.USER += C.USER self.SECTIONS["variables"] += C.SECTIONS["variables"] self.SECTIONS["region"] += C.SECTIONS["region"] self.SECTIONS["create"] += C.SECTIONS["create"] self.SECTIONS["group"] += C.SECTIONS["group"] self.SECTIONS["move"] += C.SECTIONS["move"] self.FLAGSECTIONS["variables"] = self.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"] self.FLAGSECTIONS["region"] = self.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"] self.FLAGSECTIONS["create"] = self.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"] self.FLAGSECTIONS["group"] = self.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"] self.FLAGSECTIONS["move"] = self.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"] return self raise TypeError("the operand must a region.coregeometry object") def __or__(self,C): """ overload | pipe """ if isinstance(C,coregeometry): dup = deepduplicate(self) dup.name = cleanname(self.name) +"|"+ cleanname(C.name) dup.USER = dup.USER + C.USER dup.USER.ID = "$" + cleanname(self.USER.ID) +"|"+ cleanname(C.USER.ID) dup.SECTIONS["variables"] = dup.SECTIONS["variables"] | C.SECTIONS["variables"] dup.SECTIONS["region"] = dup.SECTIONS["region"] | C.SECTIONS["region"] dup.SECTIONS["create"] = dup.SECTIONS["create"] | C.SECTIONS["create"] dup.SECTIONS["group"] = dup.SECTIONS["group"] | C.SECTIONS["group"] dup.SECTIONS["move"] = dup.SECTIONS["move"] | C.SECTIONS["move"] self.FLAGSECTIONS["variables"] = self.FLAGSECTIONS["variables"] or C.FLAGSECTIONS["variables"] self.FLAGSECTIONS["region"] = self.FLAGSECTIONS["region"] or C.FLAGSECTIONS["region"] self.FLAGSECTIONS["create"] = self.FLAGSECTIONS["create"] or C.FLAGSECTIONS["create"] self.FLAGSECTIONS["group"] = self.FLAGSECTIONS["group"] or C.FLAGSECTIONS["group"] self.FLAGSECTIONS["move"] = self.FLAGSECTIONS["move"] or C.FLAGSECTIONS["move"] return dup raise TypeError("the second operand must a region.coregeometry object") # copy and deep copy methods for the class (required) 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 __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, deepduplicate(v, memo)) # replace duplicatedeep by deepduplicate (OV: 2023-07-28) return copie # Return the number of atoms @property def natoms(self): """Calculate the number of beads based on density, mass, and volume""" if hasattr(self, 'volume'): try: volume_siunits = self.volume("si") voxel_volume_siunits = self.lattice_scale**3 number_of_beads = volume_siunits / voxel_volume_siunits packing_factors = { 'sc': 1.0, 'fcc': 4.0, 'bcc': 2.0, 'hcp': 6.0, # Approximate value, requires specific volume calculation for accuracy 'dia': 8.0, 'bco': 2.0, # Assuming orthorhombic lattice similar to bcc 'fco': 4.0, # Assuming orthorhombic lattice similar to fcc } packing_factor = packing_factors.get(self.lattice_style, 1.0) # Default to simple cubic if unknown number_of_beads *= packing_factor return round(number_of_beads) except Exception as e: print(f"Error calculating number of beads: {e}") return None else: print("Volume attribute is missing.") return None # return parent region details @property def regiondetails(self): return "\n".join(( f"\n--- | Region Details | ---", f"Name: {self.name}", f"Lattice Style: {self.lattice_style}", f"Lattice Scale: {self.lattice_scale}", f"Lattice Scale (SI units): {self.lattice_scale_siunits}", f"Volume: {self.volume()}", f"Volume (SI units): {self.volume('si')}", f"Number of Atoms: {self.natoms}","\n" )) # return geometry details (2024-07-04) @property def geometry(self): """Return the geometry details of the object.""" details = self.regiondetails details += "\n--- | Geometry Details | ---\n" if hasattr(self.USER, 'geometry'): details += self.USER.geometry else: details = "No geometry available.\n" return details
Subclasses
Instance variables
var VARIABLES
-
return variables
Expand source code
@property def VARIABLES(self): """ return variables """ if isinstance(self.SECTIONS["variables"],script): return self.SECTIONS["variables"].VARIABLES else: v = regiondata() for i in range(len(self.SECTIONS["variables"].scripts)): v = v + self.SECTIONS["variables"].scripts[i].VARIABLES return v
var flags
-
return a list of all flags that are currently set
Expand source code
@property def flags(self): """ return a list of all flags that are currently set """ flag_names = list(self.SECTIONS.keys()) return [flag for flag in flag_names if getattr(self, f"has{flag}")]
var geometry
-
Return the geometry details of the object.
Expand source code
@property def geometry(self): """Return the geometry details of the object.""" details = self.regiondetails details += "\n--- | Geometry Details | ---\n" if hasattr(self.USER, 'geometry'): details += self.USER.geometry else: details = "No geometry available.\n" return details
var hascreate
-
return the flag CREATE
Expand source code
@property def hascreate(self): """ return the flag CREATE """ return isinstance(self.SECTIONS["create"],script) \ and self.FLAGSECTIONS["create"] \ and (not self.spacefilling)
var hasgroup
-
return the flag GROUP
Expand source code
@property def hasgroup(self): """ return the flag GROUP """ return isinstance(self.SECTIONS["group"],script) \ and self.FLAGSECTIONS["group"]
var hasmove
-
return the flag MOVE
Expand source code
@property def hasmove(self): """ return the flag MOVE """ return isinstance(self.SECTIONS["move"],script) \ and self.FLAGSECTIONS["move"]
var hasregion
-
return the flag REGION
Expand source code
@property def hasregion(self): """ return the flag REGION """ return isinstance(self.SECTIONS["region"],script) \ and self.FLAGSECTIONS["region"]
var hassetgroup
-
return the flag GROUP
Expand source code
@property def hassetgroup(self): """ return the flag GROUP """ return isinstance(self.SECTIONS["setgroup"],script) \ and self.FLAGSECTIONS["setgroup"] \ and self.hasgroup \ and (not self.hascreate)
var hasvariables
-
return the flag VARIABLES
Expand source code
@property def hasvariables(self): """ return the flag VARIABLES """ return isinstance(self.SECTIONS["variables"],script) \ and self.FLAGSECTIONS["variables"]
var isspacefilled
-
return the flag spacefilling
Expand source code
@property def isspacefilled(self): """ return the flag spacefilling """ return isinstance(self.SECTIONS["spacefilling"],script) \ and self.FLAGSECTIONS["spacefilling"]
var natoms
-
Calculate the number of beads based on density, mass, and volume
Expand source code
@property def natoms(self): """Calculate the number of beads based on density, mass, and volume""" if hasattr(self, 'volume'): try: volume_siunits = self.volume("si") voxel_volume_siunits = self.lattice_scale**3 number_of_beads = volume_siunits / voxel_volume_siunits packing_factors = { 'sc': 1.0, 'fcc': 4.0, 'bcc': 2.0, 'hcp': 6.0, # Approximate value, requires specific volume calculation for accuracy 'dia': 8.0, 'bco': 2.0, # Assuming orthorhombic lattice similar to bcc 'fco': 4.0, # Assuming orthorhombic lattice similar to fcc } packing_factor = packing_factors.get(self.lattice_style, 1.0) # Default to simple cubic if unknown number_of_beads *= packing_factor return round(number_of_beads) except Exception as e: print(f"Error calculating number of beads: {e}") return None else: print("Volume attribute is missing.") return None
var regiondetails
-
Expand source code
@property def regiondetails(self): return "\n".join(( f"\n--- | Region Details | ---", f"Name: {self.name}", f"Lattice Style: {self.lattice_style}", f"Lattice Scale: {self.lattice_scale}", f"Lattice Scale (SI units): {self.lattice_scale_siunits}", f"Volume: {self.volume()}", f"Volume (SI units): {self.volume('si')}", f"Number of Atoms: {self.natoms}","\n" ))
var script
-
generates a pipe script from sections
Expand source code
@property def script(self): """ generates a pipe script from sections """ self.update() ptmp = self.SECTIONS["variables"] if self.hasvariables else None if self.hasregion: ptmp = self.SECTIONS["region"] if ptmp is None else ptmp | self.SECTIONS["region"] if self.hascreate: ptmp = self.SECTIONS["create"] if ptmp is None else ptmp | self.SECTIONS["create"] if self.hasgroup: ptmp = self.SECTIONS["group"] if ptmp is None else ptmp | self.SECTIONS["group"] if self.hassetgroup: ptmp = self.SECTIONS["setgroup"] if ptmp is None else ptmp | self.SECTIONS["setgroup"] if self.hasmove: ptmp = self.SECTIONS["move"] if ptmp is None else ptmp | self.SECTIONS["move"] return ptmp # before 2023-07-17 #return self.SECTIONS["variables"] | self.SECTIONS["region"] | self.SECTIONS["create"]
var shortflags
-
return a string made from the first letter of each set flag
Expand source code
@property def shortflags(self): """ return a string made from the first letter of each set flag """ return "".join([flag[0] for flag in self.flags])
Methods
def copy(self, beadtype=None, name='')
-
returns a copy of the graphical object
Expand source code
def copy(self,beadtype=None,name=""): """ returns a copy of the graphical object """ if self.alike != "mixed": dup = deepduplicate(self) if beadtype != None: # update beadtype dup.beadtype = beadtype if name != "": # update name dup.name = name return dup else: raise ValueError("collections cannot be copied, regenerate the collection instead")
def creategroup(self)
-
force the group creation in script
Expand source code
def creategroup(self): """ force the group creation in script """ self.FLAGSECTIONS["group"] = True
def createmove(self)
-
force the fix move creation in script
Expand source code
def createmove(self): """ force the fix move creation in script """ self.FLAGSECTIONS["move"] = True
def do(self, printflag=False, verbosity=1)
-
generates a script
Expand source code
def do(self,printflag=False,verbosity=1): """ generates a script """ p = self.script # intentional, force script before do(), comment added on 2023-07-17 cmd = p.do(printflag=printflag,verbosity=verbosity) # if printflag: print(cmd) return cmd
def fixmoveargs(self, linear=None, wiggle=None, rotate=None, transrot=None, variable=None)
-
Validates all arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html) the result is adictionary, all fixmove can be combined
Expand source code
def fixmoveargs(self, linear=None, wiggle=None, rotate=None, transrot=None, variable=None): """ Validates all arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html) the result is adictionary, all fixmove can be combined """ argsdict = { "linear": [linear, 3], "wiggle": [wiggle, 4], "rotate": [rotate, 7], "transrot": [transrot, 10], "variable": [variable, 6] } for argtype, arginfo in argsdict.items(): arg, arglen = arginfo if arg is not None: argsdict[argtype] = self.fixmoveargvalidator(argtype, arg, arglen) return argsdict
def fixmoveargvalidator(self, argtype, arg, arglen)
-
Validation of arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html)
LAMMPS syntax: fix ID group-ID move style args - linear args = Vx Vy Vz - wiggle args = Ax Ay Az period - rotate args = Px Py Pz Rx Ry Rz period - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period - variable args = v_dx v_dy v_dz v_vx v_vy v_vz
Args
argtype
- Type of the argument (linear, wiggle, rotate, transrot, variable)
arg
- The argument to validate
arglen
- Expected length of the argument
Expand source code
def fixmoveargvalidator(self, argtype, arg, arglen): """ Validation of arguments for fix move command in LAMMPS (https://docs.lammps.org/fix_move.html) LAMMPS syntax: fix ID group-ID move style args - linear args = Vx Vy Vz - wiggle args = Ax Ay Az period - rotate args = Px Py Pz Rx Ry Rz period - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period - variable args = v_dx v_dy v_dz v_vx v_vy v_vz Args: argtype: Type of the argument (linear, wiggle, rotate, transrot, variable) arg: The argument to validate arglen: Expected length of the argument """ prefix = "$" if arg in ("","none",None): return "" elif isinstance(arg,(list,tuple)): if len(arg) < arglen: print(f"NULL will be added to {argtype}") elif len(arg) > arglen: print(f"{argtype} will be truncated to {arglen} elements") argvalid = ["NULL"]*arglen for i in range(min(arglen,len(arg))): if isinstance(arg[i],str): if arg[i].upper()!="NULL": if prefix in arg[i]: argvalid[i] = round(eval(self.VARIABLES.formateval(arg[i])),6) else: argvalid[i] = arg[i] elif not isinstance(arg[i],(int,float)): if (arg[i] is not None): raise TypeError(f"{argtype} values should be str, int or float") return f"{prefix} move {span(argvalid)}" else: raise TypeError(f"the parameter {argtype} should be a list or tuple")
def get_fixmovesyntax(self, argtype=None)
-
Returns the syntax for LAMMPS command, or detailed explanation for a specific argument type
Args: argtype: Optional; Type of the argument (linear, wiggle, rotate, transrot, variable)
Expand source code
def get_fixmovesyntax(self, argtype=None): """ Returns the syntax for LAMMPS command, or detailed explanation for a specific argument type Args: argtype: Optional; Type of the argument (linear, wiggle, rotate, transrot, variable) """ syntax = { "linear": "linear args = Vx Vy Vz\n" "Vx,Vy,Vz = components of velocity vector (velocity units), any component can be specified as NULL", "wiggle": "wiggle args = Ax Ay Az period\n" "Ax,Ay,Az = components of amplitude vector (distance units), any component can be specified as NULL\n" "period = period of oscillation (time units)", "rotate": "rotate args = Px Py Pz Rx Ry Rz period\n" "Px,Py,Pz = origin point of axis of rotation (distance units)\n" "Rx,Ry,Rz = axis of rotation vector\n" "period = period of rotation (time units)", "transrot": "transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n" "Vx,Vy,Vz = components of velocity vector (velocity units)\n" "Px,Py,Pz = origin point of axis of rotation (distance units)\n" "Rx,Ry,Rz = axis of rotation vector\n" "period = period of rotation (time units)", "variable": "variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n" "v_dx,v_dy,v_dz = 3 variable names that calculate x,y,z displacement as function of time, any component can be specified as NULL\n" "v_vx,v_vy,v_vz = 3 variable names that calculate x,y,z velocity as function of time, any component can be specified as NULL", } base_syntax = ( "fix ID group-ID move style args\n" " - linear args = Vx Vy Vz\n" " - wiggle args = Ax Ay Az period\n" " - rotate args = Px Py Pz Rx Ry Rz period\n" " - transrot args = Vx Vy Vz Px Py Pz Rx Ry Rz period\n" " - variable args = v_dx v_dy v_dz v_vx v_vy v_vz\n\n" 'use get_movesyntax("movemethod") for details' "manual: https://docs.lammps.org/fix_move.html" ) return syntax.get(argtype, base_syntax)
def movearg(self, move)
-
Validation of move arguments for region command (https://docs.lammps.org/region.html) move args = v_x v_y v_z v_x,v_y,v_z = equal-style variables for x,y,z displacement of region over time (distance units)
Expand source code
def movearg(self,move): """ Validation of move arguments for region command (https://docs.lammps.org/region.html) move args = v_x v_y v_z v_x,v_y,v_z = equal-style variables for x,y,z displacement of region over time (distance units) """ prefix = "$" if move is None: return "" elif isinstance(move, str): move = move.lower() if move in("","none"): return "" else: return f"{prefix} move {move}" elif isinstance(move,(list,tuple)): if len(move)<3: print("NULL will be added to move") elif len(move)>3: print("move will be truncated to 3 elements") movevalid = ["NULL","NULL","NULL"] for i in range(min(3,len(move))): if isinstance(move[i],str): if move[i].upper()!="NULL": if prefix in move[i]: # we assume a numeric result after evaluation # Pizza variables will be evaluated # formateval for the evaluation of ${} # eval for residual expressions movevalid[i] = round(eval(self.VARIABLES.formateval(move[i])),6) else: # we assume a variable (LAMMPS variable, not Pizza ones) movevalid[i] = "v_" + move[i] elif not isinstance(move[i],(int,float)): if (move[i] is not None): raise TypeError("move values should be str, int or float") return f"{prefix} move {span(movevalid)}" else: raise TypeError("the parameter move should be a list or tuple")
def openarg(self, open)
-
Validation of open arguments for region command (https://docs.lammps.org/region.html) open value = integer from 1-6 corresponding to face index (see below) The indices specified as part of the open keyword have the following meanings:
For style block, indices 1-6 correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces of the block. I.e. 1 is the yz plane at x = xlo, 2 is the yz-plane at x = xhi, 3 is the xz plane at y = ylo, 4 is the xz plane at y = yhi, 5 is the xy plane at z = zlo, 6 is the xy plane at z = zhi). In the second-to-last example above, the region is a box open at both xy planes.
For style prism, values 1-6 have the same mapping as for style block. I.e. in an untilted prism, open indices correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces.
For style cylinder, index 1 corresponds to the flat end cap at the low coordinate along the cylinder axis, index 2 corresponds to the high-coordinate flat end cap along the cylinder axis, and index 3 is the curved cylinder surface. For example, a cylinder region with open 1 open 2 keywords will be open at both ends (e.g. a section of pipe), regardless of the cylinder orientation.
Expand source code
def openarg(self,open): """ Validation of open arguments for region command (https://docs.lammps.org/region.html) open value = integer from 1-6 corresponding to face index (see below) The indices specified as part of the open keyword have the following meanings: For style block, indices 1-6 correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces of the block. I.e. 1 is the yz plane at x = xlo, 2 is the yz-plane at x = xhi, 3 is the xz plane at y = ylo, 4 is the xz plane at y = yhi, 5 is the xy plane at z = zlo, 6 is the xy plane at z = zhi). In the second-to-last example above, the region is a box open at both xy planes. For style prism, values 1-6 have the same mapping as for style block. I.e. in an untilted prism, open indices correspond to the xlo, xhi, ylo, yhi, zlo, zhi surfaces. For style cylinder, index 1 corresponds to the flat end cap at the low coordinate along the cylinder axis, index 2 corresponds to the high-coordinate flat end cap along the cylinder axis, and index 3 is the curved cylinder surface. For example, a cylinder region with open 1 open 2 keywords will be open at both ends (e.g. a section of pipe), regardless of the cylinder orientation. """ prefix = "$" if open in ("","none",None): return "" elif isinstance(open, str): raise TypeError(" the parameter open should be an integer or a list/tuple of integers from 1-6") elif isinstance(open, int): if open in range(1,7): return f"{prefix} open {open}" else: raise TypeError(" open value should be integer from 1-6") elif isinstance(open, (list,tuple)): openvalid = [f"{prefix} open {i}" for i in range(1,7) if i in open] return f"$ {span(openvalid)}"
def removegroup(self)
-
force the group creation in script
Expand source code
def removegroup(self): """ force the group creation in script """ self.FLAGSECTIONS["group"] = False
def removemove(self)
-
force the fix move creation in script
Expand source code
def removemove(self): """ force the fix move creation in script """ self.FLAGSECTIONS["move"] = False
def rotatearg(self, rotate)
-
Validation of rotate arguments for region command (https://docs.lammps.org/region.html) rotate args = v_theta Px Py Pz Rx Ry Rz v_theta = equal-style variable for rotaton of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector
Expand source code
def rotatearg(self,rotate): """ Validation of rotate arguments for region command (https://docs.lammps.org/region.html) rotate args = v_theta Px Py Pz Rx Ry Rz v_theta = equal-style variable for rotaton of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector """ prefix = "$" if rotate is None: return "" elif isinstance(rotate, str): rotate = rotate.lower() if rotate in ("","none",None): return "" else: return f"{prefix} rotate {rotate}" elif isinstance(rotate,(list,tuple)): if len(rotate)<7: print("NULL will be added to rotate") elif len(rotate)>7: print("rotate will be truncated to 7 elements") rotatevalid = ["NULL"]*7 for i in range(min(7,len(rotate))): if isinstance(rotate[i],str): if rotate[i].upper()!="NULL": if prefix in rotate[i]: rotatevalid[i] = round(eval(self.VARIABLES.formateval(rotate[i])),6) else: rotatevalid[i] = rotate[i] elif not isinstance(rotate[i],(int,float)): if (rotate[i] is not None): raise TypeError("rotate values should be str, int or float") return f"{prefix} move {span(rotatevalid)}" else: raise TypeError("the parameter rotate should be a list or tuple")
def scriptobject(self, beadtype=None, name=None, fullname=None, group=None, style=None, forcefield=None, USER=script data (SD object) with 0 definitions)
-
Method to return a scriptobject based on region instead of an input file Syntax similar to script.scriptobject OBJ = scriptobject(…) Implemented properties: beadtype=1,2,… name="short name" fullname = "comprehensive name" style = "smd" forcefield = any valid forcefield instance (default = rigidwall())
Expand source code
def scriptobject(self, beadtype=None, name=None, fullname=None, group=None, style=None, forcefield=None, USER = scriptdata()): """ Method to return a scriptobject based on region instead of an input file Syntax similar to script.scriptobject OBJ = scriptobject(...) Implemented properties: beadtype=1,2,... name="short name" fullname = "comprehensive name" style = "smd" forcefield = any valid forcefield instance (default = rigidwall()) """ # Set defaults using instance attributes if parameters are None if beadtype is None: beadtype = self.beadtype if name is None: name = f"{self.name} bead" if fullname is None: fullname = f"beads of type {self.beadtype} | object {self.name} of kind region.{self.kind}" if group is None: group = self.SCRIPTOBJECT_USER["group"] if style is None: style = self.SCRIPTOBJECT_USER["style"] if forcefield is None: style = self.SCRIPTOBJECT_USER["forcefield"] return scriptobject( beadtype=beadtype, name=name, fullname=fullname, style=style, group=group, filename=None, # No need for a file USER = USER )
def setgroup(self)
-
force the group creation in script
Expand source code
def setgroup(self): """ force the group creation in script """ self.FLAGSECTIONS["setgroup"] = True
def sidearg(self, side)
-
Validation of side arguments for region command (https://docs.lammps.org/region.html) side value = in or out in = the region is inside the specified geometry out = the region is outside the specified geometry
Expand source code
def sidearg(self,side): """ Validation of side arguments for region command (https://docs.lammps.org/region.html) side value = in or out in = the region is inside the specified geometry out = the region is outside the specified geometry """ prefix = "$" if side is None: return "" elif isinstance(side, str): side = side.lower() if side in ("in","out"): return f"{prefix} side {side}" elif side in ("","none"): return "" else: raise ValueError(f'the value of side: "{side}" is not recognized') else: raise ValueError('the parameter side can be "in|out|None"')
def unitsarg(self, units)
-
Validation for units arguments for region command (https://docs.lammps.org/region.html) units value = lattice or box lattice = the geometry is defined in lattice units box = the geometry is defined in simulation box units
Expand source code
def unitsarg(self,units): """ Validation for units arguments for region command (https://docs.lammps.org/region.html) units value = lattice or box lattice = the geometry is defined in lattice units box = the geometry is defined in simulation box units """ prefix = "$" if units is None: return "" elif isinstance(units,str): units = units.lower() if units in ("lattice","box"): return f"{prefix} units {units}" elif (units=="") or (units=="none"): return "" else: raise ValueError(f'the value of side: "{units}" is not recognized') else: raise TypeError('the parameter units can be "lattice|box|None"')
def update(self)
-
update the USER content for all three scripts
Expand source code
def update(self): """ update the USER content for all three scripts """ if isinstance(self.SECTIONS["variables"],script): self.SECTIONS["variables"].USER += self.USER if isinstance(self.SECTIONS["region"],script): self.SECTIONS["region"].USER += self.USER if isinstance(self.SECTIONS["create"],script): self.SECTIONS["create"].USER += self.USER if isinstance(self.SECTIONS["group"],script): self.SECTIONS["group"].USER += self.USER if isinstance(self.SECTIONS["setgroup"],script): self.SECTIONS["setgroup"].USER += self.USER if isinstance(self.SECTIONS["move"],script): self.SECTIONS["move"].USER += self.USER
class emulsion (xmin=10, ymin=10, zmin=10, xmax=90, ymax=90, zmax=90, maxtrials=1000, beadtype=1, forcedinsertion=True)
-
emulsion generator
Parameters
- The insertions are performed between xmin,ymin and xmax,ymax
xmin
:int64
orreal
, optional- x left corner. The default is 10.
ymin
:int64
orreal
, optional- y bottom corner. The default is 10.
zmin
:int64
orreal
, optional- z bottom corner. The default is 10.
xmax
:int64
orreal
, optional- x right corner. The default is 90.
ymax
:int64
orreal
, optional- y top corner. The default is 90.
zmax
:int64
orreal
, optional- z top corner. The default is 90.
beadtype
:default beadtype to apply if not precised at insertion
maxtrials
:integer
, optional- Maximum of attempts for an object. The default is 1000.
forcedinsertion
:logical
, optional- Set it to true to force the next insertion. The default is True.
Returns
None.
Expand source code
class emulsion(scatter): """ emulsion generator """ def __init__(self, xmin=10, ymin=10, zmin=10, xmax=90, ymax=90, zmax=90, maxtrials=1000, beadtype=1, forcedinsertion=True): """ Parameters ---------- The insertions are performed between xmin,ymin and xmax,ymax xmin : int64 or real, optional x left corner. The default is 10. ymin : int64 or real, optional y bottom corner. The default is 10. zmin : int64 or real, optional z bottom corner. The default is 10. xmax : int64 or real, optional x right corner. The default is 90. ymax : int64 or real, optional y top corner. The default is 90. zmax : int64 or real, optional z top corner. The default is 90. beadtype : default beadtype to apply if not precised at insertion maxtrials : integer, optional Maximum of attempts for an object. The default is 1000. forcedinsertion : logical, optional Set it to true to force the next insertion. The default is True. Returns ------- None. """ super().__init__() self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax = xmin, xmax, ymin, ymax, zmin, zmax self.lastinsertion = (None,None,None,None,None) # x,y,z,r, beadtype self.length = xmax-xmin self.width = ymax-ymin self.height = zmax-zmin self.defautbeadtype = beadtype self.maxtrials = maxtrials self.forcedinsertion = forcedinsertion def __repr__(self): print(f" Emulsion object\n\t{self.length}x{self.width}x{self.height} starting at x={self.xmin}, y={self.ymin}, z={self.zmin}") print(f"\tcontains {self.n} insertions") print("\tmaximum insertion trials:", self.maxtrials) print("\tforce next insertion if previous fails:", self.forcedinsertion) return f"emulsion with {self.n} insertions" def walldist(self,x,y,z): """ shortest distance to the wall """ return min(abs(x-self.xmin),abs(y-self.ymin),abs(z-self.zmin),abs(x-self.xmax),abs(y-self.ymax),abs(z-self.zmax)) def dist(self,x,y,z): """ shortest distance of the center (x,y) to the wall or any object""" return np.minimum(np.min(self.pairdist(x,y,z)),self.walldist(x,y,z)) def accepted(self,x,y,z,r): """ acceptation criterion """ return self.dist(x,y,z)>r def rand(self): """ random position x,y """ return np.random.uniform(low=self.xmin,high=self.xmax), \ np.random.uniform(low=self.ymin,high=self.ymax),\ np.random.uniform(low=self.zmin,high=self.zmax) def setbeadtype(self,beadtype): """ set the default or the supplied beadtype """ if beadtype == None: self.beadtype.append(self.defautbeadtype) return self.defautbeadtype else: self.beadtype.append(beadtype) return beadtype def insertone(self,x=None,y=None,z=None,r=None,beadtype=None,overlap=False): """ insert one object of radius r properties: x,y,z coordinates (if missing, picked randomly from uniform distribution) r radius (default = 2% of diagonal) beadtype (default = defautbeadtype) overlap = False (accept only if no overlap) """ attempt, success = 0, False random = (x==None) or (y==None) or (z==None) if r==None: r = 0.02*np.sqrt(self.length**2+self.width**2+self.height**2) while not success and attempt<self.maxtrials: attempt += 1 if random: x,y,z = self.rand() if overlap: success = True else: success = self.accepted(x,y,z,r) if success: self.x = np.append(self.x,x) self.y = np.append(self.y,y) self.z = np.append(self.z,z) self.r = np.append(self.r,r) b=self.setbeadtype(beadtype) self.lastinsertion = (x,y,z,r,b) return success def insertion(self,rlist,beadtype=None): """ insert a list of objects nsuccess=insertion(rlist,beadtype=None) beadtype=b forces the value b if None, defaultbeadtype is used instead """ rlist.sort(reverse=True) ntodo = len(rlist) n = nsuccess = 0 stop = False while not stop: n += 1 success = self.insertone(r=rlist[n-1],beadtype=beadtype) if success: nsuccess += 1 stop = (n==ntodo) or (not success and not self.forcedinsertion) if nsuccess==ntodo: print(f"{nsuccess} objects inserted successfully") else: print(f"partial success: {nsuccess} of {ntodo} objects inserted") return nsuccess
Ancestors
Methods
def accepted(self, x, y, z, r)
-
acceptation criterion
Expand source code
def accepted(self,x,y,z,r): """ acceptation criterion """ return self.dist(x,y,z)>r
def dist(self, x, y, z)
-
shortest distance of the center (x,y) to the wall or any object
Expand source code
def dist(self,x,y,z): """ shortest distance of the center (x,y) to the wall or any object""" return np.minimum(np.min(self.pairdist(x,y,z)),self.walldist(x,y,z))
def insertion(self, rlist, beadtype=None)
-
insert a list of objects nsuccess=insertion(rlist,beadtype=None) beadtype=b forces the value b if None, defaultbeadtype is used instead
Expand source code
def insertion(self,rlist,beadtype=None): """ insert a list of objects nsuccess=insertion(rlist,beadtype=None) beadtype=b forces the value b if None, defaultbeadtype is used instead """ rlist.sort(reverse=True) ntodo = len(rlist) n = nsuccess = 0 stop = False while not stop: n += 1 success = self.insertone(r=rlist[n-1],beadtype=beadtype) if success: nsuccess += 1 stop = (n==ntodo) or (not success and not self.forcedinsertion) if nsuccess==ntodo: print(f"{nsuccess} objects inserted successfully") else: print(f"partial success: {nsuccess} of {ntodo} objects inserted") return nsuccess
def insertone(self, x=None, y=None, z=None, r=None, beadtype=None, overlap=False)
-
insert one object of radius r properties: x,y,z coordinates (if missing, picked randomly from uniform distribution) r radius (default = 2% of diagonal) beadtype (default = defautbeadtype) overlap = False (accept only if no overlap)
Expand source code
def insertone(self,x=None,y=None,z=None,r=None,beadtype=None,overlap=False): """ insert one object of radius r properties: x,y,z coordinates (if missing, picked randomly from uniform distribution) r radius (default = 2% of diagonal) beadtype (default = defautbeadtype) overlap = False (accept only if no overlap) """ attempt, success = 0, False random = (x==None) or (y==None) or (z==None) if r==None: r = 0.02*np.sqrt(self.length**2+self.width**2+self.height**2) while not success and attempt<self.maxtrials: attempt += 1 if random: x,y,z = self.rand() if overlap: success = True else: success = self.accepted(x,y,z,r) if success: self.x = np.append(self.x,x) self.y = np.append(self.y,y) self.z = np.append(self.z,z) self.r = np.append(self.r,r) b=self.setbeadtype(beadtype) self.lastinsertion = (x,y,z,r,b) return success
def rand(self)
-
random position x,y
Expand source code
def rand(self): """ random position x,y """ return np.random.uniform(low=self.xmin,high=self.xmax), \ np.random.uniform(low=self.ymin,high=self.ymax),\ np.random.uniform(low=self.zmin,high=self.zmax)
def setbeadtype(self, beadtype)
-
set the default or the supplied beadtype
Expand source code
def setbeadtype(self,beadtype): """ set the default or the supplied beadtype """ if beadtype == None: self.beadtype.append(self.defautbeadtype) return self.defautbeadtype else: self.beadtype.append(beadtype) return beadtype
def walldist(self, x, y, z)
-
shortest distance to the wall
Expand source code
def walldist(self,x,y,z): """ shortest distance to the wall """ return min(abs(x-self.xmin),abs(y-self.ymin),abs(z-self.zmin),abs(x-self.xmax),abs(y-self.ymax),abs(z-self.zmax))
Inherited members
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
- pizza.forcefield.smd
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 headersRegiondata (sortdefinitions=False, **kwargs)
-
class of script parameters Typical constructor: DEFINITIONS = headersRegiondata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it
constructor
Expand source code
class headersRegiondata(regiondata): """ class of script parameters Typical constructor: DEFINITIONS = headersRegiondata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it """ _type = "HRD" _fulltype = "Header parameters - helper for scripts" _ftype = "header definition"
Ancestors
- regiondata
- pizza.private.mstruct.paramauto
- pizza.private.mstruct.param
- pizza.private.mstruct.struct
Inherited members
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
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Subclasses
- pizza.forcefield.rigidwall
Class variables
var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name
class param (sortdefinitions=False, **kwargs)
-
Class:
param
A class derived from
struct
that introduces dynamic evaluation of field values. Theparam
class acts as a container for evaluated parameters, allowing expressions to depend on other fields. It supports advanced evaluation, sorting of dependencies, and text formatting.
Features
- Inherits all functionalities of
struct
. - Supports dynamic evaluation of field expressions.
- Automatically resolves dependencies between fields.
- Includes utility methods for text formatting and evaluation.
Examples
Basic Usage with Evaluation
s = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can', d="$this is a string", e="1000 # this is my number") s.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 3) # d: $this is a string (= this is a string) # e: 1000 # this is my number (= 1000) # -------- s.a = 10 s.eval() # Output: # -------- # a: 10 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 12) # d: $this is a string (= this is a string) # e: 1000 # this is my number (= 1000) # --------
Handling Text Parameters
s = param() s.mypath = "$/this/folder" s.myfile = "$file" s.myext = "$ext" s.fullfile = "$${mypath}/${myfile}.${myext}" s.eval() # Output: # -------- # mypath: $/this/folder (= /this/folder) # myfile: $file (= file) # myext: $ext (= ext) # fullfile: $${mypath}/${myfile}.${myext} (= /this/folder/file.ext) # --------
Text Evaluation and Formatting
Evaluate Strings
s = param(a=1, b=2) result = s.eval("this is a string with ${a} and ${b}") print(result) # "this is a string with 1 and 2"
Prevent Evaluation
definitions = param(a=1, b="${a}*10+${a}", c="\${a}+10", d='\${myparam}') text = definitions.formateval("this is my text ${a}, ${b}, \${myvar}=${c}+${d}") print(text) # "this is my text 1, 11, \${myvar}=\${a}+10+${myparam}"
Advanced Usage
Rearranging and Sorting Definitions
s = param( a=1, f="${e}/3", e="${a}*${c}", c="${a}+${b}", b=2, d="${c}*2" ) s.sortdefinitions() s.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} (= 3) # d: ${c} * 2 (= 6) # e: ${a} * ${c} (= 3) # f: ${e} / 3 (= 1.0) # --------
Error Handling
p = param(b="${a}+1", c="${a}+${d}", a=1) p.disp() # Output: # -------- # b: ${a} + 1 (= 2) # c: ${a} + ${d} (= < undef definition "${d}" >) # a: 1 # --------
Sorting unresolved definitions raises errors unless explicitly suppressed:
p.sortdefinitions(raiseerror=False) # WARNING: unable to interpret 1/3 expressions in "definitions"
Utility Methods
Method Description eval()
Evaluate all field expressions. formateval(string)
Format and evaluate a string with field placeholders. protect(string)
Escape variable placeholders in a string. sortdefinitions()
Sort definitions to resolve dependencies. escape(string)
Protect escaped variables in a string.
Overloaded Methods and Operators
Supported Operators
+
: Concatenation of two parameter lists, sorting definitions.-
: Subtraction of fields.len()
: Number of fields.in
: Check for field existence.
Notes
- The
paramauto
class simplifies handling of partial definitions and inherits fromparam
. - Use
paramauto
when definitions need to be stacked irrespective of execution order.
constructor
Expand source code
class param(struct): """ Class: `param` ============== A class derived from `struct` that introduces dynamic evaluation of field values. The `param` class acts as a container for evaluated parameters, allowing expressions to depend on other fields. It supports advanced evaluation, sorting of dependencies, and text formatting. --- ### Features - Inherits all functionalities of `struct`. - Supports dynamic evaluation of field expressions. - Automatically resolves dependencies between fields. - Includes utility methods for text formatting and evaluation. --- ### Examples #### Basic Usage with Evaluation ```python s = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can', d="$this is a string", e="1000 # this is my number") s.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 3) # d: $this is a string (= this is a string) # e: 1000 # this is my number (= 1000) # -------- s.a = 10 s.eval() # Output: # -------- # a: 10 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 12) # d: $this is a string (= this is a string) # e: 1000 # this is my number (= 1000) # -------- ``` #### Handling Text Parameters ```python s = param() s.mypath = "$/this/folder" s.myfile = "$file" s.myext = "$ext" s.fullfile = "$${mypath}/${myfile}.${myext}" s.eval() # Output: # -------- # mypath: $/this/folder (= /this/folder) # myfile: $file (= file) # myext: $ext (= ext) # fullfile: $${mypath}/${myfile}.${myext} (= /this/folder/file.ext) # -------- ``` --- ### Text Evaluation and Formatting #### Evaluate Strings ```python s = param(a=1, b=2) result = s.eval("this is a string with ${a} and ${b}") print(result) # "this is a string with 1 and 2" ``` #### Prevent Evaluation ```python definitions = param(a=1, b="${a}*10+${a}", c="\${a}+10", d='\${myparam}') text = definitions.formateval("this is my text ${a}, ${b}, \${myvar}=${c}+${d}") print(text) # "this is my text 1, 11, \${myvar}=\${a}+10+${myparam}" ``` --- ### Advanced Usage #### Rearranging and Sorting Definitions ```python s = param( a=1, f="${e}/3", e="${a}*${c}", c="${a}+${b}", b=2, d="${c}*2" ) s.sortdefinitions() s.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} (= 3) # d: ${c} * 2 (= 6) # e: ${a} * ${c} (= 3) # f: ${e} / 3 (= 1.0) # -------- ``` #### Error Handling ```python p = param(b="${a}+1", c="${a}+${d}", a=1) p.disp() # Output: # -------- # b: ${a} + 1 (= 2) # c: ${a} + ${d} (= < undef definition "${d}" >) # a: 1 # -------- ``` Sorting unresolved definitions raises errors unless explicitly suppressed: ```python p.sortdefinitions(raiseerror=False) # WARNING: unable to interpret 1/3 expressions in "definitions" ``` --- ### Utility Methods | Method | Description | |-----------------------|---------------------------------------------------------| | `eval()` | Evaluate all field expressions. | | `formateval(string)` | Format and evaluate a string with field placeholders. | | `protect(string)` | Escape variable placeholders in a string. | | `sortdefinitions()` | Sort definitions to resolve dependencies. | | `escape(string)` | Protect escaped variables in a string. | --- ### Overloaded Methods and Operators #### Supported Operators - `+`: Concatenation of two parameter lists, sorting definitions. - `-`: Subtraction of fields. - `len()`: Number of fields. - `in`: Check for field existence. --- ### Notes - The `paramauto` class simplifies handling of partial definitions and inherits from `param`. - Use `paramauto` when definitions need to be stacked irrespective of execution order. """ # override _type = "param" _fulltype = "parameter list" _ftype = "definition" _evalfeature = True # This class can be evaluated with .eval() _returnerror = True # This class returns an error in the evaluation string (added on 2024-09-06) # magic constructor def __init__(self,_protection=False,_evaluation=True, sortdefinitions=False,**kwargs): """ constructor """ super().__init__(**kwargs) self._protection = _protection self._evaluation = _evaluation if sortdefinitions: self.sortdefinitions() # escape definitions if needed @staticmethod def escape(s): """ escape \${} as ${{}} --> keep variable names convert ${} as {} --> prepare Python replacement Examples: escape("\${a}") returns ('${{a}}', True) escape(" \${abc} ${a} \${bc}") returns (' ${{abc}} {a} ${{bc}}', True) escape("${a}") Out[94]: ('{a}', False) escape("${tata}") returns ('{tata}', False) """ if not isinstance(s,str): raise TypeError(f'the argument must be string not {type(s)}') se, start, found = "", 0, True while found: pos0 = s.find("\${",start) found = pos0>=0 if found: pos1 = s.find("}",pos0) found = pos1>=0 if found: se += s[start:pos0].replace("${","{")+"${{"+s[pos0+3:pos1]+"}}" start=pos1+1 result = se+s[start:].replace("${","{") if isinstance(s,pstr): result = pstr(result) return result,start>0 # protect variables in a string def protect(self,s=""): """ protect $variable as ${variable} """ if isinstance(s,str): t = s.replace("\$","££") # && is a placeholder escape = t!=s for k in self.keyssorted(): t = t.replace("$"+k,"${"+k+"}") if escape: t = t.replace("££","\$") if isinstance(s,pstr): t = pstr(t) return t, escape raise TypeError(f'the argument must be string not {type(s)}') # lines starting with # (hash) are interpreted as comments # ${variable} or {variable} are substituted by variable.value # any line starting with $ is assumed to be a string (no interpretation) # ^ is accepted in formula(replaced by **)) def eval(self,s="",protection=False): """ Eval method for structure such as MS.alias s = p.eval() or s = p.eval(string) where : p is a param object s is a structure with evaluated fields string is only used to determine whether definitions have been forgotten """ # Evaluate all DEFINITIONS # the argument s is only used by formateval() for error management tmp = struct() for key,value in self.items(): # strings are assumed to be expressions on one single line if isinstance(value,str): # replace ${variable} (Bash, Lammps syntax) by {variable} (Python syntax) # use \${variable} to prevent replacement (espace with \) # Protect variables if required ispstr = isinstance(value,pstr) valuesafe = pstr.eval(value,ispstr=ispstr) # value.strip() if protection or self._protection: valuesafe, escape0 = self.protect(valuesafe) else: escape0 = False valuesafe, escape = param.escape(valuesafe) escape = escape or escape0 # replace "^" (Matlab, Lammps exponent) by "**" (Python syntax) valuesafe = pstr.eval(valuesafe.replace("^","**"),ispstr=ispstr) # Remove all content after # # if the first character is '#', it is not comment (e.g. MarkDown titles) poscomment = valuesafe.find("#") if poscomment>0: valuesafe = valuesafe[0:poscomment].strip() # Literal string starts with $ if not self._evaluation: tmp.setattr(key, pstr.eval(tmp.format(valuesafe,escape),ispstr=ispstr)) elif valuesafe.startswith("$") and not escape: tmp.setattr(key,tmp.format(valuesafe[1:].lstrip())) # discard $ elif valuesafe.startswith("%"): tmp.setattr(key,tmp.format(valuesafe[1:].lstrip())) # discard % else: # string empty or which can be evaluated if valuesafe=="": tmp.setattr(key,valuesafe) # empty content else: if isinstance(value,pstr): # keep path tmp.setattr(key, pstr.topath(tmp.format(valuesafe,escape=escape))) elif escape: # partial evaluation tmp.setattr(key, tmp.format(valuesafe,escape=True)) else: # full evaluation try: resstr = tmp.format(valuesafe,raiseerror=False) except (KeyError,NameError) as nameerr: if self._returnerror: # added on 2024-09-06 strnameerr = str(nameerr).replace("'","") tmp.setattr(key,'< undef %s "${%s}" >' % \ (self._ftype,strnameerr)) else: tmp.setattr(key,value) #we keep the original value except (SyntaxError,TypeError,ValueError) as commonerr: tmp.setattr(key,"ERROR < %s >" % commonerr) except Exception as othererr: tmp.setattr(key,"Unknown Error < %s >" % othererr) else: try: reseval = eval(resstr) except Exception as othererr: tmp.setattr(key,"Eval Error < %s >" % othererr) else: tmp.setattr(key,reseval) elif isinstance(value,(int,float,list,tuple)): # already a number tmp.setattr(key, value) # store the value with the key else: # unsupported types if s.find("{"+key+"}")>=0: print(f'*** WARNING ***\n\tIn the {self._ftype}:"\n{s}\n"') else: print(f'unable to interpret the "{key}" of type {type(value)}') return tmp # formateval obeys to following rules # lines starting with # (hash) are interpreted as comments def formateval(self,s,protection=False): """ format method with evaluation feature txt = p.formateval("this my text with ${variable1}, ${variable2} ") where: p is a param object Example: definitions = param(a=1,b="${a}",c="\${a}") text = definitions.formateval("this my text ${a}, ${b}, ${c}") print(text) """ tmp = self.eval(s,protection=protection) # Do all replacements in s (keep comments) if len(tmp)==0: return s else: ispstr = isinstance(s,pstr) ssafe, escape = param.escape(s) slines = ssafe.split("\n") for i in range(len(slines)): poscomment = slines[i].find("#") if poscomment>=0: while (poscomment>0) and (slines[i][poscomment-1]==" "): poscomment -= 1 comment = slines[i][poscomment:len(slines[i])] slines[i] = slines[i][0:poscomment] else: comment = "" # Protect variables if required if protection or self._protection: slines[i], escape2 = self.protect(slines[i]) # conversion if ispstr: slines[i] = pstr.eval(tmp.format(slines[i],escape=escape),ispstr=ispstr) else: slines[i] = tmp.format(slines[i],escape=escape)+comment # convert starting % into # to authorize replacement in comments if len(slines[i])>0: if slines[i][0] == "%": slines[i]="#"+slines[i][1:] return "\n".join(slines) # returns the equivalent structure evaluated def tostruct(self,protection=False): """ generate the evaluated structure tostruct(protection=False) """ return self.eval(protection=protection) # returns the equivalent structure evaluated def tostatic(self): """ convert dynamic a param() object to a static struct() object. note: no interpretation note: use tostruct() to interpret them and convert it to struct note: tostatic().struct2param() makes it reversible """ return struct.fromkeysvalues(self.keys(),self.values(),makeparam=False)
Ancestors
- pizza.private.mstruct.struct
Subclasses
- pizza.private.mstruct.paramauto
- pizza.script.scriptdata
Static methods
def escape(s)
-
escape \${} as ${{}} –> keep variable names convert ${} as {} –> prepare Python replacement
Examples
escape("\${a}") returns ('${{a}}', True)
escape(" \${abc} ${a} \${bc}") returns (' ${{abc}} {a} ${{bc}}', True)
escape("${a}") Out[94]: ('{a}', False)
escape("${tata}") returns ('{tata}', False)
Expand source code
@staticmethod def escape(s): """ escape \${} as ${{}} --> keep variable names convert ${} as {} --> prepare Python replacement Examples: escape("\${a}") returns ('${{a}}', True) escape(" \${abc} ${a} \${bc}") returns (' ${{abc}} {a} ${{bc}}', True) escape("${a}") Out[94]: ('{a}', False) escape("${tata}") returns ('{tata}', False) """ if not isinstance(s,str): raise TypeError(f'the argument must be string not {type(s)}') se, start, found = "", 0, True while found: pos0 = s.find("\${",start) found = pos0>=0 if found: pos1 = s.find("}",pos0) found = pos1>=0 if found: se += s[start:pos0].replace("${","{")+"${{"+s[pos0+3:pos1]+"}}" start=pos1+1 result = se+s[start:].replace("${","{") if isinstance(s,pstr): result = pstr(result) return result,start>0
Methods
def eval(self, s='', protection=False)
-
Eval method for structure such as MS.alias
s = p.eval() or s = p.eval(string) where : p is a param object s is a structure with evaluated fields string is only used to determine whether definitions have been forgotten
Expand source code
def eval(self,s="",protection=False): """ Eval method for structure such as MS.alias s = p.eval() or s = p.eval(string) where : p is a param object s is a structure with evaluated fields string is only used to determine whether definitions have been forgotten """ # Evaluate all DEFINITIONS # the argument s is only used by formateval() for error management tmp = struct() for key,value in self.items(): # strings are assumed to be expressions on one single line if isinstance(value,str): # replace ${variable} (Bash, Lammps syntax) by {variable} (Python syntax) # use \${variable} to prevent replacement (espace with \) # Protect variables if required ispstr = isinstance(value,pstr) valuesafe = pstr.eval(value,ispstr=ispstr) # value.strip() if protection or self._protection: valuesafe, escape0 = self.protect(valuesafe) else: escape0 = False valuesafe, escape = param.escape(valuesafe) escape = escape or escape0 # replace "^" (Matlab, Lammps exponent) by "**" (Python syntax) valuesafe = pstr.eval(valuesafe.replace("^","**"),ispstr=ispstr) # Remove all content after # # if the first character is '#', it is not comment (e.g. MarkDown titles) poscomment = valuesafe.find("#") if poscomment>0: valuesafe = valuesafe[0:poscomment].strip() # Literal string starts with $ if not self._evaluation: tmp.setattr(key, pstr.eval(tmp.format(valuesafe,escape),ispstr=ispstr)) elif valuesafe.startswith("$") and not escape: tmp.setattr(key,tmp.format(valuesafe[1:].lstrip())) # discard $ elif valuesafe.startswith("%"): tmp.setattr(key,tmp.format(valuesafe[1:].lstrip())) # discard % else: # string empty or which can be evaluated if valuesafe=="": tmp.setattr(key,valuesafe) # empty content else: if isinstance(value,pstr): # keep path tmp.setattr(key, pstr.topath(tmp.format(valuesafe,escape=escape))) elif escape: # partial evaluation tmp.setattr(key, tmp.format(valuesafe,escape=True)) else: # full evaluation try: resstr = tmp.format(valuesafe,raiseerror=False) except (KeyError,NameError) as nameerr: if self._returnerror: # added on 2024-09-06 strnameerr = str(nameerr).replace("'","") tmp.setattr(key,'< undef %s "${%s}" >' % \ (self._ftype,strnameerr)) else: tmp.setattr(key,value) #we keep the original value except (SyntaxError,TypeError,ValueError) as commonerr: tmp.setattr(key,"ERROR < %s >" % commonerr) except Exception as othererr: tmp.setattr(key,"Unknown Error < %s >" % othererr) else: try: reseval = eval(resstr) except Exception as othererr: tmp.setattr(key,"Eval Error < %s >" % othererr) else: tmp.setattr(key,reseval) elif isinstance(value,(int,float,list,tuple)): # already a number tmp.setattr(key, value) # store the value with the key else: # unsupported types if s.find("{"+key+"}")>=0: print(f'*** WARNING ***\n\tIn the {self._ftype}:"\n{s}\n"') else: print(f'unable to interpret the "{key}" of type {type(value)}') return tmp
def formateval(self, s, protection=False)
-
format method with evaluation feature
txt = p.formateval("this my text with ${variable1}, ${variable2} ") where: p is a param object Example: definitions = param(a=1,b="${a}",c="\${a}") text = definitions.formateval("this my text ${a}, ${b}, ${c}") print(text)
Expand source code
def formateval(self,s,protection=False): """ format method with evaluation feature txt = p.formateval("this my text with ${variable1}, ${variable2} ") where: p is a param object Example: definitions = param(a=1,b="${a}",c="\${a}") text = definitions.formateval("this my text ${a}, ${b}, ${c}") print(text) """ tmp = self.eval(s,protection=protection) # Do all replacements in s (keep comments) if len(tmp)==0: return s else: ispstr = isinstance(s,pstr) ssafe, escape = param.escape(s) slines = ssafe.split("\n") for i in range(len(slines)): poscomment = slines[i].find("#") if poscomment>=0: while (poscomment>0) and (slines[i][poscomment-1]==" "): poscomment -= 1 comment = slines[i][poscomment:len(slines[i])] slines[i] = slines[i][0:poscomment] else: comment = "" # Protect variables if required if protection or self._protection: slines[i], escape2 = self.protect(slines[i]) # conversion if ispstr: slines[i] = pstr.eval(tmp.format(slines[i],escape=escape),ispstr=ispstr) else: slines[i] = tmp.format(slines[i],escape=escape)+comment # convert starting % into # to authorize replacement in comments if len(slines[i])>0: if slines[i][0] == "%": slines[i]="#"+slines[i][1:] return "\n".join(slines)
def protect(self, s='')
-
protect $variable as ${variable}
Expand source code
def protect(self,s=""): """ protect $variable as ${variable} """ if isinstance(s,str): t = s.replace("\$","££") # && is a placeholder escape = t!=s for k in self.keyssorted(): t = t.replace("$"+k,"${"+k+"}") if escape: t = t.replace("££","\$") if isinstance(s,pstr): t = pstr(t) return t, escape raise TypeError(f'the argument must be string not {type(s)}')
def tostatic(self)
-
convert dynamic a param() object to a static struct() object. note: no interpretation note: use tostruct() to interpret them and convert it to struct note: tostatic().struct2param() makes it reversible
Expand source code
def tostatic(self): """ convert dynamic a param() object to a static struct() object. note: no interpretation note: use tostruct() to interpret them and convert it to struct note: tostatic().struct2param() makes it reversible """ return struct.fromkeysvalues(self.keys(),self.values(),makeparam=False)
def tostruct(self, protection=False)
-
generate the evaluated structure tostruct(protection=False)
Expand source code
def tostruct(self,protection=False): """ generate the evaluated structure tostruct(protection=False) """ return self.eval(protection=protection)
- Inherits all functionalities of
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
- pizza.dscript.lambdaScriptdata
- pizza.forcefield.parameterforcefield
- pizza.region.regiondata
- 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
Subclasses
- pizza.generic.genericdata
class pipescript (s=None, name=None, printflag=False, verbose=True, verbosity=None)
-
pipescript: A Class for Managing Script Pipelines
The
pipescript
class stores scripts in a pipeline where multiple scripts, script objects, or script object groups can be combined and executed sequentially. Scripts in the pipeline are executed using the pipe (|
) operator, allowing for dynamic control over execution order, script concatenation, and variable management.Key Features:
- Pipeline Construction: Create pipelines of scripts, combining multiple
script objects,
script
,scriptobject
, orscriptobjectgroup
instances. The pipe operator (|
) is overloaded to concatenate scripts. - Sequential Execution: Execute all scripts in the pipeline in the order they were added, with support for reordering, selective execution, and clearing of individual steps.
- User and Definition Spaces: Manage local and global user-defined variables
(
USER
space) and static definitions for each script in the pipeline. Global definitions apply to all scripts in the pipeline, while local variables apply to specific steps. - Flexible Script Handling: Indexing, slicing, reordering, and renaming scripts in the pipeline are supported. Scripts can be accessed, replaced, and modified like array elements.
Practical Use Cases:
- LAMMPS Script Automation: Automate the generation of multi-step simulation scripts for LAMMPS, combining different simulation setups into a single pipeline.
- Script Management: Combine and manage multiple scripts, tracking user variables and ensuring that execution order can be adjusted easily.
- Advanced Script Execution: Perform partial pipeline execution, reorder steps, or clear completed steps while maintaining the original pipeline structure.
Methods:
init(self, s=None): Initializes a new
pipescript
object, optionally starting with a script or script-like object (script
,scriptobject
,scriptobjectgroup
).setUSER(self, idx, key, value): Set a user-defined variable (
USER
) for the script at the specified index.getUSER(self, idx, key): Get the value of a user-defined variable (
USER
) for the script at the specified index.clear(self, idx=None): Clear the execution status of scripts in the pipeline, allowing them to be executed again.
do(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Execute the pipeline or a subset of the pipeline, generating a combined LAMMPS-compatible script.
script(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Generate the final LAMMPS script from the pipeline or a subset of the pipeline.
rename(self, name="", idx=None): Rename the scripts in the pipeline, assigning new names to specific indices or all scripts.
write(self, file, printflag=True, verbosity=2, verbose=None): Write the generated script to a file.
dscript(self, verbose=None, **USER) Convert the current pipescript into a dscript object
Static Methods:
join(liste): Combine a list of
script
andpipescript
objects into a single pipeline.Additional Features:
- Indexing and Slicing: Use array-like indexing (
p[0]
,p[1:3]
) to access and manipulate scripts in the pipeline. - Deep Copy Support: The pipeline supports deep copying, preserving the entire pipeline structure and its scripts.
- Verbose and Print Options: Control verbosity and printing behavior for generated scripts, allowing for detailed output or minimal script generation.
Original Content:
The
pipescript
class supports a variety of pipeline operations, including: - Sequential execution withcmd = p.do()
. - Reordering pipelines withp[[2, 0, 1]]
. - Deleting steps withp[[0, 1]] = []
. - Accessing local and global user space variables viap.USER[idx].var
andp.scripts[idx].USER.var
. - Managing static definitions for each script in the pipeline. - Example usage:p = pipescript() p | i p = G | c | g | d | b | i | t | d | s | r p.rename(["G", "c", "g", "d", "b", "i", "t", "d", "s", "r"]) cmd = p.do([0, 1, 4, 7]) sp = p.script([0, 1, 4, 7])
- Scripts in the pipeline are executed sequentially, and definitions propagate from left to right. TheUSER
space andDEFINITIONS
are managed separately for each script in the pipeline.Overview
Pipescript class stores scripts in pipelines By assuming: s0, s1, s2... scripts, scriptobject or scriptobjectgroup p = s0 | s1 | s2 generates a pipe script Example of pipeline: ------------:---------------------------------------- [-] 00: G with (0>>0>>19) DEFINITIONS [-] 01: c with (0>>0>>10) DEFINITIONS [-] 02: g with (0>>0>>26) DEFINITIONS [-] 03: d with (0>>0>>19) DEFINITIONS [-] 04: b with (0>>0>>28) DEFINITIONS [-] 05: i with (0>>0>>49) DEFINITIONS [-] 06: t with (0>>0>>2) DEFINITIONS [-] 07: d with (0>>0>>19) DEFINITIONS [-] 08: s with (0>>0>>1) DEFINITIONS [-] 09: r with (0>>0>>20) DEFINITIONS ------------:---------------------------------------- Out[35]: pipescript containing 11 scripts with 8 executed[*] note: XX>>YY>>ZZ represents the number of stored variables and the direction of propagation (inheritance from left) XX: number of definitions in the pipeline USER space YY: number of definitions in the script instance (frozen in the pipeline) ZZ: number of definitions in the script (frozen space) pipelines are executed sequentially (i.e. parameters can be multivalued) cmd = p.do() fullscript = p.script() pipelines are indexed cmd = p[[0,2]].do() cmd = p[0:2].do() cmd = p.do([0,2]) pipelines can be reordered q = p[[2,0,1]] steps can be deleted p[[0,1]] = [] clear all executions with p.clear() p.clear(idx=1,2) local USER space can be accessed via (affects only the considered step) p.USER[0].a = 1 p.USER[0].b = [1 2] p.USER[0].c = "$ hello world" global USER space can accessed via (affects all steps onward) p.scripts[0].USER.a = 10 p.scripts[0].USER.b = [10 20] p.scripts[0].USER.c = "$ bye bye" static definitions p.scripts[0].DEFINITIONS steps can be renamed with the method rename() syntaxes are à la Matlab: p = pipescript() p | i p = collection | G p[0] q = p | p q[0] = [] p[0:1] = q[0:1] p = G | c | g | d | b | i | t | d | s | r p.rename(["G","c","g","d","b","i","t","d","s","r"]) cmd = p.do([0,1,4,7]) sp = p.script([0,1,4,7]) r = collection | p join joins a list (static method) p = pipescript.join([p1,p2,s3,s4]) Pending: mechanism to store LAMMPS results (dump3) in the pipeline
constructor
Expand source code
class pipescript: """ pipescript: A Class for Managing Script Pipelines The `pipescript` class stores scripts in a pipeline where multiple scripts, script objects, or script object groups can be combined and executed sequentially. Scripts in the pipeline are executed using the pipe (`|`) operator, allowing for dynamic control over execution order, script concatenation, and variable management. Key Features: ------------- - **Pipeline Construction**: Create pipelines of scripts, combining multiple script objects, `script`, `scriptobject`, or `scriptobjectgroup` instances. The pipe operator (`|`) is overloaded to concatenate scripts. - **Sequential Execution**: Execute all scripts in the pipeline in the order they were added, with support for reordering, selective execution, and clearing of individual steps. - **User and Definition Spaces**: Manage local and global user-defined variables (`USER` space) and static definitions for each script in the pipeline. Global definitions apply to all scripts in the pipeline, while local variables apply to specific steps. - **Flexible Script Handling**: Indexing, slicing, reordering, and renaming scripts in the pipeline are supported. Scripts can be accessed, replaced, and modified like array elements. Practical Use Cases: -------------------- - **LAMMPS Script Automation**: Automate the generation of multi-step simulation scripts for LAMMPS, combining different simulation setups into a single pipeline. - **Script Management**: Combine and manage multiple scripts, tracking user variables and ensuring that execution order can be adjusted easily. - **Advanced Script Execution**: Perform partial pipeline execution, reorder steps, or clear completed steps while maintaining the original pipeline structure. Methods: -------- __init__(self, s=None): Initializes a new `pipescript` object, optionally starting with a script or script-like object (`script`, `scriptobject`, `scriptobjectgroup`). setUSER(self, idx, key, value): Set a user-defined variable (`USER`) for the script at the specified index. getUSER(self, idx, key): Get the value of a user-defined variable (`USER`) for the script at the specified index. clear(self, idx=None): Clear the execution status of scripts in the pipeline, allowing them to be executed again. do(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Execute the pipeline or a subset of the pipeline, generating a combined LAMMPS-compatible script. script(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Generate the final LAMMPS script from the pipeline or a subset of the pipeline. rename(self, name="", idx=None): Rename the scripts in the pipeline, assigning new names to specific indices or all scripts. write(self, file, printflag=True, verbosity=2, verbose=None): Write the generated script to a file. dscript(self, verbose=None, **USER) Convert the current pipescript into a dscript object Static Methods: --------------- join(liste): Combine a list of `script` and `pipescript` objects into a single pipeline. Additional Features: -------------------- - **Indexing and Slicing**: Use array-like indexing (`p[0]`, `p[1:3]`) to access and manipulate scripts in the pipeline. - **Deep Copy Support**: The pipeline supports deep copying, preserving the entire pipeline structure and its scripts. - **Verbose and Print Options**: Control verbosity and printing behavior for generated scripts, allowing for detailed output or minimal script generation. Original Content: ----------------- The `pipescript` class supports a variety of pipeline operations, including: - Sequential execution with `cmd = p.do()`. - Reordering pipelines with `p[[2, 0, 1]]`. - Deleting steps with `p[[0, 1]] = []`. - Accessing local and global user space variables via `p.USER[idx].var` and `p.scripts[idx].USER.var`. - Managing static definitions for each script in the pipeline. - Example usage: ``` p = pipescript() p | i p = G | c | g | d | b | i | t | d | s | r p.rename(["G", "c", "g", "d", "b", "i", "t", "d", "s", "r"]) cmd = p.do([0, 1, 4, 7]) sp = p.script([0, 1, 4, 7]) ``` - Scripts in the pipeline are executed sequentially, and definitions propagate from left to right. The `USER` space and `DEFINITIONS` are managed separately for each script in the pipeline. OVERVIEW ----------------- Pipescript class stores scripts in pipelines By assuming: s0, s1, s2... scripts, scriptobject or scriptobjectgroup p = s0 | s1 | s2 generates a pipe script Example of pipeline: ------------:---------------------------------------- [-] 00: G with (0>>0>>19) DEFINITIONS [-] 01: c with (0>>0>>10) DEFINITIONS [-] 02: g with (0>>0>>26) DEFINITIONS [-] 03: d with (0>>0>>19) DEFINITIONS [-] 04: b with (0>>0>>28) DEFINITIONS [-] 05: i with (0>>0>>49) DEFINITIONS [-] 06: t with (0>>0>>2) DEFINITIONS [-] 07: d with (0>>0>>19) DEFINITIONS [-] 08: s with (0>>0>>1) DEFINITIONS [-] 09: r with (0>>0>>20) DEFINITIONS ------------:---------------------------------------- Out[35]: pipescript containing 11 scripts with 8 executed[*] note: XX>>YY>>ZZ represents the number of stored variables and the direction of propagation (inheritance from left) XX: number of definitions in the pipeline USER space YY: number of definitions in the script instance (frozen in the pipeline) ZZ: number of definitions in the script (frozen space) pipelines are executed sequentially (i.e. parameters can be multivalued) cmd = p.do() fullscript = p.script() pipelines are indexed cmd = p[[0,2]].do() cmd = p[0:2].do() cmd = p.do([0,2]) pipelines can be reordered q = p[[2,0,1]] steps can be deleted p[[0,1]] = [] clear all executions with p.clear() p.clear(idx=1,2) local USER space can be accessed via (affects only the considered step) p.USER[0].a = 1 p.USER[0].b = [1 2] p.USER[0].c = "$ hello world" global USER space can accessed via (affects all steps onward) p.scripts[0].USER.a = 10 p.scripts[0].USER.b = [10 20] p.scripts[0].USER.c = "$ bye bye" static definitions p.scripts[0].DEFINITIONS steps can be renamed with the method rename() syntaxes are à la Matlab: p = pipescript() p | i p = collection | G p[0] q = p | p q[0] = [] p[0:1] = q[0:1] p = G | c | g | d | b | i | t | d | s | r p.rename(["G","c","g","d","b","i","t","d","s","r"]) cmd = p.do([0,1,4,7]) sp = p.script([0,1,4,7]) r = collection | p join joins a list (static method) p = pipescript.join([p1,p2,s3,s4]) Pending: mechanism to store LAMMPS results (dump3) in the pipeline """ def __init__(self,s=None, name=None, printflag=False, verbose=True, verbosity = None): """ constructor """ self.globalscript = None self.listscript = [] self.listUSER = [] self.printflag = printflag self.verbose = verbose if verbosity is None else verbosity>0 self.verbosity = 0 if not verbose else verbosity self.cmd = "" if isinstance(s,script): self.listscript = [duplicate(s)] self.listUSER = [scriptdata()] elif isinstance(s,scriptobject): self.listscript = [scriptobjectgroup(s).script] self.listUSER = [scriptdata()] elif isinstance(s,scriptobjectgroup): self.listscript = [s.script] self.listUSER = [scriptdata()] else: ValueError("the argument should be a scriptobject or scriptobjectgroup") if s != None: self.name = [str(s)] self.executed = [False] else: self.name = [] self.executed = [] def setUSER(self,idx,key,value): """ setUSER sets USER variables setUSER(idx,varname,varvalue) """ if isinstance(idx,int) and (idx>=0) and (idx<self.n): self.listUSER[idx].setattr(key,value) else: raise IndexError(f"the index in the pipe should be comprised between 0 and {len(self)-1}") def getUSER(self,idx,key): """ getUSER get USER variable getUSER(idx,varname) """ if isinstance(idx,int) and (idx>=0) and (idx<self.n): self.listUSER[idx].getattr(key) else: raise IndexError(f"the index in the pipe should be comprised between 0 and {len(self)-1}") @property def USER(self): """ p.USER[idx].var returns the value of the USER variable var p.USER[idx].var = val assigns the value val to the USER variable var """ return self.listUSER # override listuser @property def scripts(self): """ p.scripts[idx].USER.var returns the value of the USER variable var p.scripts[idx].USER.var = val assigns the value val to the USER variable var """ return self.listscript # override listuser def __add__(self,s): """ overload + as pipe with copy """ if isinstance(s,pipescript): dup = deepduplicate(self) return dup | s # + or | are synonyms else: raise TypeError("The operand should be a pipescript") def __iadd__(self,s): """ overload += as pipe without copy """ if isinstance(s,pipescript): return self | s # + or | are synonyms else: raise TypeError("The operand should be a pipescript") def __mul__(self,ntimes): """ overload * as multiple pipes with copy """ if isinstance(self,pipescript): res = deepduplicate(self) if ntimes>1: for n in range(1,ntimes): res += self return res else: raise TypeError("The operand should be a pipescript") def __or__(self, s): """ Overload | pipe operator in pipescript """ leftarg = deepduplicate(self) # Make a deep copy of the current object # Local import only when dscript type needs to be checked from pizza.dscript import dscript from pizza.group import group, groupcollection from pizza.region import region # Convert rightarg to pipescript if needed if isinstance(s, dscript): rightarg = s.pipescript(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) # Convert the dscript object to a pipescript native = False elif isinstance(s,script): rightarg = pipescript(s,printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) # Convert the script-like object into a pipescript native = False elif isinstance(s,(scriptobject,scriptobjectgroup)): rightarg = pipescript(s,printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) # Convert the script-like object into a pipescript native = False elif isinstance(s, group): stmp = s.script(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) rightarg = pipescript(stmp,printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) # Convert the script-like object into a pipescript native = False elif isinstance(s, groupcollection): stmp = s.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) rightarg = pipescript(stmp,printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) # Convert the script-like object into a pipescript native = False elif isinstance(s, region): rightarg = s.pipescript(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) # Convert the script-like object into a pipescript native = False elif isinstance(s,pipescript): rightarg = s native = True else: raise TypeError(f"The operand should be a pipescript, dscript, script, scriptobject, scriptobjectgroup, group or groupcollection not {type(s)}") # Native piping if native: leftarg.listscript = leftarg.listscript + rightarg.listscript leftarg.listUSER = leftarg.listUSER + rightarg.listUSER leftarg.name = leftarg.name + rightarg.name for i in range(len(rightarg)): rightarg.executed[i] = False leftarg.executed = leftarg.executed + rightarg.executed return leftarg # Piping for non-native objects (dscript or script-like objects) else: # Loop through all items in rightarg and concatenate them for i in range(rightarg.n): leftarg.listscript.append(rightarg.listscript[i]) leftarg.listUSER.append(rightarg.listUSER[i]) leftarg.name.append(rightarg.name[i]) leftarg.executed.append(False) return leftarg def __str__(self): """ string representation """ return f"pipescript containing {self.n} scripts with {self.nrun} executed[*]" def __repr__(self): """ display method """ line = " "+"-"*12+":"+"-"*40 if self.verbose: print("","Pipeline with %d scripts and" % self.n, "D(STATIC:GLOBAL:LOCAL) DEFINITIONS",line,sep="\n") else: print(line) for i in range(len(self)): if self.executed[i]: state = "*" else: state = "-" print("%10s" % ("[%s] %02d:" % (state,i)), self.name[i],"with D(%2d:%2d:%2d)" % ( len(self.listscript[i].DEFINITIONS), len(self.listscript[i].USER), len(self.listUSER[i]) ) ) if self.verbose: print(line,"::: notes :::","p[i], p[i:j], p[[i,j]] copy pipeline segments", "LOCAL: p.USER[i],p.USER[i].variable modify the user space of only p[i]", "GLOBAL: p.scripts[i].USER.var to modify the user space from p[i] and onwards", "STATIC: p.scripts[i].DEFINITIONS", 'p.rename(idx=range(2),name=["A","B"]), p.clear(idx=[0,3,4])', "p.script(), p.script(idx=range(5)), p[0:5].script()","",sep="\n") else: print(line) return str(self) def __len__(self): """ len() method """ return len(self.listscript) @property def n(self): """ number of scripts """ return len(self) @property def nrun(self): """ number of scripts executed continuously from origin """ n, nmax = 0, len(self) while n<nmax and self.executed[n]: n+=1 return n def __getitem__(self,idx): """ return the ith or slice element(s) of the pipe """ dup = deepduplicate(self) if isinstance(idx,slice): dup.listscript = dup.listscript[idx] dup.listUSER = dup.listUSER[idx] dup.name = dup.name[idx] dup.executed = dup.executed[idx] elif isinstance(idx,int): if idx<len(self): dup.listscript = dup.listscript[idx:idx+1] dup.listUSER = dup.listUSER[idx:idx+1] dup.name = dup.name[idx:idx+1] dup.executed = dup.executed[idx:idx+1] else: raise IndexError(f"the index in the pipe should be comprised between 0 and {len(self)-1}") elif isinstance(idx,list): dup.listscript = picker(dup.listscript,idx) dup.listUSER = picker(dup.listUSER,idx) dup.name = picker(dup.name,idx) dup.executed = picker(dup.executed,idx) else: raise IndexError("the index needs to be a slice or an integer") return dup def __setitem__(self,idx,s): """ modify the ith element of the pipe p[4] = [] removes the 4th element p[4:7] = [] removes the elements from position 4 to 6 p[2:4] = p[0:2] copy the elements 0 and 1 in positions 2 and 3 p[[3,4]]=p[0] """ if isinstance(s,(script,scriptobject,scriptobjectgroup)): dup = pipescript(s) elif isinstance(s,pipescript): dup = s elif s==[]: dup = [] else: raise ValueError("the value must be a pipescript, script, scriptobject, scriptobjectgroup") if len(s)<1: # remove (delete) if isinstance(idx,slice) or idx<len(self): del self.listscript[idx] del self.listUSER[idx] del self.name[idx] del self.executed[idx] else: raise IndexError("the index must be a slice or an integer") elif len(s)==1: # scalar if isinstance(idx,int): if idx<len(self): self.listscript[idx] = dup.listscript[0] self.listUSER[idx] = dup.listUSER[0] self.name[idx] = dup.name[0] self.executed[idx] = False elif idx==len(self): self.listscript.append(dup.listscript[0]) self.listUSER.append(dup.listUSER[0]) self.name.append(dup.name[0]) self.executed.append(False) else: raise IndexError(f"the index must be ranged between 0 and {self.n}") elif isinstance(idx,list): for i in range(len(idx)): self.__setitem__(idx[i], s) # call as a scalar elif isinstance(idx,slice): for i in range(*idx.indices(len(self)+1)): self.__setitem__(i, s) else: raise IndexError("unrocognized index value, the index should be an integer or a slice") else: # many values if isinstance(idx,list): # list call à la Matlab if len(idx)==len(s): for i in range(len(s)): self.__setitem__(idx[i], s[i]) # call as a scalar else: raise IndexError(f"the number of indices {len(list)} does not match the number of values {len(s)}") elif isinstance(idx,slice): ilist = list(range(*idx.indices(len(self)+len(s)))) self.__setitem__(ilist, s) # call as a list else: raise IndexError("unrocognized index value, the index should be an integer or a slice") def rename(self,name="",idx=None): """ rename scripts in the pipe p.rename(idx=[0,2,3],name=["A","B","C"]) """ if isinstance(name,list): if len(name)==len(self) and idx==None: self.name = name elif len(name) == len(idx): for i in range(len(idx)): self.rename(name[i],idx[i]) else: IndexError(f"the number of indices {len(idx)} does not match the number of names {len(name)}") elif idx !=None and idx<len(self) and name!="": self.name[idx] = name else: raise ValueError("provide a non empty name and valid index") def clear(self,idx=None): if len(self)>0: if idx==None: for i in range(len(self)): self.clear(i) else: if isinstance(idx,(range,list)): for i in idx: self.clear(idx=i) elif isinstance(idx,int) and idx<len(self): self.executed[idx] = False else: raise IndexError(f"the index should be ranged between 0 and {self.n-1}") if not self.executed[0]: self.globalscript = None self.cmd = "" def do(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): """ Execute the pipeline or a part of the pipeline and generate the LAMMPS script. Parameters: idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. printflag (bool, optional): Whether to print the script for each step. Default is True. verbosity (int, optional): Level of verbosity for the output. verbose (bool, optional): Override for verbosity. If False, sets verbosity to 0. forced (bool, optional): If True, forces the pipeline to regenerate all scripts. Returns: str: Combined LAMMPS script for the specified pipeline steps. Execute the pipeline or a part of the pipeline and generate the LAMMPS script. This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level. Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the >> operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string. Example Usage: -------------- >>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0) Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range. Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do". """ verbosity = 0 if verbose is False else verbosity if len(self) == 0: return "# empty pipe - nothing to do" # Check if not all steps are executed or if there are gaps not_all_executed = not all(self.executed[:self.nrun]) # Check up to the last executed step # Determine pipeline range total_steps = len(self) if self.globalscript is None or forced or not_all_executed: start = 0 self.cmd = "" else: start = self.nrun self.cmd = self.cmd.rstrip("\n") + "\n\n" if idx is None: idx = range(start, total_steps) if isinstance(idx, int): idx = [idx] if isinstance(idx, range): idx = list(idx) idx = [i % total_steps for i in idx] start, stop = min(idx), max(idx) # Prevent re-executing already completed steps if not forced: idx = [step for step in idx if not self.executed[step]] # Execute pipeline steps for step in idx: step_wrapped = step % total_steps # Combine scripts if step_wrapped == 0: self.globalscript = self.listscript[step_wrapped] else: self.globalscript = self.globalscript >> self.listscript[step_wrapped] # Step label step_name = f"<{self.name[step]}>" step_label = f"# [{step+1} of {total_steps} from {start}:{stop}] {step_name}" # Get script content for the step step_output = self.globalscript.do(printflag=printflag, verbose=verbosity > 1) # Add comments and content if step_output.strip(): self.cmd += f"{step_label}\n{step_output.strip()}\n\n" elif verbosity > 0: self.cmd += f"{step_label} :: no content\n\n" # Update USER definitions self.globalscript.USER += self.listUSER[step] self.executed[step] = True # Clean up and finalize script self.cmd = self.cmd.replace("\\n", "\n").strip() # Remove literal \\n and extra spaces self.cmd += "\n" # Ensure trailing newline return remove_comments(self.cmd) if verbosity == 0 else self.cmd def do_legacy(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): """ Execute the pipeline or a part of the pipeline and generate the LAMMPS script. This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level. Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated Returns: - str: The combined LAMMPS script generated from the specified steps of the pipeline. Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the >> operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string. Example Usage: -------------- >>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0) Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range. Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do". """ verbosity = 0 if verbose is False else verbosity if len(self)>0: # ranges ntot = len(self) stop = ntot-1 if (self.globalscript == None) or (self.globalscript == []) or not self.executed[0] or forced: start = 0 self.cmd = "" else: start = self.nrun if start>stop: return self.cmd if idx is None: idx = range(start,stop+1) if isinstance(idx,range): idx = list(idx) if isinstance(idx,int): idx = [idx] start,stop = min(idx),max(idx) # do for i in idx: j = i % ntot if j==0: self.globalscript = self.listscript[j] else: self.globalscript = self.globalscript >> self.listscript[j] name = " "+self.name[i]+" " if verbosity>0: self.cmd += "\n\n#\t --- run step [%d/%d] --- [%s] %20s\n" % \ (j,ntot-1,name.center(50,"="),"pipeline [%d]-->[%d]" %(start,stop)) else: self.cmd +="\n" self.globalscript.USER = self.globalscript.USER + self.listUSER[j] self.cmd += self.globalscript.do(printflag=printflag,verbose=verbosity>1) self.executed[i] = True self.cmd = self.cmd.replace("\\n", "\n") # remove literal \\n if any (dscript.save add \\n) return remove_comments(self.cmd) if verbosity==0 else self.cmd else: return "# empty pipe - nothing to do" def script(self,idx=None, printflag=True, verbosity=2, verbose=None, forced=False, style=4): """ script the pipeline or parts of the pipeline s = p.script() s = p.script([0,2]) Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `4` (thick outer frame). """ printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity=0 if verbose is False else verbosity s = script(printflag=printflag, verbose=verbosity>0) s.name = "pipescript" s.description = "pipeline with %d scripts" % len(self) if len(self)>1: s.userid = self.name[0]+"->"+self.name[-1] elif len(self)==1: s.userid = self.name[0] else: s.userid = "empty pipeline" s.TEMPLATE = self.header(verbosity=verbosity, style=style) + "\n" +\ self.do(idx, printflag=printflag, verbosity=verbosity, verbose=verbose, forced=forced) s.DEFINITIONS = duplicate(self.globalscript.DEFINITIONS) s.USER = duplicate(self.globalscript.USER) return s @staticmethod def join(liste): """ join a combination scripts and pipescripts within a pipescript p = pipescript.join([s1,s2,p3,p4,p5...]) """ if not isinstance(liste,list): raise ValueError("the argument should be a list") ok = True for i in range(len(liste)): ok = ok and isinstance(liste[i],(script,pipescript)) if not ok: raise ValueError(f"the entry [{i}] should be a script or pipescript") if len(liste)<1: return liste out = liste[0] for i in range(1,len(liste)): out = out | liste[i] return out # Note that it was not the original intent to copy pipescripts 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, deepduplicate(v, memo)) return copie # write file def write(self, file, printflag=False, verbosity=2, verbose=None, overwrite=False): """ Write the combined script to a file. Parameters: file (str): The file path where the script will be saved. printflag (bool): Flag to enable/disable printing of details. verbosity (int): Level of verbosity for the script generation. verbose (bool or None): If True, enables verbose mode; if None, defaults to the instance's verbosity. overwrite (bool): Whether to overwrite the file if it already exists. Default is False. Returns: str: The full absolute path of the file written. Raises: FileExistsError: If the file already exists and overwrite is False. Notes: - This method combines the individual scripts within the `pipescript` object and saves the resulting script to the specified file. - If `overwrite` is False and the file exists, an error is raised. - If `verbose` is True and the file is overwritten, a warning is displayed. """ # Generate the combined script myscript = self.script(printflag=printflag, verbosity=verbosity, verbose=verbose, forced=True) # Call the script's write method with the overwrite parameter return myscript.write(file, printflag=printflag, verbose=verbose, overwrite=overwrite) def dscript(self, name=None, printflag=None, verbose=None, verbosity=None, **USER): """ Convert the current pipescript object to a dscript object. This method merges the STATIC, GLOBAL, and LOCAL variable spaces from each step in the pipescript into a single dynamic script per step in the dscript. Each step in the pipescript is transformed into a dynamic script in the dscript, where variable spaces are combined using the following order: 1. STATIC: Definitions specific to each script in the pipescript. 2. GLOBAL: User variables shared across steps from a specific point onwards. 3. LOCAL: User variables for each individual step. Parameters: ----------- verbose : bool, optional Controls verbosity of the dynamic scripts in the resulting dscript object. If None, the verbosity setting of the pipescript will be used. **USER : scriptobjectdata(), optional Additional user-defined variables that can override existing static variables in the dscript object or be added to it. Returns: -------- outd : dscript A dscript object that contains all steps of the pipescript as dynamic scripts. Each step from the pipescript is added as a dynamic script with the same content and combined variable spaces. """ # Local imports from pizza.dscript import dscript, ScriptTemplate, lambdaScriptdata # verbosity printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity # Adjust name if name is None: if isinstance(self.name, str): name = self.name elif isinstance(self.name, list): name = ( self.name[0] if len(self.name) == 1 else self.name[0] + "..." + self.name[-1] ) # Create the dscript container with the pipescript name as the userid outd = dscript(userid=name, verbose=self.verbose, **USER) # Initialize static merged definitions staticmerged_definitions = lambdaScriptdata() # Track used variables per step step_used_variables = [] # Loop over each step in the pipescript for i, script in enumerate(self.listscript): # Merge STATIC, GLOBAL, and LOCAL variables for the current step static_vars = self.listUSER[i] # script.DEFINITIONS global_vars = script.DEFINITIONS # self.scripts[i].USER local_vars = script.USER # self.USER[i] refreshed_globalvars = static_vars + global_vars # Detect variables used in the current template used_variables = set(script.detect_variables()) step_used_variables.append(used_variables) # Track used variables for this step # Copy all current variables to local_static_updates and remove matching variables from staticmerged_definitions local_static_updates = lambdaScriptdata(**local_vars) for var, value in refreshed_globalvars.items(): if var in staticmerged_definitions: if (getattr(staticmerged_definitions, var) != value) and (var not in local_vars): setattr(local_static_updates, var, value) else: setattr(staticmerged_definitions, var, value) # Create the dynamic script for this step using the method in dscript key_name = i # Use the index 'i' as the key in TEMPLATE content = script.TEMPLATE # Use the helper method in dscript to add this dynamic script outd.add_dynamic_script( key=key_name, content=content, definitions = lambdaScriptdata(**local_static_updates), verbose=self.verbose if verbose is None else verbose, userid=self.name[i] ) # Set eval=True only if variables are detected in the template if outd.TEMPLATE[key_name].detect_variables(): outd.TEMPLATE[key_name].eval = True # Compute the union of all used variables across all steps global_used_variables = set().union(*step_used_variables) # Filter staticmerged_definitions to keep only variables that are used filtered_definitions = { var: value for var, value in staticmerged_definitions.items() if var in global_used_variables } # Assign the filtered definitions along with USER variables to outd.DEFINITIONS outd.DEFINITIONS = lambdaScriptdata(**filtered_definitions) return outd def header(self, verbose=True,verbosity=None, style=4): """ Generate a formatted header for the pipescript file. Parameters: verbosity (bool, optional): If specified, overrides the instance's `verbose` setting. Defaults to the instance's `verbose`. style (int from 1 to 6, optional): ASCII style to frame the header (default=4) Returns: str: A formatted string representing the pipescript object. Returns an empty string if verbosity is False. The header includes: - Total number of scripts in the pipeline. - The verbosity setting. - The range of scripts from the first to the last script. - All enclosed within an ASCII frame that adjusts to the content. """ verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity if not verbosity: return "" # Prepare the header content lines = [ f"PIPESCRIPT with {self.n} scripts | Verbosity: {verbosity}", "", f"From: <{str(self.scripts[0])}> To: <{str(self.scripts[-1])}>", ] # Use the shared method to format the header return frame_header(lines,style=style)
Static methods
def join(liste)
-
join a combination scripts and pipescripts within a pipescript p = pipescript.join([s1,s2,p3,p4,p5…])
Expand source code
@staticmethod def join(liste): """ join a combination scripts and pipescripts within a pipescript p = pipescript.join([s1,s2,p3,p4,p5...]) """ if not isinstance(liste,list): raise ValueError("the argument should be a list") ok = True for i in range(len(liste)): ok = ok and isinstance(liste[i],(script,pipescript)) if not ok: raise ValueError(f"the entry [{i}] should be a script or pipescript") if len(liste)<1: return liste out = liste[0] for i in range(1,len(liste)): out = out | liste[i] return out
Instance variables
var USER
-
p.USER[idx].var returns the value of the USER variable var p.USER[idx].var = val assigns the value val to the USER variable var
Expand source code
@property def USER(self): """ p.USER[idx].var returns the value of the USER variable var p.USER[idx].var = val assigns the value val to the USER variable var """ return self.listUSER # override listuser
var n
-
number of scripts
Expand source code
@property def n(self): """ number of scripts """ return len(self)
var nrun
-
number of scripts executed continuously from origin
Expand source code
@property def nrun(self): """ number of scripts executed continuously from origin """ n, nmax = 0, len(self) while n<nmax and self.executed[n]: n+=1 return n
var scripts
-
p.scripts[idx].USER.var returns the value of the USER variable var p.scripts[idx].USER.var = val assigns the value val to the USER variable var
Expand source code
@property def scripts(self): """ p.scripts[idx].USER.var returns the value of the USER variable var p.scripts[idx].USER.var = val assigns the value val to the USER variable var """ return self.listscript # override listuser
Methods
def clear(self, idx=None)
-
Expand source code
def clear(self,idx=None): if len(self)>0: if idx==None: for i in range(len(self)): self.clear(i) else: if isinstance(idx,(range,list)): for i in idx: self.clear(idx=i) elif isinstance(idx,int) and idx<len(self): self.executed[idx] = False else: raise IndexError(f"the index should be ranged between 0 and {self.n-1}") if not self.executed[0]: self.globalscript = None self.cmd = ""
def do(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False)
-
Execute the pipeline or a part of the pipeline and generate the LAMMPS script.
Parameters
idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. printflag (bool, optional): Whether to print the script for each step. Default is True. verbosity (int, optional): Level of verbosity for the output. verbose (bool, optional): Override for verbosity. If False, sets verbosity to 0. forced (bool, optional): If True, forces the pipeline to regenerate all scripts.
Returns
str
- Combined LAMMPS script for the specified pipeline steps.
Execute the pipeline or a part of the pipeline and generate the LAMMPS script.
This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level. Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the >> operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string.
Example Usage:
>>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0) Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range. Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do".
Expand source code
def do(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): """ Execute the pipeline or a part of the pipeline and generate the LAMMPS script. Parameters: idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. printflag (bool, optional): Whether to print the script for each step. Default is True. verbosity (int, optional): Level of verbosity for the output. verbose (bool, optional): Override for verbosity. If False, sets verbosity to 0. forced (bool, optional): If True, forces the pipeline to regenerate all scripts. Returns: str: Combined LAMMPS script for the specified pipeline steps. Execute the pipeline or a part of the pipeline and generate the LAMMPS script. This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level. Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the >> operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string. Example Usage: -------------- >>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0) Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range. Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do". """ verbosity = 0 if verbose is False else verbosity if len(self) == 0: return "# empty pipe - nothing to do" # Check if not all steps are executed or if there are gaps not_all_executed = not all(self.executed[:self.nrun]) # Check up to the last executed step # Determine pipeline range total_steps = len(self) if self.globalscript is None or forced or not_all_executed: start = 0 self.cmd = "" else: start = self.nrun self.cmd = self.cmd.rstrip("\n") + "\n\n" if idx is None: idx = range(start, total_steps) if isinstance(idx, int): idx = [idx] if isinstance(idx, range): idx = list(idx) idx = [i % total_steps for i in idx] start, stop = min(idx), max(idx) # Prevent re-executing already completed steps if not forced: idx = [step for step in idx if not self.executed[step]] # Execute pipeline steps for step in idx: step_wrapped = step % total_steps # Combine scripts if step_wrapped == 0: self.globalscript = self.listscript[step_wrapped] else: self.globalscript = self.globalscript >> self.listscript[step_wrapped] # Step label step_name = f"<{self.name[step]}>" step_label = f"# [{step+1} of {total_steps} from {start}:{stop}] {step_name}" # Get script content for the step step_output = self.globalscript.do(printflag=printflag, verbose=verbosity > 1) # Add comments and content if step_output.strip(): self.cmd += f"{step_label}\n{step_output.strip()}\n\n" elif verbosity > 0: self.cmd += f"{step_label} :: no content\n\n" # Update USER definitions self.globalscript.USER += self.listUSER[step] self.executed[step] = True # Clean up and finalize script self.cmd = self.cmd.replace("\\n", "\n").strip() # Remove literal \\n and extra spaces self.cmd += "\n" # Ensure trailing newline return remove_comments(self.cmd) if verbosity == 0 else self.cmd
def do_legacy(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False)
-
Execute the pipeline or a part of the pipeline and generate the LAMMPS script.
This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level.
Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated
Returns: - str: The combined LAMMPS script generated from the specified steps of the pipeline.
Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the
operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string.
Example Usage:
>>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0)
Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range.
Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do".
Expand source code
def do_legacy(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): """ Execute the pipeline or a part of the pipeline and generate the LAMMPS script. This method processes the pipeline of script objects, executing each step to generate a combined LAMMPS-compatible script. The execution can be done for the entire pipeline or for a specified range of indices. The generated script can include comments and metadata based on the verbosity level. Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated Returns: - str: The combined LAMMPS script generated from the specified steps of the pipeline. Method Workflow: - The method first checks if there are any script objects in the pipeline. If the pipeline is empty, it returns a message indicating that there is nothing to execute. - It determines the start and stop indices for the range of steps to execute. If idx is not provided, it defaults to executing all steps from the last executed position. - If a specific index or list of indices is provided, it executes only those steps. - The pipeline steps are executed in order, combining the scripts using the >> operator for sequential execution. - The generated script includes comments indicating the current run step and pipeline range, based on the specified verbosity level. - The final combined script is returned as a string. Example Usage: -------------- >>> p = pipescript() >>> # Execute the entire pipeline >>> full_script = p.do() >>> # Execute steps 0 and 2 only >>> partial_script = p.do([0, 2]) >>> # Execute step 1 with minimal verbosity >>> minimal_script = p.do(idx=1, verbosity=0) Notes: - The method uses modular arithmetic to handle index wrapping, allowing for cyclic execution of pipeline steps. - If the pipeline is empty, the method returns the string "# empty pipe - nothing to do". - The globalscript is initialized or updated with each step's script, and the USER definitions are accumulated across the steps. - The command string self.cmd is updated with the generated script for each step in the specified range. Raises: - None: The method does not raise exceptions directly, but an empty pipeline will result in the return of "# empty pipe - nothing to do". """ verbosity = 0 if verbose is False else verbosity if len(self)>0: # ranges ntot = len(self) stop = ntot-1 if (self.globalscript == None) or (self.globalscript == []) or not self.executed[0] or forced: start = 0 self.cmd = "" else: start = self.nrun if start>stop: return self.cmd if idx is None: idx = range(start,stop+1) if isinstance(idx,range): idx = list(idx) if isinstance(idx,int): idx = [idx] start,stop = min(idx),max(idx) # do for i in idx: j = i % ntot if j==0: self.globalscript = self.listscript[j] else: self.globalscript = self.globalscript >> self.listscript[j] name = " "+self.name[i]+" " if verbosity>0: self.cmd += "\n\n#\t --- run step [%d/%d] --- [%s] %20s\n" % \ (j,ntot-1,name.center(50,"="),"pipeline [%d]-->[%d]" %(start,stop)) else: self.cmd +="\n" self.globalscript.USER = self.globalscript.USER + self.listUSER[j] self.cmd += self.globalscript.do(printflag=printflag,verbose=verbosity>1) self.executed[i] = True self.cmd = self.cmd.replace("\\n", "\n") # remove literal \\n if any (dscript.save add \\n) return remove_comments(self.cmd) if verbosity==0 else self.cmd else: return "# empty pipe - nothing to do"
def dscript(self, name=None, printflag=None, verbose=None, verbosity=None, **USER)
-
Convert the current pipescript object to a dscript object.
This method merges the STATIC, GLOBAL, and LOCAL variable spaces from each step in the pipescript into a single dynamic script per step in the dscript. Each step in the pipescript is transformed into a dynamic script in the dscript, where variable spaces are combined using the following order:
- STATIC: Definitions specific to each script in the pipescript.
- GLOBAL: User variables shared across steps from a specific point onwards.
- LOCAL: User variables for each individual step.
Parameters:
verbose : bool, optional Controls verbosity of the dynamic scripts in the resulting dscript object. If None, the verbosity setting of the pipescript will be used.
**USER : scriptobjectdata(), optional Additional user-defined variables that can override existing static variables in the dscript object or be added to it.
Returns:
outd : dscript A dscript object that contains all steps of the pipescript as dynamic scripts. Each step from the pipescript is added as a dynamic script with the same content and combined variable spaces.
Expand source code
def dscript(self, name=None, printflag=None, verbose=None, verbosity=None, **USER): """ Convert the current pipescript object to a dscript object. This method merges the STATIC, GLOBAL, and LOCAL variable spaces from each step in the pipescript into a single dynamic script per step in the dscript. Each step in the pipescript is transformed into a dynamic script in the dscript, where variable spaces are combined using the following order: 1. STATIC: Definitions specific to each script in the pipescript. 2. GLOBAL: User variables shared across steps from a specific point onwards. 3. LOCAL: User variables for each individual step. Parameters: ----------- verbose : bool, optional Controls verbosity of the dynamic scripts in the resulting dscript object. If None, the verbosity setting of the pipescript will be used. **USER : scriptobjectdata(), optional Additional user-defined variables that can override existing static variables in the dscript object or be added to it. Returns: -------- outd : dscript A dscript object that contains all steps of the pipescript as dynamic scripts. Each step from the pipescript is added as a dynamic script with the same content and combined variable spaces. """ # Local imports from pizza.dscript import dscript, ScriptTemplate, lambdaScriptdata # verbosity printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity # Adjust name if name is None: if isinstance(self.name, str): name = self.name elif isinstance(self.name, list): name = ( self.name[0] if len(self.name) == 1 else self.name[0] + "..." + self.name[-1] ) # Create the dscript container with the pipescript name as the userid outd = dscript(userid=name, verbose=self.verbose, **USER) # Initialize static merged definitions staticmerged_definitions = lambdaScriptdata() # Track used variables per step step_used_variables = [] # Loop over each step in the pipescript for i, script in enumerate(self.listscript): # Merge STATIC, GLOBAL, and LOCAL variables for the current step static_vars = self.listUSER[i] # script.DEFINITIONS global_vars = script.DEFINITIONS # self.scripts[i].USER local_vars = script.USER # self.USER[i] refreshed_globalvars = static_vars + global_vars # Detect variables used in the current template used_variables = set(script.detect_variables()) step_used_variables.append(used_variables) # Track used variables for this step # Copy all current variables to local_static_updates and remove matching variables from staticmerged_definitions local_static_updates = lambdaScriptdata(**local_vars) for var, value in refreshed_globalvars.items(): if var in staticmerged_definitions: if (getattr(staticmerged_definitions, var) != value) and (var not in local_vars): setattr(local_static_updates, var, value) else: setattr(staticmerged_definitions, var, value) # Create the dynamic script for this step using the method in dscript key_name = i # Use the index 'i' as the key in TEMPLATE content = script.TEMPLATE # Use the helper method in dscript to add this dynamic script outd.add_dynamic_script( key=key_name, content=content, definitions = lambdaScriptdata(**local_static_updates), verbose=self.verbose if verbose is None else verbose, userid=self.name[i] ) # Set eval=True only if variables are detected in the template if outd.TEMPLATE[key_name].detect_variables(): outd.TEMPLATE[key_name].eval = True # Compute the union of all used variables across all steps global_used_variables = set().union(*step_used_variables) # Filter staticmerged_definitions to keep only variables that are used filtered_definitions = { var: value for var, value in staticmerged_definitions.items() if var in global_used_variables } # Assign the filtered definitions along with USER variables to outd.DEFINITIONS outd.DEFINITIONS = lambdaScriptdata(**filtered_definitions) return outd
def getUSER(self, idx, key)
-
getUSER get USER variable getUSER(idx,varname)
Expand source code
def getUSER(self,idx,key): """ getUSER get USER variable getUSER(idx,varname) """ if isinstance(idx,int) and (idx>=0) and (idx<self.n): self.listUSER[idx].getattr(key) else: raise IndexError(f"the index in the pipe should be comprised between 0 and {len(self)-1}")
def header(self, verbose=True, verbosity=None, style=4)
-
Generate a formatted header for the pipescript file.
Parameters
verbosity (bool, optional): If specified, overrides the instance's
verbose
setting. Defaults to the instance'sverbose
. style (int from 1 to 6, optional): ASCII style to frame the header (default=4)Returns
str
- A formatted string representing the pipescript object. Returns an empty string if verbosity is False.
The header includes: - Total number of scripts in the pipeline. - The verbosity setting. - The range of scripts from the first to the last script. - All enclosed within an ASCII frame that adjusts to the content.
Expand source code
def header(self, verbose=True,verbosity=None, style=4): """ Generate a formatted header for the pipescript file. Parameters: verbosity (bool, optional): If specified, overrides the instance's `verbose` setting. Defaults to the instance's `verbose`. style (int from 1 to 6, optional): ASCII style to frame the header (default=4) Returns: str: A formatted string representing the pipescript object. Returns an empty string if verbosity is False. The header includes: - Total number of scripts in the pipeline. - The verbosity setting. - The range of scripts from the first to the last script. - All enclosed within an ASCII frame that adjusts to the content. """ verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity if not verbosity: return "" # Prepare the header content lines = [ f"PIPESCRIPT with {self.n} scripts | Verbosity: {verbosity}", "", f"From: <{str(self.scripts[0])}> To: <{str(self.scripts[-1])}>", ] # Use the shared method to format the header return frame_header(lines,style=style)
def rename(self, name='', idx=None)
-
rename scripts in the pipe p.rename(idx=[0,2,3],name=["A","B","C"])
Expand source code
def rename(self,name="",idx=None): """ rename scripts in the pipe p.rename(idx=[0,2,3],name=["A","B","C"]) """ if isinstance(name,list): if len(name)==len(self) and idx==None: self.name = name elif len(name) == len(idx): for i in range(len(idx)): self.rename(name[i],idx[i]) else: IndexError(f"the number of indices {len(idx)} does not match the number of names {len(name)}") elif idx !=None and idx<len(self) and name!="": self.name[idx] = name else: raise ValueError("provide a non empty name and valid index")
def script(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False, style=4)
-
script the pipeline or parts of the pipeline s = p.script() s = p.script([0,2])
Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with
+
,-
, and|
2. Double-line frame with╔
,═
, and║
3. Rounded corners with.
,'
,-
, and|
4. Thick outer frame with#
,=
, and#
5. Box drawing characters with┌
,─
, and│
6. Minimalist dotted frame with.
,:
, and.
Default is4
(thick outer frame).Expand source code
def script(self,idx=None, printflag=True, verbosity=2, verbose=None, forced=False, style=4): """ script the pipeline or parts of the pipeline s = p.script() s = p.script([0,2]) Parameters: - idx (list, range, or int, optional): Specifies which steps of the pipeline to execute. If None, all steps from the current position to the end are executed. A list of indices can be provided to execute specific steps, or a single integer can be passed to execute a specific step. Default is None. - printflag (bool, optional): If True, the generated script for each step is printed to the console. Default is True. - verbosity (int, optional): Controls the level of detail in the generated script. - 0: Minimal output, no comments. - 1: Basic comments for run steps. - 2: Detailed comments with additional information. Default is 2. - forced (bool, optional): If True, all scripts are regenerated - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `4` (thick outer frame). """ printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity=0 if verbose is False else verbosity s = script(printflag=printflag, verbose=verbosity>0) s.name = "pipescript" s.description = "pipeline with %d scripts" % len(self) if len(self)>1: s.userid = self.name[0]+"->"+self.name[-1] elif len(self)==1: s.userid = self.name[0] else: s.userid = "empty pipeline" s.TEMPLATE = self.header(verbosity=verbosity, style=style) + "\n" +\ self.do(idx, printflag=printflag, verbosity=verbosity, verbose=verbose, forced=forced) s.DEFINITIONS = duplicate(self.globalscript.DEFINITIONS) s.USER = duplicate(self.globalscript.USER) return s
def setUSER(self, idx, key, value)
-
setUSER sets USER variables setUSER(idx,varname,varvalue)
Expand source code
def setUSER(self,idx,key,value): """ setUSER sets USER variables setUSER(idx,varname,varvalue) """ if isinstance(idx,int) and (idx>=0) and (idx<self.n): self.listUSER[idx].setattr(key,value) else: raise IndexError(f"the index in the pipe should be comprised between 0 and {len(self)-1}")
def write(self, file, printflag=False, verbosity=2, verbose=None, overwrite=False)
-
Write the combined script to a file.
Parameters
file (str): The file path where the script will be saved. printflag (bool): Flag to enable/disable printing of details. verbosity (int): Level of verbosity for the script generation. verbose (bool or None): If True, enables verbose mode; if None, defaults to the instance's verbosity. overwrite (bool): Whether to overwrite the file if it already exists. Default is False.
Returns: str: The full absolute path of the file written.
Raises
FileExistsError
- If the file already exists and overwrite is False.
Notes
- This method combines the individual scripts within the
pipescript
object and saves the resulting script to the specified file. - If
overwrite
is False and the file exists, an error is raised. - If
verbose
is True and the file is overwritten, a warning is displayed.
Expand source code
def write(self, file, printflag=False, verbosity=2, verbose=None, overwrite=False): """ Write the combined script to a file. Parameters: file (str): The file path where the script will be saved. printflag (bool): Flag to enable/disable printing of details. verbosity (int): Level of verbosity for the script generation. verbose (bool or None): If True, enables verbose mode; if None, defaults to the instance's verbosity. overwrite (bool): Whether to overwrite the file if it already exists. Default is False. Returns: str: The full absolute path of the file written. Raises: FileExistsError: If the file already exists and overwrite is False. Notes: - This method combines the individual scripts within the `pipescript` object and saves the resulting script to the specified file. - If `overwrite` is False and the file exists, an error is raised. - If `verbose` is True and the file is overwritten, a warning is displayed. """ # Generate the combined script myscript = self.script(printflag=printflag, verbosity=verbosity, verbose=verbose, forced=True) # Call the script's write method with the overwrite parameter return myscript.write(file, printflag=printflag, verbose=verbose, overwrite=overwrite)
- Pipeline Construction: Create pipelines of scripts, combining multiple
script objects,
class pstr (...)
-
Class:
pstr
A specialized string class for handling paths and filenames, derived from
struct
. Thepstr
class ensures compatibility with POSIX-style paths and provides enhanced operations for path manipulation.
Features
- Maintains POSIX-style paths.
- Automatically handles trailing slashes.
- Supports path concatenation using
/
. - Converts seamlessly back to
str
for compatibility with string methods. - Includes additional utility methods for path evaluation and formatting.
Examples
Basic Usage
a = pstr("this/is/mypath//") b = pstr("mylocalfolder/myfile.ext") c = a / b print(c) # this/is/mypath/mylocalfolder/myfile.ext
Keeping Trailing Slashes
a = pstr("this/is/mypath//") print(a) # this/is/mypath/
Path Operations
Path Concatenation
Use the
/
operator to concatenate paths:a = pstr("folder/subfolder") b = pstr("file.txt") c = a / b print(c) # folder/subfolder/file.txt
Path Evaluation
Evaluate or convert paths while preserving the
pstr
type:result = pstr.eval("some/path/afterreplacement", ispstr=True) print(result) # some/path/afterreplacement
Advanced Usage
Using String Methods
Methods like
replace()
convertpstr
back tostr
. To retain thepstr
type:new_path = pstr.eval(a.replace("mypath", "newpath"), ispstr=True) print(new_path) # this/is/newpath/
Handling POSIX Paths
The
pstr.topath()
method ensures the path remains POSIX-compliant:path = pstr("C:\Windows\Path") posix_path = path.topath() print(posix_path) # C:/Windows/Path
Overloaded Operators
Supported Operators
/
: Concatenates two paths (__truediv__
).+
: Concatenates strings as paths, resulting in apstr
object (__add__
).+=
: Adds to an existingpstr
object (__iadd__
).
Utility Methods
Method Description eval(value)
Evaluates the path or string for compatibility with pstr
.topath()
Returns the POSIX-compliant path.
Notes
Expand source code
class pstr(str): """ Class: `pstr` ============= A specialized string class for handling paths and filenames, derived from `struct`. The `pstr` class ensures compatibility with POSIX-style paths and provides enhanced operations for path manipulation. --- ### Features - Maintains POSIX-style paths. - Automatically handles trailing slashes. - Supports path concatenation using `/`. - Converts seamlessly back to `str` for compatibility with string methods. - Includes additional utility methods for path evaluation and formatting. --- ### Examples #### Basic Usage ```python a = pstr("this/is/mypath//") b = pstr("mylocalfolder/myfile.ext") c = a / b print(c) # this/is/mypath/mylocalfolder/myfile.ext ``` #### Keeping Trailing Slashes ```python a = pstr("this/is/mypath//") print(a) # this/is/mypath/ ``` --- ### Path Operations #### Path Concatenation Use the `/` operator to concatenate paths: ```python a = pstr("folder/subfolder") b = pstr("file.txt") c = a / b print(c) # folder/subfolder/file.txt ``` #### Path Evaluation Evaluate or convert paths while preserving the `pstr` type: ```python result = pstr.eval("some/path/afterreplacement", ispstr=True) print(result) # some/path/afterreplacement ``` --- ### Advanced Usage #### Using String Methods Methods like `replace()` convert `pstr` back to `str`. To retain the `pstr` type: ```python new_path = pstr.eval(a.replace("mypath", "newpath"), ispstr=True) print(new_path) # this/is/newpath/ ``` #### Handling POSIX Paths The `pstr.topath()` method ensures the path remains POSIX-compliant: ```python path = pstr("C:\\Windows\\Path") posix_path = path.topath() print(posix_path) # C:/Windows/Path ``` --- ### Overloaded Operators #### Supported Operators - `/`: Concatenates two paths (`__truediv__`). - `+`: Concatenates strings as paths, resulting in a `pstr` object (`__add__`). - `+=`: Adds to an existing `pstr` object (`__iadd__`). --- ### Utility Methods | Method | Description | |------------------|----------------------------------------------| | `eval(value)` | Evaluates the path or string for compatibility with `pstr`. | | `topath()` | Returns the POSIX-compliant path. | --- ### Notes - Use `pstr` for consistent and safe handling of file paths across different platforms. - Converts back to `str` when using non-`pstr` specific methods to ensure compatibility. """ def __repr__(self): result = self.topath() if result[-1] != "/" and self[-1] == "/": result += "/" return result def topath(self): """ return a validated path """ value = pstr(PurePath(self)) if value[-1] != "/" and self [-1]=="/": value += "/" return value @staticmethod def eval(value,ispstr=False): """ evaluate the path of it os a path """ if isinstance(value,pstr): return value.topath() elif isinstance(value,PurePath) or ispstr: return pstr(value).topath() else: return value def __truediv__(self,value): """ overload / """ operand = pstr.eval(value) result = pstr(PurePath(self) / operand) if result[-1] != "/" and operand[-1] == "/": result += "/" return result def __add__(self,value): return pstr(str(self)+value) def __iadd__(self,value): return pstr(str(self)+value)
Ancestors
- builtins.str
Static methods
def eval(value, ispstr=False)
-
evaluate the path of it os a path
Expand source code
@staticmethod def eval(value,ispstr=False): """ evaluate the path of it os a path """ if isinstance(value,pstr): return value.topath() elif isinstance(value,PurePath) or ispstr: return pstr(value).topath() else: return value
Methods
def topath(self)
-
return a validated path
Expand source code
def topath(self): """ return a validated path """ value = pstr(PurePath(self)) if value[-1] != "/" and self [-1]=="/": value += "/" return value
class region (name='region container', dimension=3, boundary=None, nbeads=1, units='', mass=1.0, volume=1.0, density=1.0, radius=1.5, contactradius=0.5, velocities=[0.0, 0.0, 0.0], forces=[0.0, 0.0, 0.0], filename='', previewfilename='', index=None, run=1, center=[0.0, 0.0, 0.0], width=10.0, height=10.0, depth=10.0, hasfixmove=False, spacefilling=False, fillingbeadtype=1, boxid='box', regionunits='lattice', separationdistance=5e-06, lattice_scale=0.8442, lattice_spacing=None, lattice_style='fcc', atom_style='smd', atom_modify=['map', 'array'], comm_modify=['vel', 'yes'], neigh_modify=['every', 10, 'delay', 0, 'check', 'yes'], newton='off', live_units='lj', live_atom_style='atomic', livepreview_options={'static': {'run': 1}, 'dynamic': {'run': 100}}, printflag=False, verbose=True, verbosity=None)
-
The
region
class represents a simulation region, centered at the origin (0, 0, 0) by default, and is characterized by its physical dimensions, properties, and boundary conditions. It supports setting up lattice structures, particle properties, and options for live previews.Attributes:
name : str, optional Name of the region (default is 'region container').
dimension : int, optional Number of spatial dimensions for the simulation (either 2 or 3, default is 3).
boundary : list of str or None, optional Boundary conditions for each dimension. If None, defaults to ["sm"] * dimension. Must be a list of length
dimension
, where "s" indicates shrink-wrapped, and "m" indicates a non-periodic boundary.nbeads : int, optional Number of beads in the region (default is 1).
units : str, optional Units for the simulation box (default is "").
Particle Properties:
mass : float, optional Mass of particles in the region (default is 1).
volume : float, optional Volume of the region (default is 1).
density : float, optional Density of the region (default is 1).
radius : float, optional Radius of the particles (default is 1.5).
contactradius : float, optional Contact radius of the particles (default is 0.5).
velocities : list of floats, optional Initial velocities of particles (default is [0, 0, 0]).
forces : list of floats, optional External forces acting on the particles (default is [0, 0, 0]).
Other Properties:
filename : str, optional Name of the output file (default is an empty string, which will auto-generate a name based on the region name).
index : int, optional Index or identifier for the region.
run : int, optional Run configuration parameter (default is 1).
Box Properties:
center : list of floats, optional Center of the simulation box for coordinate scaling (default is [0, 0, 0]).
width : float, optional Width of the region (default is 10).
height : float, optional Height of the region (default is 10).
depth : float, optional Depth of the region (default is 10).
hasfixmove : bool, optional Indicates whether the region has a fixed movement (default is False).
Spacefilling Design:
spacefilling : bool, optional Indicates whether the design is space-filling (default is False).
fillingbeadtype : int, optional Type of bead used for space filling (default is 1).
Lattice Properties:
regionunits : str, optional Defines the units of the region. Can be either "lattice" (default) or "si".
separationdistance : float, optional Separation distance between atoms in SI units (default is 5e-6).
lattice_scale : float, optional Scaling factor for the lattice, used mainly in visualization (default is 0.8442).
lattice_spacing : list or None, optional Specifies the spacing between lattice points. If None, the default spacing is used. Can be a list of [dx, dy, dz].
lattice_style : str, optional Specifies the lattice structure style (default is "fcc"). Accepts any LAMMPS valid style, e.g., "sc" for simple cubic.
Atom Properties:
atom_style : str, optional Defines the atom style for the region (default is "smd").
atom_modify : list of str, optional LAMMPS command for atom modification (default is ["map", "array"]).
comm_modify : list of str, optional LAMMPS command for communication modification (default is ["vel", "yes"]).
neigh_modify : list, optional LAMMPS command for neighbor list modification (default is ["every", 10, "delay", 0, "check", "yes"]).
newton : str, optional Specifies the Newton flag (default is "off").
Live Preview:
live_units : str, optional Units for live preview (default is "lj", for Lennard-Jones units).
live_atom_style : str, optional Atom style used specifically for live LAMMPS sessions (default is "atomic").
livepreview_options : dict, optional Contains options for live preview. The dictionary includes 'static' (default: run = 1) and 'dynamic' (default: run = 100) options.
Methods:
init : Constructor method to initialize all the attributes of the
region
class.constructor
Expand source code
class region: """ The `region` class represents a simulation region, centered at the origin (0, 0, 0) by default, and is characterized by its physical dimensions, properties, and boundary conditions. It supports setting up lattice structures, particle properties, and options for live previews. Attributes: ---------- name : str, optional Name of the region (default is 'region container'). dimension : int, optional Number of spatial dimensions for the simulation (either 2 or 3, default is 3). boundary : list of str or None, optional Boundary conditions for each dimension. If None, defaults to ["sm"] * dimension. Must be a list of length `dimension`, where "s" indicates shrink-wrapped, and "m" indicates a non-periodic boundary. nbeads : int, optional Number of beads in the region (default is 1). units : str, optional Units for the simulation box (default is ""). Particle Properties: ------------------- mass : float, optional Mass of particles in the region (default is 1). volume : float, optional Volume of the region (default is 1). density : float, optional Density of the region (default is 1). radius : float, optional Radius of the particles (default is 1.5). contactradius : float, optional Contact radius of the particles (default is 0.5). velocities : list of floats, optional Initial velocities of particles (default is [0, 0, 0]). forces : list of floats, optional External forces acting on the particles (default is [0, 0, 0]). Other Properties: ---------------- filename : str, optional Name of the output file (default is an empty string, which will auto-generate a name based on the region name). index : int, optional Index or identifier for the region. run : int, optional Run configuration parameter (default is 1). Box Properties: --------------- center : list of floats, optional Center of the simulation box for coordinate scaling (default is [0, 0, 0]). width : float, optional Width of the region (default is 10). height : float, optional Height of the region (default is 10). depth : float, optional Depth of the region (default is 10). hasfixmove : bool, optional Indicates whether the region has a fixed movement (default is False). Spacefilling Design: ------------------- spacefilling : bool, optional Indicates whether the design is space-filling (default is False). fillingbeadtype : int, optional Type of bead used for space filling (default is 1). Lattice Properties: ------------------ regionunits : str, optional Defines the units of the region. Can be either "lattice" (default) or "si". separationdistance : float, optional Separation distance between atoms in SI units (default is 5e-6). lattice_scale : float, optional Scaling factor for the lattice, used mainly in visualization (default is 0.8442). lattice_spacing : list or None, optional Specifies the spacing between lattice points. If None, the default spacing is used. Can be a list of [dx, dy, dz]. lattice_style : str, optional Specifies the lattice structure style (default is "fcc"). Accepts any LAMMPS valid style, e.g., "sc" for simple cubic. Atom Properties: ---------------- atom_style : str, optional Defines the atom style for the region (default is "smd"). atom_modify : list of str, optional LAMMPS command for atom modification (default is ["map", "array"]). comm_modify : list of str, optional LAMMPS command for communication modification (default is ["vel", "yes"]). neigh_modify : list, optional LAMMPS command for neighbor list modification (default is ["every", 10, "delay", 0, "check", "yes"]). newton : str, optional Specifies the Newton flag (default is "off"). Live Preview: ------------ live_units : str, optional Units for live preview (default is "lj", for Lennard-Jones units). live_atom_style : str, optional Atom style used specifically for live LAMMPS sessions (default is "atomic"). livepreview_options : dict, optional Contains options for live preview. The dictionary includes 'static' (default: run = 1) and 'dynamic' (default: run = 100) options. Methods: ------- __init__ : Constructor method to initialize all the attributes of the `region` class. """ _version = "0.9997" __custom_documentations__ = "pizza.region.region class" # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # # CONSTRUCTOR METHOD # # # The constructor include # the main container: objects (a dictionnary) # several attributes covering current and future use of PIZZA.REGION() # # The original constructor is derived from PIZZA.RASTER() with # an intent to allow at some point some forward and backward port between # objects of the class PIZZA.RASTER() and PIZZA.REGION(). # # The code will evolve according to the needs, please come back regularly. # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # CONSTRUCTOR ---------------------------- def __init__(self, # container properties name="region container", dimension = 3, boundary = None, nbeads=1, units = "", # particle properties mass=1.0, volume=1.0, density=1.0, radius=1.5, contactradius=0.5, velocities=[0.0,0.0,0.0], forces=[0.0,0.0,0.0], # other properties filename="", previewfilename="", index = None, run=1, # Box lengths center = [0.0,0.0,0.0], # center of the box for coordinates scaling width = 10.0, # along x height = 10.0, # along y depth = 10.0, # along z hasfixmove = False, # by default no fix move # Spacefilling design (added on 2023-08-10) spacefilling = False, fillingbeadtype = 1, # Lattice properties boxid = "box", # default value for ${boxid_arg} regionunits = "lattice", # units ("lattice" or "si") separationdistance = 5e-6, # SI units lattice_scale = 0.8442, # LJ units (for visualization) lattice_spacing = None, # lattice spacing is not used by default (set [dx dy dz] if needed) lattice_style = "fcc" , # any valid lattice style accepted by LAMMPS (sc=simple cubic) # Atom properties atom_style = "smd", atom_modify = ["map","array"], comm_modify = ["vel","yes"], neigh_modify = ["every",10,"delay",0,"check","yes"], newton ="off", # Live preview live_units = "lj", # units to be used ONLY with livelammps (https://andeplane.github.io/atomify/) live_atom_style = "atomic",# atom style to be used ONLY with livelammps (https://andeplane.github.io/atomify/) # livepreview options livepreview_options = { 'static':{'run':1}, 'dynamic':{'run':100} }, # common flags (for scripting) printflag = False, verbose = True, verbosity = None ): """ constructor """ self.name = name # Ensure dimension is an integer (must be 2 or 3 for LAMMPS) if not isinstance(dimension, int) or dimension not in (2, 3): raise ValueError("dimension must be either 2 or 3.") # Handle boundary input if boundary is None: boundary = ["sm"] * dimension elif isinstance(boundary, list): if len(boundary) != dimension: raise ValueError(f"The length of boundary ({len(boundary)}) must match the dimension ({dimension}).") else: raise ValueError("boundary must be a list of strings or None.") # Validate regionunits if regionunits not in ("lattice", "si"): raise ValueError("regionunits can only be 'lattice' or 'si'.") # Lattice scaling logic lattice_scale_siunits = lattice_scale if regionunits == "si" else separationdistance if lattice_scale_siunits is None or lattice_scale_siunits=="": lattice_scale_siunits = separationdistance if lattice_spacing == "": lattice_spacing = None elif isinstance(lattice_spacing, (int, float)): lattice_spacing = [lattice_spacing] * dimension elif isinstance(lattice_spacing, list): lattice_spacing = lattice_spacing + [lattice_spacing[-1]] * (dimension - len(lattice_spacing)) if len(lattice_spacing) < dimension else lattice_spacing[:dimension] # live data (updated 2024-07-04) live_lattice_scale = lattice_scale/separationdistance if regionunits == "si" else lattice_scale live_box_scale = 1/lattice_scale_siunits if regionunits == "si" else 1 self.live = regiondata(nbeads=nbeads, run=run, width=math.ceil(width*live_box_scale), # live_box_scale force lattice units for live visualization height=math.ceil(height*live_box_scale), # live_box_scale force lattice units for live visualization depth=math.ceil(depth*live_box_scale), # live_box_scale force lattice units for live visualization live_units = "$"+live_units, live_atom_style = "$"+live_atom_style, live_lattice_style="$"+lattice_style, live_lattice_scale=live_lattice_scale) # generic SMD properties (to be rescaled) self.volume = volume self.mass = mass self.density = density self.radius = radius self.contactradius = contactradius self.velocities = velocities self.forces = forces if filename == "": self.filename = f"region_{self.name}" else: self.filename = filename self.index = index self.objects = {} # object container self.nobjects = 0 # total number of objects (alive) # count objects per type self.counter = { "ellipsoid":0, "block":0, "sphere":0, "cone":0, "cylinder":0, "prism":0, "plane":0, "union":0, "intersect":0, "eval":0, "collection":0, "all":0 } # fix move flag self.hasfixmove = hasfixmove # livelammps (for live sessions) - added 2023-02-06 self.livelammps = { "URL": livelammpsURL, "active": False, "file": None, "options": livepreview_options } # space filling (added 2023-08-10) self.spacefilling = { "flag": spacefilling, "fillingstyle": "$block", "fillingbeadtype": fillingbeadtype, "fillingwidth": width, "fillingheight": height, "fillingdepth": depth, "fillingunits": units } # region object units self.regionunits = regionunits # lattice self.units = units self.center = center self.separationdistance = separationdistance self.lattice_scale = lattice_scale self.lattice_spacing = lattice_spacing self.lattice_scale_siunits = lattice_scale_siunits self.lattice_style = lattice_style # headers for header scripts (added 2024-09-01) # geometry is assumed to be units set by ${boxunits_arg} (new standard 2024-11-26) self.headersData = headersRegiondata( # use $ and [] to prevent execution name = "$"+name, previewfilename = "$dump.initial."+self.filename if previewfilename=="" else "$"+previewfilename, # Initialize Lammps dimension = dimension, units = "$"+units, boundary = boundary, atom_style = "$" + atom_style, atom_modify = atom_modify, comm_modify = comm_modify, neigh_modify = neigh_modify, newton ="$" + newton, # Box (added 2024-11-26) boxid = "$"+boxid, boxunits_arg = "$units box" if regionunits=="si" else "", # standard on 2025-11-26 # Lattice lattice_style = "$"+lattice_style, lattice_scale = lattice_scale, lattice_spacing = lattice_spacing, # Box xmin = -(width/2) +center[0], xmax = +(width/2) +center[0], ymin = -(height/2) +center[1], ymax = +(height/2) +center[1], zmin = -(depth/2) +center[2], zmax = +(depth/2) +center[2], nbeads = nbeads, mass = mass ) self.printflag = printflag self.verbose = verbose if verbosity is None else verbosity>0 self.verbosity = 0 if not verbose else verbosity # Method for coordinate/length scaling and translation including with formula embedded strings (updated 2024-07-03, fixed 2024-07-04) # Note that the translation is not fully required since the scaling applies also to full coordinates. # However, an implementation is provided for arbitrary offset. def scale_and_translate(self, value, offset=0): """ Scale and translate a value or encapsulate the formula within a string. If self.regionunits is "si", only the offset is applied without scaling. Otherwise, scaling and translation are performed based on self.units ("si" or "lattice"). Parameters: value (str or float): The value or formula to be scaled and translated. offset (float, optional): The offset to apply. Defaults to 0. Returns: str or float: The scaled and translated value or formula. """ if self.regionunits == "si": # Only apply offset without scaling if isinstance(value, str): if offset: translated = f"({value}) - {offset}" else: translated = f"{value}" return translated else: if offset: return value - offset else: return value else: # Existing behavior based on self.units if isinstance(value, str): if offset: translated = f"({value}) - {offset}" else: translated = f"{value}" if self.units == "si": return f"({translated}) / {self.lattice_scale} + {offset / self.lattice_scale}" else: # "lattice" return f"({translated}) * {self.lattice_scale} + {offset * self.lattice_scale}" else: if offset: translated = value - offset else: translated = value if self.units == "si": return translated / self.lattice_scale + (offset / self.lattice_scale) else: # "lattice" return translated * self.lattice_scale + (offset * self.lattice_scale) # space filling attributes (cannot be changed) @property def isspacefilled(self): return self.spacefilling["flag"] @property def spacefillingbeadtype(self): return self.spacefilling["fillingbeadtype"] # total number of atoms in the region @property def natoms(self): """Count the total number of atoms in all objects within the region.""" total_atoms = 0 for eachobj in self: total_atoms += eachobj.natoms return total_atoms # details if the geometry of the region @property def geometry(self): """Display the dimensions and characteristics of the region and its objects.""" details = f"Region: {self.name}\n" details += f"Total atoms: {self.natoms}\n" details += f"Span: width={self.spacefilling['fillingwidth']}, height={self.spacefilling['fillingheight']}, depth={self.spacefilling['fillingdepth']}\n" details += f"Box center: {self.center}\n" details += "Objects in the region:\n\n" for obj in self: details += "\n\n"+"-"*32+"\n" details += f"\nObject: {obj.name}\n" details += f"Type: {type(obj).__name__}\n" if hasattr(obj, 'geometry'): details += "\n"+"-"*32+"\n" details += obj.geometry else: details += "No geometry information available.\n" print(details) # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # # REGION.GEOMETRY constructors # # # These methods create the 3D geometry objects (at least their code) # A geometry is a collection of PIZZA.SCRIPT() objects (LAMMPS codelet) # not a real geometry. The distinction between the creation (definition) # and the execution (generation) of the gometry object existed already # in PIZZA.RASTER(), but here they remain codelets as ONLY LAMMPS can # generate the real object. # # This level of abstraction makes it possible to mix PIZZA variables # (USER, PIZZA.SCRIPT.USER, PIZZA.PIPESCRIPT.USER) with LAMMPS variables. # The same object template can be used in different LAMMPS scripts with # different values and without writting additional Python code. # In shorts: USER fields store PIZZA.SCRIPT() like variables # (they are compiled [statically] before LAMMPS execution) # VARIABLES are defined in the generated LAMMPS script but # created [dynamically] in LAMMPS. Note that these variables # are defined explicitly with the LAMMPS variable command: # variable name style args ... # Note: static variables can have only one single value for LAMMPS, which # is known before LAMMPS is launched. The others can be assigned # at runtime when LAMMPS is running. # Example with complex definitions # R.ellipsoid(0,0,0,1,1,1,name="E2",side="out", # move=["left","${up}*3",None], # up=0.1) # R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})' # R.E2.VARIABLES.a="${b}-5" # R.E2.VARIABLES.b=5 # R.E2.VARIABLES.c=100 # # The methods PIZZA.REGION.DO(), PIZZA.REGION.DOLIVE() compiles # (statically) and generate the corresponding LAMMPS code. The static # compiler accepts hybrid constructions where USER and VARIABLES are # mixed. Any undefined variables will be assumed to be defined elsewhere # in the LAMMPS code. # # Current attributes of PIZZA.REGION.OBJECT cover current and future use # of these objects and will allow some point some forward and backward # compatibility with the same PIZZA.RASTER.OBJECT. # # # References: # https://docs.lammps.org/region.html # https://docs.lammps.org/variable.html # https://docs.lammps.org/create_atoms.html # https://docs.lammps.org/create_box.html # # # List of implemented geometries (shown here with the LAMMPS syntax) # block args = xlo xhi ylo yhi zlo zhi # cone args = dim c1 c2 radlo radhi lo hi # cylinder args = dim c1 c2 radius lo hi # ellipsoid args = x y z a b c <-- first method to be implemented # plane args = px py pz nx ny n # prism args = xlo xhi ylo yhi zlo zhi xy xz yz # sphere args = x y z radius # union args = N reg-ID1 reg-ID2 .. # intersect args = N reg-ID1 reg-ID2 ... # # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # BLOCK method --------------------------- # block args = xlo xhi ylo yhi zlo zhi # xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units) def block(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a block region xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "block001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "block" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object B with B for block obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density B = Block((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): B.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: B.beadtype = beadtype # bead type (if not defined, default index will apply) B.USER.ID = "$"+B.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [xlo, xhi, ylo, yhi, zlo, zhi] # args = [....] as defined in the class Block args_scaled = [ self.scale_and_translate(xlo, self.center[0]), self.scale_and_translate(xhi, self.center[0]), self.scale_and_translate(ylo, self.center[1]), self.scale_and_translate(yhi, self.center[1]), self.scale_and_translate(zlo, self.center[2]), self.scale_and_translate(zhi, self.center[2]) ] if self.units == "si": B.USER.args = args_scaled B.USER.args_siunits = args else: # "lattice" B.USER.args = args B.USER.args_siunits = args_scaled # geometry B.USER.geometry = ( f"Block Region: {B.name}\n" "Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi] = bounds of block in all dimensions" f"Coordinates (scaled): {B.USER.args}\n" f"Coordinates (SI units): {B.USER.args_siunits}\n" f"\talong x: [{B.USER.args[0]}, {B.USER.args[1]}]\n" f"\talong y: [{B.USER.args[2]}, {B.USER.args[3]}]\n" f"\talong z: [{B.USER.args[4]}, {B.USER.args[5]}]" ) # other attributes ------------------------------------- B.USER.beadtype = B.beadtype # beadtype to be used for create_atoms B.USER.side = B.sidearg(side) # extra parameter side B.USER.move = B.movearg(move) # move arg B.USER.units = B.unitsarg(units) # units B.USER.rotate = B.rotatearg(rotate) # rotate B.USER.open = B.openarg(open) # open # Create the object if not fake if fake: return B else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = B self.nobjects += 1 return None # CONE method --------------------------- # cone args = dim c1 c2 radlo radhi lo hi # dim = x or y or z = axis of cone # c1,c2 = coords of cone axis in other 2 dimensions (distance units) # radlo,radhi = cone radii at lo and hi end (distance units) # lo,hi = bounds of cone in dim (distance units) def cone(self,dim="z",c1=0,c2=0,radlo=2,radhi=5,lo=-10,hi=10, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a cone region dim = "x" or "y" or "z" = axis of the cone note: USER, LAMMPS variables are not authorized here c1,c2 = coords of cone axis in other 2 dimensions (distance units) radlo,radhi = cone radii at lo and hi end (distance units) lo,hi = bounds of cone in dim (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "cone001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "cone" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object C with C for cone obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density C = Cone((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) C.USER.ID = "$"+C.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [dim, c1, c2, radlo, radhi, lo, hi] # args = [....] as defined in the class Cone if dim == "x": # x-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[1]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[0]), self.scale_and_translate(hi, self.center[0]) ] elif dim == "y": # y-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[1]), self.scale_and_translate(hi, self.center[1]) ] else: # z-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[1]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[2]), self.scale_and_translate(hi, self.center[2]) ] if self.units == "si": C.USER.args = args_scaled C.USER.args_siunits = args else: # "lattice" C.USER.args = args C.USER.args_siunits = args_scaled # geometry C.USER.geometry = ( f"Cone Region: {C.name}\n" "Coordinates: [dim,c1,c2,radlo,radhi,lo,hi] = dimensions of cone\n" f"Coordinates (scaled): {C.USER.args}\n" f"Coordinates (SI units): {C.USER.args_siunits}\n" f"\tdim: {C.USER.args[0]}\n" f"\tc1: {C.USER.args[1]}\n" f"\tc2: {C.USER.args[2]}\n" f"\tradlo: {C.USER.args[3]}\n" f"\tradhi: {C.USER.args[4]}\n" f"\tlo: {C.USER.args[5]}\n" f"\thi: {C.USER.args[6]}" ) # other attributes ------------------------------------- C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms C.USER.side = C.sidearg(side) # extra parameter side C.USER.move = C.movearg(move) # move arg C.USER.units = C.unitsarg(units) # units C.USER.rotate = C.rotatearg(rotate) # rotate C.USER.open = C.openarg(open) # open # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None # CYLINDER method --------------------------- # cylinder args = dim c1 c2 radius lo hi # dim = x or y or z = axis of cylinder # c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) # radius = cylinder radius (distance units) # c1,c2, and radius can be a variable (see below) # lo,hi = bounds of cylinder in dim (distance units) def cylinder(self,dim="z",c1=0,c2=0,radius=4,lo=-10,hi=10, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a cylinder region dim = x or y or z = axis of cylinder c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) radius = cylinder radius (distance units) c1,c2, and radius can be a LAMMPS variable lo,hi = bounds of cylinder in dim (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "cylinder001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "cylinder" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object C with C for cylinder obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density C = Cylinder((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) C.USER.ID = "$"+C.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [dim, c1, c2, radius, lo, hi] # args = [....] as defined in the class Cylinder if dim == "x": # x-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[1]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[0]), self.scale_and_translate(hi, self.center[0]) ] elif dim == "y": # y-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[1]), self.scale_and_translate(hi, self.center[1]) ] else: # z-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[1]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[2]), self.scale_and_translate(hi, self.center[2]) ] if self.units == "si": C.USER.args = args_scaled C.USER.args_siunits = args else: # "lattice" C.USER.args = args C.USER.args_siunits = args_scaled # geometry C.USER.geometry = ( f"Cylinder Region: {C.name}\n" "Coordinates: [dim,c1,c2,radius,lo,hi] = dimensions of cylinder\n" f"Coordinates (scaled): {C.USER.args}\n" f"Coordinates (SI units): {C.USER.args_siunits}\n" f"\tdim: {C.USER.args[0]}\n" f"\tc1: {C.USER.args[1]}\n" f"\tc2: {C.USER.args[2]}\n" f"\tradius: {C.USER.args[3]}\n" f"\tlo: {C.USER.args[4]}\n" f"\thi: {C.USER.args[5]}" ) # other attributes ------------------------------------- C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms C.USER.side = C.sidearg(side) # extra parameter side C.USER.move = C.movearg(move) # move arg C.USER.units = C.unitsarg(units) # units C.USER.rotate = C.rotatearg(rotate) # rotate C.USER.open = C.openarg(open) # open # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None # ELLIPSOID method --------------------------- # ellipsoid args = x y z a b c # x,y,z = center of ellipsoid (distance units) # a,b,c = half the length of the principal axes of the ellipsoid (distance units) # x,y,z,a,b,c can be variables def ellipsoid(self,x=0,y=0,z=0,a=5,b=3,c=2, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates an ellipsoid region ellipsoid(x,y,z,a,b,c [,name=None,beadtype=None,property=value,...]) x,y,z = center of ellipsoid (distance units) a,b,c = half the length of the principal axes of the ellipsoid (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "ellipsoid001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index Examples: # example with variables created either at creation or later R = region(name="my region") R.ellipsoid(0, 0, 0, 1, 1, 1,name="E1",toto=3) repr(R.E1) R.E1.VARIABLES.a=1 R.E1.VARIABLES.b=2 R.E1.VARIABLES.c="(${a},${b},100)" R.E1.VARIABLES.d = '"%s%s" %("test",${c}) # note that test could be replaced by any function' # example with extra parameters R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",move=["left","${up}*3",None],up=0.1) R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})' R.E2.VARIABLES.a="${b}-5" R.E2.VARIABLES.b=5 R.E2.VARIABLES.c=100 """ # prepare object creation kind = "ellipsoid" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object E with E for Ellipsoid obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density E = Ellipsoid((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): E.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply) E.USER.ID = "$"+E.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [x, y, z, a, b, c] # args = [....] as defined in the class Ellipsoid args_scaled = [ self.scale_and_translate(x, self.center[0]), self.scale_and_translate(y, self.center[1]), self.scale_and_translate(z, self.center[2]), self.scale_and_translate(a, 0), self.scale_and_translate(b, 0), self.scale_and_translate(c, 0) ] if self.units == "si": E.USER.args = args_scaled E.USER.args_siunits = args else: # "lattice" E.USER.args = args E.USER.args_siunits = args_scaled # geometry E.USER.geometry = ( f"Ellipsoid Region: {E.name}\n" "Coordinates: [x,y,z,a,b,c] = center and radii of ellipsoid\n" f"Coordinates (scaled): {E.USER.args}\n" f"Coordinates (SI units): {E.USER.args_siunits}\n" f"\tcenter: [{E.USER.args[0]}, {E.USER.args[1]}, {E.USER.args[2]}]\n" f"\ta: {E.USER.args[3]}\n" f"\tb: {E.USER.args[4]}\n" f"\tc: {E.USER.args[5]}" ) # other attributes ------------------------------------- E.USER.beadtype = E.beadtype # beadtype to be used for create_atoms E.USER.side = E.sidearg(side) # extra parameter side E.USER.move = E.movearg(move) # move arg E.USER.units = E.unitsarg(units) # units E.USER.rotate = E.rotatearg(rotate) # rotate E.USER.open = E.openarg(open) # open # Create the object if not fake if fake: return E else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = E self.nobjects += 1 return None # PLANE method --------------------------- # plane args = px py pz nx ny nz # px,py,pz = point on the plane (distance units) # nx,ny,nz = direction normal to plane (distance units) def plane(self,px=0,py=0,pz=0,nx=0,ny=0,nz=1, name=None,beadtype=None,fake=False, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a plane region px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "plane001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "plane" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object P with P for plane P = Plane((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=self.mass, density=self.density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): P.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply) P.USER.ID = "$"+P.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [px, py, pz, nx, ny, nz] # args = [....] as defined in the class Plane args_scaled = [ self.scale_and_translate(px, self.center[0]), self.scale_and_translate(py, self.center[1]), self.scale_and_translate(pz, self.center[2]), self.scale_and_translate(nx, 0), self.scale_and_translate(ny, 0), self.scale_and_translate(nz, 0) ] if self.units == "si": P.USER.args = args_scaled P.USER.args_siunits = args else: # "lattice" P.USER.args = args P.USER.args_siunits = args_scaled # geometry P.USER.geometry = ( f"Plane Region: {P.name}\n" "Coordinates: [px,py,pz,nx,ny,nz] = point and normal vector of plane\n" f"Coordinates (scaled): {P.USER.args}\n" f"Coordinates (SI units): {P.USER.args_siunits}\n" f"\tpoint: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}]\n" f"\tnormal: [{P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]" ) # other attributes --------------------------- P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms P.USER.side = P.sidearg(side) # extra parameter side P.USER.move = P.movearg(move) # move arg P.USER.units = P.unitsarg(units) # units P.USER.rotate = P.rotatearg(rotate) # rotate P.USER.open = P.openarg(open) # open # Create the object if not fake if fake: return P else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = P self.nobjects += 1 return None # PRISM method --------------------------- # prism args = xlo xhi ylo yhi zlo zhi xy xz yz # xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units) # xy = distance to tilt y in x direction (distance units) # xz = distance to tilt z in x direction (distance units) # yz = distance to tilt z in y direction (distance units) def prism(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5,xy=1,xz=1,yz=1, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a prism region xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units) xy = distance to tilt y in x direction (distance units) xz = distance to tilt z in x direction (distance units) yz = distance to tilt z in y direction (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "prism001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "prism" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object P with P for prism obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density P = Prism((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): P.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply) P.USER.ID = "$"+P.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz] # args = [....] as defined in the class Prism args_scaled = [ self.scale_and_translate(xlo, self.center[0]), self.scale_and_translate(xhi, self.center[0]), self.scale_and_translate(ylo, self.center[1]), self.scale_and_translate(yhi, self.center[1]), self.scale_and_translate(zlo, self.center[2]), self.scale_and_translate(zhi, self.center[2]), self.scale_and_translate(xy, 0), self.scale_and_translate(xz, 0), self.scale_and_translate(yz, 0) ] if self.units == "si": P.USER.args = args_scaled P.USER.args_siunits = args else: # "lattice" P.USER.args = args P.USER.args_siunits = args_scaled # geometry P.USER.geometry = ( f"Prism Region: {P.name}\n" "Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi,xy,xz,yz] = bounds and tilts of prism\n" f"Coordinates (scaled): {P.USER.args}\n" f"Coordinates (SI units): {P.USER.args_siunits}\n" f"\tbounds: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}, {P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]\n" f"\ttilts: [{P.USER.args[6]}, {P.USER.args[7]}, {P.USER.args[8]}]" ) # other attributes --------------------------- P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms P.USER.side = P.sidearg(side) # extra parameter side P.USER.move = P.movearg(move) # move arg P.USER.units = P.unitsarg(units) # units P.USER.rotate = P.rotatearg(rotate) # rotate P.USER.open = P.openarg(open) # open # Create the object if not fake if fake: return P else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = P self.nobjects += 1 return None # SPHERE method --------------------------- # sphere args = x y z radius # x,y,z = center of sphere (distance units) # radius = radius of sphere (distance units) # x,y,z, and radius can be a variable (see below) def sphere(self,x=0,y=0,z=0,radius=3, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a sphere region x,y,z = center of sphere (distance units) radius = radius of sphere (distance units) x,y,z, and radius can be a variable URL: https://docs.lammps.org/region.html Main properties = default value name = "sphere001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "sphere" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object S with S for sphere obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density S = Sphere((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): S.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: S.beadtype = beadtype # bead type (if not defined, default index will apply) S.USER.ID = "$"+S.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [x, y, z, radius] # args = [....] as defined in the class Sphere args_scaled = [ self.scale_and_translate(x, self.center[0]), self.scale_and_translate(y, self.center[1]), self.scale_and_translate(z, self.center[2]), self.scale_and_translate(radius, 0) ] if self.units == "si": S.USER.args = args_scaled S.USER.args_siunits = args else: # "lattice" S.USER.args = args S.USER.args_siunits = args_scaled # geometry S.USER.geometry = ( f"Sphere Region: {S.name}\n" "Coordinates: [x,y,z,radius] = center and radius of sphere\n" f"Coordinates (scaled): {S.USER.args}\n" f"Coordinates (SI units): {S.USER.args_siunits}\n" f"\tcenter: [{S.USER.args[0]}, {S.USER.args[1]}, {S.USER.args[2]}]\n" f"\tradius: {S.USER.args[3]}" ) # other attributes --------------------------- S.USER.beadtype = S.beadtype # beadtype to be used for create_atoms S.USER.side = S.sidearg(side) # extra parameter side S.USER.move = S.movearg(move) # move arg S.USER.units = S.unitsarg(units) # units S.USER.rotate = S.rotatearg(rotate) # rotate S.USER.open = S.openarg(open) # open # Create the object if not fake if fake: return S else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = S self.nobjects += 1 return None # UNION method --------------------------- # union args = N reg-ID1 reg-ID2 def union(self,*regID, name=None,beadtype=1,fake=False, index = None,subindex = None, **variables): """ creates a union region union("reg-ID1","reg-ID2",name="myname",beadtype=1,...) reg-ID1,reg-ID2, ... = IDs of regions to join together URL: https://docs.lammps.org/region.html Main properties = default value name = "union001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex """ kind = "union" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object U with U for union U = Union((self.counter["all"]+1,self.counter[kind]+1), index=index,subindex=subindex,**variables) # feed USER fields if name not in (None,""): U.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: U.beadtype = beadtype # bead type (if not defined, default index will apply) U.USER.ID = "$"+U.name # add $ to prevent its execution U.USER.side, U.USER.move, U.USER.units, U.USER.rotate, U.USER.open = "","","","","" # build arguments based on regID nregID = len(regID) if nregID<2: raise ValueError('two objects must be given at least for an union') args = [None] # the number of arguments is not known yet validID = range(nregID) for ireg in validID: if isinstance(regID[ireg],int): if regID[ireg] in validID: args.append(self.names[regID[ireg]]) else: raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}") elif isinstance(regID[ireg],str): if regID[ireg] in self: args.append(regID[ireg]) else: raise KeyError(f'the object "{regID[ireg]}" does not exist') else: raise KeyError(f"the {ireg+1}th object should be given as a string or an index") # prevent the creation of atoms merged (avoid duplicates) self.objects[regID[ireg]].FLAGSECTIONS["create"] = False args[0] = len(regID) U.USER.args = args # args = [....] as defined in the class Union # Create the object if not fake if fake: return U else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = U self.nobjects += 1 return None # UNION method --------------------------- # union args = N reg-ID1 reg-ID2 def intersect(self,*regID, name=None,beadtype=1,fake=False, index = None,subindex = None, **variables): """ creates an intersection region intersect("reg-ID1","reg-ID2",name="myname",beadtype=1,...) reg-ID1,reg-ID2, ... = IDs of regions to join together URL: https://docs.lammps.org/region.html Main properties = default value name = "intersect001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex """ kind = "intersect" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object I with I for intersect I = Intersect((self.counter["all"]+1,self.counter[kind]+1), index=index,subindex=subindex,**variables) # feed USER fields if name not in (None,""): I.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: I.beadtype = beadtype # bead type (if not defined, default index will apply) I.USER.ID = "$"+I.name # add $ to prevent its execution I.USER.side, I.USER.move, I.USER.units, I.USER.rotate, I.USER.open = "","","","","" # build arguments based on regID nregID = len(regID) if nregID<2: raise ValueError('two objects must be given at least for an intersection') args = [None] # the number of arguments is not known yet validID = range(nregID) for ireg in validID: if isinstance(regID[ireg],int): if regID[ireg] in validID: args.append(self.names[regID[ireg]]) else: raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}") elif isinstance(regID[ireg],str): if regID[ireg] in self: args.append(regID[ireg]) else: raise KeyError(f'the object "{regID[ireg]}" does not exist') else: raise KeyError(f"the {ireg+1}th object should be given as a string or an index") # prevent the creation of atoms (avoid duplicates) self.objects[regID[ireg]].FLAGSECTIONS["create"] = False args[0] = len(regID) I.USER.args = args # args = [....] as defined in the class Union # Create the object if not fake if fake: return I else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = I self.nobjects += 1 return None # Group method --------------------------- def group(self,obj,name=None,fake=False): pass # COLLECTION method --------------------------- def collection(self,*obj,name=None,beadtype=None,fake=False, index = None,subindex = None, **kwobj): kind = "collection" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object C with C for collection C = Collection((index,subindex)) if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) # add objects C.collection = regioncollection(*obj,**kwobj) # apply modifications (beadtype, ismask) for o in C.collection.keys(): tmp = C.collection.getattr(o) if beadtype != None: tmp.beadtype = beadtype C.collection.setattr(o,tmp) # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None def scatter(self, E, name="emulsion", beadtype=None, ): """ Parameters ---------- E : scatter or emulsion object codes for x,y,z and r. name : string, optional name of the collection. The default is "emulsion". beadtype : integer, optional for all objects. The default is 1. Raises ------ TypeError Return an error of the object is not a scatter type. Returns ------- None. """ if isinstance(E,scatter): collect = {} for i in range(E.n): b = E.beadtype[i] if beadtype==None else beadtype nameobj = "glob%02d" % i collect[nameobj] = self.sphere(E.x[i],E.y[i],E.z[i],E.r[i], name=nameobj,beadtype=b,fake=True) self.collection(**collect,name=name) else: raise TypeError("the first argument must be an emulsion object") # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # # LOW-LEVEL METHODS # # # Low-level methods to manipulate and operate region objects (e.g., R). # They implement essentially some Python standards with the following # shortcut: R[i] or R[objecti] and R.objecti and R.objects[objecti] are # the same ith object where R.objects is the original container # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # repr() method ---------------------------- def __repr__(self): """ display method """ spacefillingstr = f"\n(space filled with beads of type {self.spacefillingbeadtype})" \ if self.isspacefilled else "" print("-"*40) print('REGION container "%s" with %d objects %s\n(units="%s", lattice="%s", scale=%0.4g [m])' \ % (self.name,self.nobjects,spacefillingstr,self.units,self.lattice_style,self.lattice_scale_siunits)) if self.nobjects>0: names = self.names l = [len(n) for n in names] width = max(10,max(l)+2) fmt = "%%%ss:" % width for i in range(self.nobjects): flags = "("+self.objects[names[i]].shortflags+")" if self.objects[names[i]].flags else "(no script)" if isinstance(self.objects[names[i]],Collection): print(fmt % names[i]," %s region (%d beadtypes)" % \ (self.objects[names[i]].kind,len(self.objects[names[i]].beadtype))," > ",flags) else: print(fmt % names[i]," %s region (beadtype=%d)" % \ (self.objects[names[i]].kind,self.objects[names[i]].beadtype)," > ",flags) print(wrap("they are",":",", ".join(self.names),10,60,80)) print("-"*40) return "REGION container %s with %d objects (%s)" % \ (self.name,self.nobjects,",".join(self.names)) # str() method ---------------------------- def __str__(self): """ string representation of a region """ return "REGION container %s with %d objects (%s)" % \ (self.name,self.nobjects,",".join(self.names)) # generic GET method ---------------------------- def get(self,name): """ returns the object """ if name in self.objects: return self.objects[name] else: raise NameError('the object "%s" does not exist, use list()' % name) # getattr() method ---------------------------- def __getattr__(self,name): """ getattr attribute override """ if (name in self.__dict__) or (name in protectedregionkeys): return self.__dict__[name] # higher precedence for root attributes if name in protectedregionkeys: return getattr(type(self), name).__get__(self) # for methods decorated as properties (@property) # Handle special cases like __wrapped__ explicitly if name == "__wrapped__": return None # Default value or appropriate behavior # Leave legitimate __dunder__ attributes to the default mechanism if name.startswith("__") and name.endswith("__"): raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}") # Default return self.get(name) # generic SET method ---------------------------- def set(self,name,value): """ set field and value """ if isinstance(value,list) and len(value)==0: if name not in self.objects: raise NameError('the object "%s" does not exist, use list()' % name) self.delete(name) elif isinstance(value,coregeometry): if name in self.objects: self.delete(name) if isinstance(value.SECTIONS,pipescript) or isinstance(value,Evalgeometry): self.eval(deepduplicate(value),name) # not a scalar else: # scalar self.objects[name] = deepduplicate(value) self.objects[name].name = name self.nobjects += 1 self.counter["all"] += 1 self.objects[name].index = self.counter["all"] self.counter[value.kind] += 1 # setattr() method ---------------------------- def __setattr__(self,name,value): """ setattr override """ if name in protectedregionkeys: # do not forget to increment protectedregionkeys self.__dict__[name] = value # if not, you may enter in infinite loops else: self.set(name,value) # generic HASATTR method ---------------------------- def hasattr(self,name): """ return true if the object exist """ if not isinstance(name,str): raise TypeError("please provide a string") return name in self.objects # IN operator ---------------------------- def __contains__(self,obj): """ in override """ return self.hasattr(obj) # len() method ---------------------------- def __len__(self): """ len method """ return len(self.objects) # indexing [int] and ["str"] method ---------------------------- def __getitem__(self,idx): """ R[i] returns the ith element of the structure R[:4] returns a structure with the four first fields R[[1,3]] returns the second and fourth elements """ if isinstance(idx,int): if idx<len(self): return self.get(self.names[idx]) raise IndexError(f"the index should be comprised between 0 and {len(self)-1}") elif isinstance(idx,str): if idx in self: return self.get(idx) raise NameError(f'{idx} does not exist, use list() to list objects') elif isinstance(idx,list): pass elif isinstance(idx,slice): return self.__getitem__(self,list(range(*idx.indices(len(self))))) else: raise IndexError("not implemented yet") # duplication GET method based on DICT ---------------------------- def __getstate__(self): """ getstate for cooperative inheritance / duplication """ return self.__dict__.copy() # duplication SET method based on DICT ---------------------------- def __setstate__(self,state): """ setstate for cooperative inheritance / duplication """ self.__dict__.update(state) # iterator method ---------------------------- def __iter__(self): """ region iterator """ # note that in the original object _iter_ is a static property not in dup dup = duplicate(self) dup._iter_ = 0 return dup # next iterator method ---------------------------- def __next__(self): """ region iterator """ self._iter_ += 1 if self._iter_<=len(self): return self[self._iter_-1] self._iter_ = 0 raise StopIteration(f"Maximum region.objects iteration reached {len(self)}") # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # # MIDDLE-LEVEL METHODS # # # These methods are specific to PIZZA.REGION() objects. # They bring useful methods for the user and developer. # Similar methods exist in PIZZA.RASTER() # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # LIST method ---------------------------- def list(self): """ list objects """ fmt = "%%%ss:" % max(10,max([len(n) for n in self.names])+2) print('REGION container "%s" with %d objects' % (self.name,self.nobjects)) for o in self.objects.keys(): print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind, "(beadtype=%d,object index=[%d,%d])" % \ (self.objects[o].beadtype, self.objects[o].index, self.objects[o].subindex)) # NAMES method set as an attribute ---------------------------- @property def names(self): """ return the names of objects sorted as index """ namesunsorted=namessorted=list(self.objects.keys()) nobj = len(namesunsorted) if nobj<1: return [] elif nobj<2: return namessorted else: for iobj in range(nobj): namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj] return namessorted # NBEADS method set as an attribute @property def nbeads(self): "return the number of beadtypes used" if len(self)>0: guess = max(len(self.count()),self.live.nbeads) return guess+1 if self.isspacefilled else guess else: return self.live.nbeads # COUNT method def count(self): """ count objects by type """ typlist = [] for o in self.names: if isinstance(self.objects[o].beadtype,list): typlist += self.objects[o].beadtype else: typlist.append(self.objects[o].beadtype) utypes = list(set(typlist)) c = [] for t in utypes: c.append((t,typlist.count(t))) return c # BEADTYPES property @property def beadtypes(self): """ list the beadtypes """ return [ x[0] for x in self.count() ] # DELETE method def delete(self,name): """ delete object """ if name in self.objects: kind = self.objects[name].kind del self.objects[name] self.nobjects -= 1 self.counter[kind] -= 1 self.counter["all"] -= 1 else: raise NameError("%s does not exist (use list()) to list valid objects" % name) # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # # HIGH-LEVEL METHODS # # # These methods are connect PIZZA.REGION() objects with their equivalent # as PIZZA.SCRIPT() and PIZZA.PIPESCRIPT() objects and methods. # # They are essential to PIZZA.REGION(). They do not have equivalent in # PIZZA.RASTER(). They use extensively the methods attached to : # PIZZA.REGION.LAMMPSGENERIC() # PIZZA.REGION.COREGEOMETRY() # # Current real-time rendering relies on # https://andeplane.github.io/atomify/ # which gives better results than # https://editor.lammps.org/ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- # EVALUATE algebraic operation on PIZZA.REGION() objects (operation on codes) def eval(self,expression,name=None,beadtype = None, fake=False,index = None,subindex = None): """ evaluates (i.e, combine scripts) an expression combining objects R= region(name="my region") R.eval(o1+o2+...,name='obj') R.eval(o1|o2|...,name='obj') R.name will be the resulting object of class region.eval (region.coregeometry) """ if not isinstance(expression, coregeometry): raise TypeError("the argument should be a region.coregeometry") # prepare object creation kind = "eval" self.counter["all"] += 1 self.counter[kind] +=1 if index is None: index = self.counter["all"] if subindex is None: subindex = self.counter[kind] # create the object E with E for Ellipsoid E = Evalgeometry((self.counter["all"],self.counter[kind]), index=index,subindex=subindex) # link expression to E if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply) if name is None: name = expression.name if name in self.name: raise NameError('the name "%s" is already used' % name) E.name = name E.SECTIONS = expression.SECTIONS E.USER = expression.USER if isinstance(E.SECTIONS,pipescript): # set beadtypes for all sections and scripts in the pipeline for i in E.SECTIONS.keys(): for j in range(len(E.SECTIONS[i])): E.SECTIONS[i].USER[j].beadtype = E.beadtype E.USER.beadtype = beadtype # Create the object if not fake if fake: self.counter["all"] -= 1 self.counter[kind] -= 1 return E else: self.objects[name] = E self.nobjects += 1 return None # PIPESCRIPT method generates a pipe for all objects and sections def pipescript(self,printflag=False,verbose=False,verbosity=0): printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity """ pipescript all objects in the region """ if len(self)<1: return pipescript() # execute all objects for myobj in self: if not isinstance(myobj,Collection): myobj.do(printflag=printflag,verbosity=verbosity) # concatenate all objects into a pipe script # for collections, only group is accepted liste = [x.SECTIONS["variables"] for x in self if not isinstance(x,Collection) and x.hasvariables] + \ [x.SECTIONS["region"] for x in self if not isinstance(x,Collection) and x.hasregion] + \ [x.SECTIONS["create"] for x in self if not isinstance(x,Collection) and x.hascreate] + \ [x.SECTIONS["group"] for x in self if not isinstance(x,Collection) and x.hasgroup] + \ [x.SECTIONS["setgroup"] for x in self if not isinstance(x,Collection) and x.hassetgroup] + \ [x.SECTIONS["move"] for x in self if not isinstance(x,Collection) and x.hasmove] # add the objects within the collection for x in self: if isinstance(x,Collection): liste += x.group() # add the eventual group for the collection liste += [x.SECTIONS["group"] for x in self if isinstance(x,Collection) and x.hasgroup] # chain all scripts return pipescript.join(liste) # SCRIPT add header and footer to PIPECRIPT def script(self,live=False, printflag=None, verbose=None, verbosity=None): """ script all objects in the region """ printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity s = self.pipescript(printflag=printflag,verbose=verbose,verbosity=verbosity).script(printflag=printflag,verbose=verbose,verbosity=verbosity) if self.isspacefilled: USERspacefilling =regiondata(**self.spacefilling) s = LammpsSpacefilling(**USERspacefilling)+s if live: beadtypes = self.beadtypes USER = regiondata(**self.live) USER.nbeads = self.nbeads USER.mass = "$" USER.pair_coeff = "$" # list beadtype and prepare mass, pair_coeff beadtypes = [ x[0] for x in self.count() ] if self.isspacefilled and self.spacefillingbeadtype not in beadtypes: beadtypes = [self.spacefillingbeadtype]+beadtypes for b in beadtypes: USER.mass += livetemplate["mass"] % b +"\n" USER.pair_coeff += livetemplate["pair_coeff"] %(b,b) +"\n" for b1 in beadtypes: for b2 in beadtypes: if b2>b1: USER.pair_coeff += livetemplate["pair_coeff"] %(b1,b2) +"\n" livemode = "dynamic" if self.hasfixmove else "static" USER.run =self.livelammps["options"][livemode]["run"] s = LammpsHeader(**USER)+s+LammpsFooter(**USER) return s # SCRIPTHEADERS add header scripts for initializing script, lattice, box for region def scriptHeaders(self, what=["init", "lattice", "box"], pipescript=False, **userdefinitions): """ Generate and return LAMMPS header scripts for initializing the simulation, defining the lattice, and specifying the simulation box for all region objects. Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", "box", "mass" and "preview". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. - pipescript (bool): If True, the generated scripts are combined with `|` instead of `+`. Default is False. Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`. Default is 1. - mass (real value or list): Sets the mass for each bead, overrriding `self.mass` Default is 1.0. Returns: - object: The combined header scripts as a single object. Header values can be overridden by updating `self.headersData`. Raises: - Exception: If no valid script options are provided in `what`. Example usage: sRheader = R.scriptHeaders("box").do() # Generate the box header script. sRallheaders = R.scriptHeaders(["init", "lattice", "box"]) # Generate all headers. Example usage without naming parameters: sRheader = R.scriptHeaders("box") # "what" specified as "box", nbeads defaults to 1. Example of overriding values sRheader = R.scriptHeaders("lattice",lattice_style = "$sq") # Generate the lattice header script with the overridden value. """ # handle overrides USERregion = self.headersData + regiondata(**userdefinitions) # Fix singletons if not isinstance(what, list): what = [what] # Generate the initialization script scripts = [] # Store all generated script objects here if "init" in what: scripts.append(LammpsHeaderInit(**USERregion)) # Generate the lattice script if "lattice" in what: scripts.append(LammpsHeaderLattice(**USERregion)) # Generate the box script if "box" in what: scripts.append(LammpsHeaderBox(**USERregion)) if self.isspacefilled: scripts.append(LammpsSpacefilling(**self.spacefilling)) # Generate the mass script if "mass" in what: scripts.append(LammpsHeaderMass(**USERregion)) # Generate the preview script if "preview" in what: scripts.append(LammpsFooterPreview(**USERregion)) if not scripts: raise Exception('nothing to do (use: "init", "lattice", "box", "mass" or "preview" within [ ])') # Combine the scripts based on the pipescript flag combined_script = scripts[0] # Initialize the combined script with the first element for script in scripts[1:]: if pipescript: # Combine scripts using the | operator, maintaining pipescript format combined_script = combined_script | script # p_ab = s_a | s_b or p_ab = s_a | p_b else: # Combine scripts using the + operator, maintaining regular script format combined_script = combined_script + script # s_ab = s_a + s_b return combined_script def pscriptHeaders(self, what=["init", "lattice", "box"], **userdefinitions): """ Surrogate method for generating LAMMPS pipescript headers. Calls the `scriptHeaders` method with `pipescript=True`. Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", and "box". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`. Default is 1. - mass (real value or list): Sets the mass for each bead, overrriding `self.mass` Default is 1.0. Returns: - object: The combined pipescript header scripts as a single object. """ # Call scriptHeaders with pipescript=True return self.scriptHeaders(what=what, pipescript=True, **userdefinitions) # DO METHOD = main static compiler def do(self, printflag=False, verbosity=1): """ execute the entire script """ return self.pipescript().do(printflag=printflag, verbosity=verbosity) # DOLIVE = fast code generation for online rendering def dolive(self): """ execute the entire script for online testing see: https://editor.lammps.org/ """ self.livelammps["file"] = self.script(live=True).tmpwrite() if not self.livelammps["active"]: livelammps(self.livelammps["URL"],new=0) self.livelammps["active"] = True return self.livelammps["file"]
Instance variables
var beadtypes
-
list the beadtypes
Expand source code
@property def beadtypes(self): """ list the beadtypes """ return [ x[0] for x in self.count() ]
var geometry
-
Display the dimensions and characteristics of the region and its objects.
Expand source code
@property def geometry(self): """Display the dimensions and characteristics of the region and its objects.""" details = f"Region: {self.name}\n" details += f"Total atoms: {self.natoms}\n" details += f"Span: width={self.spacefilling['fillingwidth']}, height={self.spacefilling['fillingheight']}, depth={self.spacefilling['fillingdepth']}\n" details += f"Box center: {self.center}\n" details += "Objects in the region:\n\n" for obj in self: details += "\n\n"+"-"*32+"\n" details += f"\nObject: {obj.name}\n" details += f"Type: {type(obj).__name__}\n" if hasattr(obj, 'geometry'): details += "\n"+"-"*32+"\n" details += obj.geometry else: details += "No geometry information available.\n" print(details)
var isspacefilled
-
Expand source code
@property def isspacefilled(self): return self.spacefilling["flag"]
var names
-
return the names of objects sorted as index
Expand source code
@property def names(self): """ return the names of objects sorted as index """ namesunsorted=namessorted=list(self.objects.keys()) nobj = len(namesunsorted) if nobj<1: return [] elif nobj<2: return namessorted else: for iobj in range(nobj): namessorted[self.objects[namesunsorted[iobj]].index-1] = namesunsorted[iobj] return namessorted
var natoms
-
Count the total number of atoms in all objects within the region.
Expand source code
@property def natoms(self): """Count the total number of atoms in all objects within the region.""" total_atoms = 0 for eachobj in self: total_atoms += eachobj.natoms return total_atoms
var nbeads
-
return the number of beadtypes used
Expand source code
@property def nbeads(self): "return the number of beadtypes used" if len(self)>0: guess = max(len(self.count()),self.live.nbeads) return guess+1 if self.isspacefilled else guess else: return self.live.nbeads
var spacefillingbeadtype
-
Expand source code
@property def spacefillingbeadtype(self): return self.spacefilling["fillingbeadtype"]
Methods
def block(self, xlo=-5, xhi=5, ylo=-5, yhi=5, zlo=-5, zhi=5, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a block region xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "block001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def block(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a block region xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "block001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "block" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object B with B for block obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density B = Block((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): B.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: B.beadtype = beadtype # bead type (if not defined, default index will apply) B.USER.ID = "$"+B.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [xlo, xhi, ylo, yhi, zlo, zhi] # args = [....] as defined in the class Block args_scaled = [ self.scale_and_translate(xlo, self.center[0]), self.scale_and_translate(xhi, self.center[0]), self.scale_and_translate(ylo, self.center[1]), self.scale_and_translate(yhi, self.center[1]), self.scale_and_translate(zlo, self.center[2]), self.scale_and_translate(zhi, self.center[2]) ] if self.units == "si": B.USER.args = args_scaled B.USER.args_siunits = args else: # "lattice" B.USER.args = args B.USER.args_siunits = args_scaled # geometry B.USER.geometry = ( f"Block Region: {B.name}\n" "Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi] = bounds of block in all dimensions" f"Coordinates (scaled): {B.USER.args}\n" f"Coordinates (SI units): {B.USER.args_siunits}\n" f"\talong x: [{B.USER.args[0]}, {B.USER.args[1]}]\n" f"\talong y: [{B.USER.args[2]}, {B.USER.args[3]}]\n" f"\talong z: [{B.USER.args[4]}, {B.USER.args[5]}]" ) # other attributes ------------------------------------- B.USER.beadtype = B.beadtype # beadtype to be used for create_atoms B.USER.side = B.sidearg(side) # extra parameter side B.USER.move = B.movearg(move) # move arg B.USER.units = B.unitsarg(units) # units B.USER.rotate = B.rotatearg(rotate) # rotate B.USER.open = B.openarg(open) # open # Create the object if not fake if fake: return B else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = B self.nobjects += 1 return None
def collection(self, *obj, name=None, beadtype=None, fake=False, index=None, subindex=None, **kwobj)
-
Expand source code
def collection(self,*obj,name=None,beadtype=None,fake=False, index = None,subindex = None, **kwobj): kind = "collection" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object C with C for collection C = Collection((index,subindex)) if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) # add objects C.collection = regioncollection(*obj,**kwobj) # apply modifications (beadtype, ismask) for o in C.collection.keys(): tmp = C.collection.getattr(o) if beadtype != None: tmp.beadtype = beadtype C.collection.setattr(o,tmp) # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None
def cone(self, dim='z', c1=0, c2=0, radlo=2, radhi=5, lo=-10, hi=10, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a cone region dim = "x" or "y" or "z" = axis of the cone note: USER, LAMMPS variables are not authorized here c1,c2 = coords of cone axis in other 2 dimensions (distance units) radlo,radhi = cone radii at lo and hi end (distance units) lo,hi = bounds of cone in dim (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "cone001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def cone(self,dim="z",c1=0,c2=0,radlo=2,radhi=5,lo=-10,hi=10, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a cone region dim = "x" or "y" or "z" = axis of the cone note: USER, LAMMPS variables are not authorized here c1,c2 = coords of cone axis in other 2 dimensions (distance units) radlo,radhi = cone radii at lo and hi end (distance units) lo,hi = bounds of cone in dim (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "cone001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "cone" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object C with C for cone obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density C = Cone((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) C.USER.ID = "$"+C.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [dim, c1, c2, radlo, radhi, lo, hi] # args = [....] as defined in the class Cone if dim == "x": # x-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[1]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[0]), self.scale_and_translate(hi, self.center[0]) ] elif dim == "y": # y-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[1]), self.scale_and_translate(hi, self.center[1]) ] else: # z-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[1]), self.scale_and_translate(radlo, 0), self.scale_and_translate(radhi, 0), self.scale_and_translate(lo, self.center[2]), self.scale_and_translate(hi, self.center[2]) ] if self.units == "si": C.USER.args = args_scaled C.USER.args_siunits = args else: # "lattice" C.USER.args = args C.USER.args_siunits = args_scaled # geometry C.USER.geometry = ( f"Cone Region: {C.name}\n" "Coordinates: [dim,c1,c2,radlo,radhi,lo,hi] = dimensions of cone\n" f"Coordinates (scaled): {C.USER.args}\n" f"Coordinates (SI units): {C.USER.args_siunits}\n" f"\tdim: {C.USER.args[0]}\n" f"\tc1: {C.USER.args[1]}\n" f"\tc2: {C.USER.args[2]}\n" f"\tradlo: {C.USER.args[3]}\n" f"\tradhi: {C.USER.args[4]}\n" f"\tlo: {C.USER.args[5]}\n" f"\thi: {C.USER.args[6]}" ) # other attributes ------------------------------------- C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms C.USER.side = C.sidearg(side) # extra parameter side C.USER.move = C.movearg(move) # move arg C.USER.units = C.unitsarg(units) # units C.USER.rotate = C.rotatearg(rotate) # rotate C.USER.open = C.openarg(open) # open # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None
def count(self)
-
count objects by type
Expand source code
def count(self): """ count objects by type """ typlist = [] for o in self.names: if isinstance(self.objects[o].beadtype,list): typlist += self.objects[o].beadtype else: typlist.append(self.objects[o].beadtype) utypes = list(set(typlist)) c = [] for t in utypes: c.append((t,typlist.count(t))) return c
def cylinder(self, dim='z', c1=0, c2=0, radius=4, lo=-10, hi=10, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a cylinder region dim = x or y or z = axis of cylinder c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) radius = cylinder radius (distance units) c1,c2, and radius can be a LAMMPS variable lo,hi = bounds of cylinder in dim (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "cylinder001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def cylinder(self,dim="z",c1=0,c2=0,radius=4,lo=-10,hi=10, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a cylinder region dim = x or y or z = axis of cylinder c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) radius = cylinder radius (distance units) c1,c2, and radius can be a LAMMPS variable lo,hi = bounds of cylinder in dim (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "cylinder001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "cylinder" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object C with C for cylinder obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density C = Cylinder((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): C.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: C.beadtype = beadtype # bead type (if not defined, default index will apply) C.USER.ID = "$"+C.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [dim, c1, c2, radius, lo, hi] # args = [....] as defined in the class Cylinder if dim == "x": # x-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[1]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[0]), self.scale_and_translate(hi, self.center[0]) ] elif dim == "y": # y-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[2]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[1]), self.scale_and_translate(hi, self.center[1]) ] else: # z-axis args_scaled = [ dim, self.scale_and_translate(c1, self.center[0]), self.scale_and_translate(c2, self.center[1]), self.scale_and_translate(radius, 0), self.scale_and_translate(lo, self.center[2]), self.scale_and_translate(hi, self.center[2]) ] if self.units == "si": C.USER.args = args_scaled C.USER.args_siunits = args else: # "lattice" C.USER.args = args C.USER.args_siunits = args_scaled # geometry C.USER.geometry = ( f"Cylinder Region: {C.name}\n" "Coordinates: [dim,c1,c2,radius,lo,hi] = dimensions of cylinder\n" f"Coordinates (scaled): {C.USER.args}\n" f"Coordinates (SI units): {C.USER.args_siunits}\n" f"\tdim: {C.USER.args[0]}\n" f"\tc1: {C.USER.args[1]}\n" f"\tc2: {C.USER.args[2]}\n" f"\tradius: {C.USER.args[3]}\n" f"\tlo: {C.USER.args[4]}\n" f"\thi: {C.USER.args[5]}" ) # other attributes ------------------------------------- C.USER.beadtype = C.beadtype # beadtype to be used for create_atoms C.USER.side = C.sidearg(side) # extra parameter side C.USER.move = C.movearg(move) # move arg C.USER.units = C.unitsarg(units) # units C.USER.rotate = C.rotatearg(rotate) # rotate C.USER.open = C.openarg(open) # open # Create the object if not fake if fake: return C else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = C self.nobjects += 1 return None
def delete(self, name)
-
delete object
Expand source code
def delete(self,name): """ delete object """ if name in self.objects: kind = self.objects[name].kind del self.objects[name] self.nobjects -= 1 self.counter[kind] -= 1 self.counter["all"] -= 1 else: raise NameError("%s does not exist (use list()) to list valid objects" % name)
def do(self, printflag=False, verbosity=1)
-
execute the entire script
Expand source code
def do(self, printflag=False, verbosity=1): """ execute the entire script """ return self.pipescript().do(printflag=printflag, verbosity=verbosity)
def dolive(self)
-
execute the entire script for online testing see: https://editor.lammps.org/
Expand source code
def dolive(self): """ execute the entire script for online testing see: https://editor.lammps.org/ """ self.livelammps["file"] = self.script(live=True).tmpwrite() if not self.livelammps["active"]: livelammps(self.livelammps["URL"],new=0) self.livelammps["active"] = True return self.livelammps["file"]
def ellipsoid(self, x=0, y=0, z=0, a=5, b=3, c=2, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates an ellipsoid region ellipsoid(x,y,z,a,b,c [,name=None,beadtype=None,property=value,…]) x,y,z = center of ellipsoid (distance units) a,b,c = half the length of the principal axes of the ellipsoid (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "ellipsoid001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index Examples: # example with variables created either at creation or later R = region(name="my region") R.ellipsoid(0, 0, 0, 1, 1, 1,name="E1",toto=3) repr(R.E1) R.E1.VARIABLES.a=1 R.E1.VARIABLES.b=2 R.E1.VARIABLES.c="(${a},${b},100)" R.E1.VARIABLES.d = '"%s%s" %("test",${c}) # note that test could be replaced by any function' # example with extra parameters R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",move=["left","${up}*3",None],up=0.1) R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})' R.E2.VARIABLES.a="${b}-5" R.E2.VARIABLES.b=5 R.E2.VARIABLES.c=100
Expand source code
def ellipsoid(self,x=0,y=0,z=0,a=5,b=3,c=2, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates an ellipsoid region ellipsoid(x,y,z,a,b,c [,name=None,beadtype=None,property=value,...]) x,y,z = center of ellipsoid (distance units) a,b,c = half the length of the principal axes of the ellipsoid (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "ellipsoid001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index Examples: # example with variables created either at creation or later R = region(name="my region") R.ellipsoid(0, 0, 0, 1, 1, 1,name="E1",toto=3) repr(R.E1) R.E1.VARIABLES.a=1 R.E1.VARIABLES.b=2 R.E1.VARIABLES.c="(${a},${b},100)" R.E1.VARIABLES.d = '"%s%s" %("test",${c}) # note that test could be replaced by any function' # example with extra parameters R.ellipsoid(0,0,0,1,1,1,name="E2",side="out",move=["left","${up}*3",None],up=0.1) R.E2.VARIABLES.left = '"swiggle(%s,%s,%s)"%(${a},${b},${c})' R.E2.VARIABLES.a="${b}-5" R.E2.VARIABLES.b=5 R.E2.VARIABLES.c=100 """ # prepare object creation kind = "ellipsoid" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object E with E for Ellipsoid obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density E = Ellipsoid((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): E.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply) E.USER.ID = "$"+E.name # add $ to prevent its execution # geometry args (2024-07-04) ------------------------------------- args = [x, y, z, a, b, c] # args = [....] as defined in the class Ellipsoid args_scaled = [ self.scale_and_translate(x, self.center[0]), self.scale_and_translate(y, self.center[1]), self.scale_and_translate(z, self.center[2]), self.scale_and_translate(a, 0), self.scale_and_translate(b, 0), self.scale_and_translate(c, 0) ] if self.units == "si": E.USER.args = args_scaled E.USER.args_siunits = args else: # "lattice" E.USER.args = args E.USER.args_siunits = args_scaled # geometry E.USER.geometry = ( f"Ellipsoid Region: {E.name}\n" "Coordinates: [x,y,z,a,b,c] = center and radii of ellipsoid\n" f"Coordinates (scaled): {E.USER.args}\n" f"Coordinates (SI units): {E.USER.args_siunits}\n" f"\tcenter: [{E.USER.args[0]}, {E.USER.args[1]}, {E.USER.args[2]}]\n" f"\ta: {E.USER.args[3]}\n" f"\tb: {E.USER.args[4]}\n" f"\tc: {E.USER.args[5]}" ) # other attributes ------------------------------------- E.USER.beadtype = E.beadtype # beadtype to be used for create_atoms E.USER.side = E.sidearg(side) # extra parameter side E.USER.move = E.movearg(move) # move arg E.USER.units = E.unitsarg(units) # units E.USER.rotate = E.rotatearg(rotate) # rotate E.USER.open = E.openarg(open) # open # Create the object if not fake if fake: return E else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = E self.nobjects += 1 return None
def eval(self, expression, name=None, beadtype=None, fake=False, index=None, subindex=None)
-
evaluates (i.e, combine scripts) an expression combining objects R= region(name="my region") R.eval(o1+o2+…,name='obj') R.eval(o1|o2|…,name='obj') R.name will be the resulting object of class region.eval (region.coregeometry)
Expand source code
def eval(self,expression,name=None,beadtype = None, fake=False,index = None,subindex = None): """ evaluates (i.e, combine scripts) an expression combining objects R= region(name="my region") R.eval(o1+o2+...,name='obj') R.eval(o1|o2|...,name='obj') R.name will be the resulting object of class region.eval (region.coregeometry) """ if not isinstance(expression, coregeometry): raise TypeError("the argument should be a region.coregeometry") # prepare object creation kind = "eval" self.counter["all"] += 1 self.counter[kind] +=1 if index is None: index = self.counter["all"] if subindex is None: subindex = self.counter[kind] # create the object E with E for Ellipsoid E = Evalgeometry((self.counter["all"],self.counter[kind]), index=index,subindex=subindex) # link expression to E if beadtype is not None: E.beadtype = beadtype # bead type (if not defined, default index will apply) if name is None: name = expression.name if name in self.name: raise NameError('the name "%s" is already used' % name) E.name = name E.SECTIONS = expression.SECTIONS E.USER = expression.USER if isinstance(E.SECTIONS,pipescript): # set beadtypes for all sections and scripts in the pipeline for i in E.SECTIONS.keys(): for j in range(len(E.SECTIONS[i])): E.SECTIONS[i].USER[j].beadtype = E.beadtype E.USER.beadtype = beadtype # Create the object if not fake if fake: self.counter["all"] -= 1 self.counter[kind] -= 1 return E else: self.objects[name] = E self.nobjects += 1 return None
def get(self, name)
-
returns the object
Expand source code
def get(self,name): """ returns the object """ if name in self.objects: return self.objects[name] else: raise NameError('the object "%s" does not exist, use list()' % name)
def group(self, obj, name=None, fake=False)
-
Expand source code
def group(self,obj,name=None,fake=False): pass
def hasattr(self, name)
-
return true if the object exist
Expand source code
def hasattr(self,name): """ return true if the object exist """ if not isinstance(name,str): raise TypeError("please provide a string") return name in self.objects
def intersect(self, *regID, name=None, beadtype=1, fake=False, index=None, subindex=None, **variables)
-
creates an intersection region intersect("reg-ID1","reg-ID2",name="myname",beadtype=1,…) reg-ID1,reg-ID2, … = IDs of regions to join together
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "intersect001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Expand source code
def intersect(self,*regID, name=None,beadtype=1,fake=False, index = None,subindex = None, **variables): """ creates an intersection region intersect("reg-ID1","reg-ID2",name="myname",beadtype=1,...) reg-ID1,reg-ID2, ... = IDs of regions to join together URL: https://docs.lammps.org/region.html Main properties = default value name = "intersect001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex """ kind = "intersect" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object I with I for intersect I = Intersect((self.counter["all"]+1,self.counter[kind]+1), index=index,subindex=subindex,**variables) # feed USER fields if name not in (None,""): I.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: I.beadtype = beadtype # bead type (if not defined, default index will apply) I.USER.ID = "$"+I.name # add $ to prevent its execution I.USER.side, I.USER.move, I.USER.units, I.USER.rotate, I.USER.open = "","","","","" # build arguments based on regID nregID = len(regID) if nregID<2: raise ValueError('two objects must be given at least for an intersection') args = [None] # the number of arguments is not known yet validID = range(nregID) for ireg in validID: if isinstance(regID[ireg],int): if regID[ireg] in validID: args.append(self.names[regID[ireg]]) else: raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}") elif isinstance(regID[ireg],str): if regID[ireg] in self: args.append(regID[ireg]) else: raise KeyError(f'the object "{regID[ireg]}" does not exist') else: raise KeyError(f"the {ireg+1}th object should be given as a string or an index") # prevent the creation of atoms (avoid duplicates) self.objects[regID[ireg]].FLAGSECTIONS["create"] = False args[0] = len(regID) I.USER.args = args # args = [....] as defined in the class Union # Create the object if not fake if fake: return I else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = I self.nobjects += 1 return None
def list(self)
-
list objects
Expand source code
def list(self): """ list objects """ fmt = "%%%ss:" % max(10,max([len(n) for n in self.names])+2) print('REGION container "%s" with %d objects' % (self.name,self.nobjects)) for o in self.objects.keys(): print(fmt % self.objects[o].name,"%-10s" % self.objects[o].kind, "(beadtype=%d,object index=[%d,%d])" % \ (self.objects[o].beadtype, self.objects[o].index, self.objects[o].subindex))
def pipescript(self, printflag=False, verbose=False, verbosity=0)
-
Expand source code
def pipescript(self,printflag=False,verbose=False,verbosity=0): printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity """ pipescript all objects in the region """ if len(self)<1: return pipescript() # execute all objects for myobj in self: if not isinstance(myobj,Collection): myobj.do(printflag=printflag,verbosity=verbosity) # concatenate all objects into a pipe script # for collections, only group is accepted liste = [x.SECTIONS["variables"] for x in self if not isinstance(x,Collection) and x.hasvariables] + \ [x.SECTIONS["region"] for x in self if not isinstance(x,Collection) and x.hasregion] + \ [x.SECTIONS["create"] for x in self if not isinstance(x,Collection) and x.hascreate] + \ [x.SECTIONS["group"] for x in self if not isinstance(x,Collection) and x.hasgroup] + \ [x.SECTIONS["setgroup"] for x in self if not isinstance(x,Collection) and x.hassetgroup] + \ [x.SECTIONS["move"] for x in self if not isinstance(x,Collection) and x.hasmove] # add the objects within the collection for x in self: if isinstance(x,Collection): liste += x.group() # add the eventual group for the collection liste += [x.SECTIONS["group"] for x in self if isinstance(x,Collection) and x.hasgroup] # chain all scripts return pipescript.join(liste)
def plane(self, px=0, py=0, pz=0, nx=0, ny=0, nz=1, name=None, beadtype=None, fake=False, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a plane region px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "plane001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def plane(self,px=0,py=0,pz=0,nx=0,ny=0,nz=1, name=None,beadtype=None,fake=False, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a plane region px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "plane001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "plane" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object P with P for plane P = Plane((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=self.mass, density=self.density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): P.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply) P.USER.ID = "$"+P.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [px, py, pz, nx, ny, nz] # args = [....] as defined in the class Plane args_scaled = [ self.scale_and_translate(px, self.center[0]), self.scale_and_translate(py, self.center[1]), self.scale_and_translate(pz, self.center[2]), self.scale_and_translate(nx, 0), self.scale_and_translate(ny, 0), self.scale_and_translate(nz, 0) ] if self.units == "si": P.USER.args = args_scaled P.USER.args_siunits = args else: # "lattice" P.USER.args = args P.USER.args_siunits = args_scaled # geometry P.USER.geometry = ( f"Plane Region: {P.name}\n" "Coordinates: [px,py,pz,nx,ny,nz] = point and normal vector of plane\n" f"Coordinates (scaled): {P.USER.args}\n" f"Coordinates (SI units): {P.USER.args_siunits}\n" f"\tpoint: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}]\n" f"\tnormal: [{P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]" ) # other attributes --------------------------- P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms P.USER.side = P.sidearg(side) # extra parameter side P.USER.move = P.movearg(move) # move arg P.USER.units = P.unitsarg(units) # units P.USER.rotate = P.rotatearg(rotate) # rotate P.USER.open = P.openarg(open) # open # Create the object if not fake if fake: return P else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = P self.nobjects += 1 return None
def prism(self, xlo=-5, xhi=5, ylo=-5, yhi=5, zlo=-5, zhi=5, xy=1, xz=1, yz=1, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a prism region xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units) xy = distance to tilt y in x direction (distance units) xz = distance to tilt z in x direction (distance units) yz = distance to tilt z in y direction (distance units)
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "prism001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def prism(self,xlo=-5,xhi=5,ylo=-5,yhi=5,zlo=-5,zhi=5,xy=1,xz=1,yz=1, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a prism region xlo,xhi,ylo,yhi,zlo,zhi = bounds of untilted prism (distance units) xy = distance to tilt y in x direction (distance units) xz = distance to tilt z in x direction (distance units) yz = distance to tilt z in y direction (distance units) URL: https://docs.lammps.org/region.html Main properties = default value name = "prism001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "prism" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object P with P for prism obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density P = Prism((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): P.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: P.beadtype = beadtype # bead type (if not defined, default index will apply) P.USER.ID = "$"+P.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz] # args = [....] as defined in the class Prism args_scaled = [ self.scale_and_translate(xlo, self.center[0]), self.scale_and_translate(xhi, self.center[0]), self.scale_and_translate(ylo, self.center[1]), self.scale_and_translate(yhi, self.center[1]), self.scale_and_translate(zlo, self.center[2]), self.scale_and_translate(zhi, self.center[2]), self.scale_and_translate(xy, 0), self.scale_and_translate(xz, 0), self.scale_and_translate(yz, 0) ] if self.units == "si": P.USER.args = args_scaled P.USER.args_siunits = args else: # "lattice" P.USER.args = args P.USER.args_siunits = args_scaled # geometry P.USER.geometry = ( f"Prism Region: {P.name}\n" "Coordinates: [xlo,xhi,ylo,yhi,zlo,zhi,xy,xz,yz] = bounds and tilts of prism\n" f"Coordinates (scaled): {P.USER.args}\n" f"Coordinates (SI units): {P.USER.args_siunits}\n" f"\tbounds: [{P.USER.args[0]}, {P.USER.args[1]}, {P.USER.args[2]}, {P.USER.args[3]}, {P.USER.args[4]}, {P.USER.args[5]}]\n" f"\ttilts: [{P.USER.args[6]}, {P.USER.args[7]}, {P.USER.args[8]}]" ) # other attributes --------------------------- P.USER.beadtype = P.beadtype # beadtype to be used for create_atoms P.USER.side = P.sidearg(side) # extra parameter side P.USER.move = P.movearg(move) # move arg P.USER.units = P.unitsarg(units) # units P.USER.rotate = P.rotatearg(rotate) # rotate P.USER.open = P.openarg(open) # open # Create the object if not fake if fake: return P else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = P self.nobjects += 1 return None
def pscriptHeaders(self, what=['init', 'lattice', 'box'], **userdefinitions)
-
Surrogate method for generating LAMMPS pipescript headers. Calls the
scriptHeaders
method withpipescript=True
.Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", and "box". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than
self.nbeads
. Default is 1. - mass (real value or list): Sets the mass for each bead, overrridingself.mass
Default is 1.0. Returns: - object: The combined pipescript header scripts as a single object.Expand source code
def pscriptHeaders(self, what=["init", "lattice", "box"], **userdefinitions): """ Surrogate method for generating LAMMPS pipescript headers. Calls the `scriptHeaders` method with `pipescript=True`. Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", and "box". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`. Default is 1. - mass (real value or list): Sets the mass for each bead, overrriding `self.mass` Default is 1.0. Returns: - object: The combined pipescript header scripts as a single object. """ # Call scriptHeaders with pipescript=True return self.scriptHeaders(what=what, pipescript=True, **userdefinitions)
def scale_and_translate(self, value, offset=0)
-
Scale and translate a value or encapsulate the formula within a string.
If self.regionunits is "si", only the offset is applied without scaling. Otherwise, scaling and translation are performed based on self.units ("si" or "lattice").
Parameters
value (str or float): The value or formula to be scaled and translated. offset (float, optional): The offset to apply. Defaults to 0.
Returns
str
orfloat
- The scaled and translated value or formula.
Expand source code
def scale_and_translate(self, value, offset=0): """ Scale and translate a value or encapsulate the formula within a string. If self.regionunits is "si", only the offset is applied without scaling. Otherwise, scaling and translation are performed based on self.units ("si" or "lattice"). Parameters: value (str or float): The value or formula to be scaled and translated. offset (float, optional): The offset to apply. Defaults to 0. Returns: str or float: The scaled and translated value or formula. """ if self.regionunits == "si": # Only apply offset without scaling if isinstance(value, str): if offset: translated = f"({value}) - {offset}" else: translated = f"{value}" return translated else: if offset: return value - offset else: return value else: # Existing behavior based on self.units if isinstance(value, str): if offset: translated = f"({value}) - {offset}" else: translated = f"{value}" if self.units == "si": return f"({translated}) / {self.lattice_scale} + {offset / self.lattice_scale}" else: # "lattice" return f"({translated}) * {self.lattice_scale} + {offset * self.lattice_scale}" else: if offset: translated = value - offset else: translated = value if self.units == "si": return translated / self.lattice_scale + (offset / self.lattice_scale) else: # "lattice" return translated * self.lattice_scale + (offset * self.lattice_scale)
def scatter(self, E, name='emulsion', beadtype=None)
-
Parameters
E
:scatter
oremulsion object
- codes for x,y,z and r.
name
:string
, optional- name of the collection. The default is "emulsion".
beadtype
:integer
, optional- for all objects. The default is 1.
Raises
TypeError
- Return an error of the object is not a scatter type.
Returns
None.
Expand source code
def scatter(self, E, name="emulsion", beadtype=None, ): """ Parameters ---------- E : scatter or emulsion object codes for x,y,z and r. name : string, optional name of the collection. The default is "emulsion". beadtype : integer, optional for all objects. The default is 1. Raises ------ TypeError Return an error of the object is not a scatter type. Returns ------- None. """ if isinstance(E,scatter): collect = {} for i in range(E.n): b = E.beadtype[i] if beadtype==None else beadtype nameobj = "glob%02d" % i collect[nameobj] = self.sphere(E.x[i],E.y[i],E.z[i],E.r[i], name=nameobj,beadtype=b,fake=True) self.collection(**collect,name=name) else: raise TypeError("the first argument must be an emulsion object")
def script(self, live=False, printflag=None, verbose=None, verbosity=None)
-
script all objects in the region
Expand source code
def script(self,live=False, printflag=None, verbose=None, verbosity=None): """ script all objects in the region """ printflag = self.printflag if printflag is None else printflag verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity s = self.pipescript(printflag=printflag,verbose=verbose,verbosity=verbosity).script(printflag=printflag,verbose=verbose,verbosity=verbosity) if self.isspacefilled: USERspacefilling =regiondata(**self.spacefilling) s = LammpsSpacefilling(**USERspacefilling)+s if live: beadtypes = self.beadtypes USER = regiondata(**self.live) USER.nbeads = self.nbeads USER.mass = "$" USER.pair_coeff = "$" # list beadtype and prepare mass, pair_coeff beadtypes = [ x[0] for x in self.count() ] if self.isspacefilled and self.spacefillingbeadtype not in beadtypes: beadtypes = [self.spacefillingbeadtype]+beadtypes for b in beadtypes: USER.mass += livetemplate["mass"] % b +"\n" USER.pair_coeff += livetemplate["pair_coeff"] %(b,b) +"\n" for b1 in beadtypes: for b2 in beadtypes: if b2>b1: USER.pair_coeff += livetemplate["pair_coeff"] %(b1,b2) +"\n" livemode = "dynamic" if self.hasfixmove else "static" USER.run =self.livelammps["options"][livemode]["run"] s = LammpsHeader(**USER)+s+LammpsFooter(**USER) return s
def scriptHeaders(self, what=['init', 'lattice', 'box'], pipescript=False, **userdefinitions)
-
Generate and return LAMMPS header scripts for initializing the simulation, defining the lattice, and specifying the simulation box for all region objects.
Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", "box", "mass" and "preview". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. - pipescript (bool): If True, the generated scripts are combined with
|
instead of+
. Default is False.Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than
self.nbeads
. Default is 1. - mass (real value or list): Sets the mass for each bead, overrridingself.mass
Default is 1.0.Returns: - object: The combined header scripts as a single object. Header values can be overridden by updating
self.headersData
.Raises: - Exception: If no valid script options are provided in
what
.Example usage: sRheader = R.scriptHeaders("box").do() # Generate the box header script. sRallheaders = R.scriptHeaders(["init", "lattice", "box"]) # Generate all headers.
Example usage without naming parameters: sRheader = R.scriptHeaders("box") # "what" specified as "box", nbeads defaults to 1. Example of overriding values sRheader = R.scriptHeaders("lattice",lattice_style = "$sq") # Generate the lattice header script with the overridden value.
Expand source code
def scriptHeaders(self, what=["init", "lattice", "box"], pipescript=False, **userdefinitions): """ Generate and return LAMMPS header scripts for initializing the simulation, defining the lattice, and specifying the simulation box for all region objects. Parameters: - what (list of str): Specifies which scripts to generate. Options are "init", "lattice", "box", "mass" and "preview". Multiple scripts can be generated by passing a list of these options. Default is ["init", "lattice", "box"]. - pipescript (bool): If True, the generated scripts are combined with `|` instead of `+`. Default is False. Property/pair value - nbeads (int): Specifies the number of beads, overriding the default if larger than `self.nbeads`. Default is 1. - mass (real value or list): Sets the mass for each bead, overrriding `self.mass` Default is 1.0. Returns: - object: The combined header scripts as a single object. Header values can be overridden by updating `self.headersData`. Raises: - Exception: If no valid script options are provided in `what`. Example usage: sRheader = R.scriptHeaders("box").do() # Generate the box header script. sRallheaders = R.scriptHeaders(["init", "lattice", "box"]) # Generate all headers. Example usage without naming parameters: sRheader = R.scriptHeaders("box") # "what" specified as "box", nbeads defaults to 1. Example of overriding values sRheader = R.scriptHeaders("lattice",lattice_style = "$sq") # Generate the lattice header script with the overridden value. """ # handle overrides USERregion = self.headersData + regiondata(**userdefinitions) # Fix singletons if not isinstance(what, list): what = [what] # Generate the initialization script scripts = [] # Store all generated script objects here if "init" in what: scripts.append(LammpsHeaderInit(**USERregion)) # Generate the lattice script if "lattice" in what: scripts.append(LammpsHeaderLattice(**USERregion)) # Generate the box script if "box" in what: scripts.append(LammpsHeaderBox(**USERregion)) if self.isspacefilled: scripts.append(LammpsSpacefilling(**self.spacefilling)) # Generate the mass script if "mass" in what: scripts.append(LammpsHeaderMass(**USERregion)) # Generate the preview script if "preview" in what: scripts.append(LammpsFooterPreview(**USERregion)) if not scripts: raise Exception('nothing to do (use: "init", "lattice", "box", "mass" or "preview" within [ ])') # Combine the scripts based on the pipescript flag combined_script = scripts[0] # Initialize the combined script with the first element for script in scripts[1:]: if pipescript: # Combine scripts using the | operator, maintaining pipescript format combined_script = combined_script | script # p_ab = s_a | s_b or p_ab = s_a | p_b else: # Combine scripts using the + operator, maintaining regular script format combined_script = combined_script + script # s_ab = s_a + s_b return combined_script
def set(self, name, value)
-
set field and value
Expand source code
def set(self,name,value): """ set field and value """ if isinstance(value,list) and len(value)==0: if name not in self.objects: raise NameError('the object "%s" does not exist, use list()' % name) self.delete(name) elif isinstance(value,coregeometry): if name in self.objects: self.delete(name) if isinstance(value.SECTIONS,pipescript) or isinstance(value,Evalgeometry): self.eval(deepduplicate(value),name) # not a scalar else: # scalar self.objects[name] = deepduplicate(value) self.objects[name].name = name self.nobjects += 1 self.counter["all"] += 1 self.objects[name].index = self.counter["all"] self.counter[value.kind] += 1
def sphere(self, x=0, y=0, z=0, radius=3, name=None, beadtype=None, fake=False, mass=None, density=None, side=None, units=None, move=None, rotate=None, open=None, index=None, subindex=None, **variables)
-
creates a sphere region x,y,z = center of sphere (distance units) radius = radius of sphere (distance units) x,y,z, and radius can be a variable
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "sphere001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid()
Expand source code
def sphere(self,x=0,y=0,z=0,radius=3, name=None,beadtype=None,fake=False, mass=None, density=None, side=None,units=None,move=None,rotate=None,open=None, index = None,subindex = None, **variables ): """ creates a sphere region x,y,z = center of sphere (distance units) radius = radius of sphere (distance units) x,y,z, and radius can be a variable URL: https://docs.lammps.org/region.html Main properties = default value name = "sphere001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex Extra properties side = "in|out" units = "lattice|box" ("box" is forced if regionunits=="si") move = "[${v1} ${v2} ${v3}]" or [v1,v2,v3] as a list with v1,v2,v3 equal-style variables for x,y,z displacement of region over time (distance units) rotate = string or 1x7 list (see move) coding for vtheta Px Py Pz Rx Ry Rz vtheta = equal-style variable for rotation of region over time (in radians) Px,Py,Pz = origin for axis of rotation (distance units) Rx,Ry,Rz = axis of rotation vector open = integer from 1-6 corresponding to face index See examples for elliposid() """ # prepare object creation kind = "sphere" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 units = "box" if self.regionunits=="si" and units is None else units # force box units of regionunits=="si" # create the object S with S for sphere obj_mass = mass if mass is not None else self.mass obj_density = density if density is not None else self.density S = Sphere((self.counter["all"]+1,self.counter[kind]+1), spacefilling=self.isspacefilled, # added on 2023-08-11 mass=obj_mass, density=obj_density, # added on 2024-06-14 index=index,subindex=subindex, lattice_style=self.lattice_style, lattice_scale=self.lattice_scale, lattice_scale_siunits=self.lattice_scale_siunits, **variables) # feed USER fields if name not in (None,""): S.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: S.beadtype = beadtype # bead type (if not defined, default index will apply) S.USER.ID = "$"+S.name # add $ to prevent its execution # geometry args (2024-07-04) --------------------------- args = [x, y, z, radius] # args = [....] as defined in the class Sphere args_scaled = [ self.scale_and_translate(x, self.center[0]), self.scale_and_translate(y, self.center[1]), self.scale_and_translate(z, self.center[2]), self.scale_and_translate(radius, 0) ] if self.units == "si": S.USER.args = args_scaled S.USER.args_siunits = args else: # "lattice" S.USER.args = args S.USER.args_siunits = args_scaled # geometry S.USER.geometry = ( f"Sphere Region: {S.name}\n" "Coordinates: [x,y,z,radius] = center and radius of sphere\n" f"Coordinates (scaled): {S.USER.args}\n" f"Coordinates (SI units): {S.USER.args_siunits}\n" f"\tcenter: [{S.USER.args[0]}, {S.USER.args[1]}, {S.USER.args[2]}]\n" f"\tradius: {S.USER.args[3]}" ) # other attributes --------------------------- S.USER.beadtype = S.beadtype # beadtype to be used for create_atoms S.USER.side = S.sidearg(side) # extra parameter side S.USER.move = S.movearg(move) # move arg S.USER.units = S.unitsarg(units) # units S.USER.rotate = S.rotatearg(rotate) # rotate S.USER.open = S.openarg(open) # open # Create the object if not fake if fake: return S else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = S self.nobjects += 1 return None
def union(self, *regID, name=None, beadtype=1, fake=False, index=None, subindex=None, **variables)
-
creates a union region union("reg-ID1","reg-ID2",name="myname",beadtype=1,…) reg-ID1,reg-ID2, … = IDs of regions to join together
URL: <https://docs.lammps.org/region.html> Main properties = default value name = "union001" beadtype = 1 fake = False (use True to test the execution)
index, subindex = object index and subindex
Expand source code
def union(self,*regID, name=None,beadtype=1,fake=False, index = None,subindex = None, **variables): """ creates a union region union("reg-ID1","reg-ID2",name="myname",beadtype=1,...) reg-ID1,reg-ID2, ... = IDs of regions to join together URL: https://docs.lammps.org/region.html Main properties = default value name = "union001" beadtype = 1 fake = False (use True to test the execution) index, subindex = object index and subindex """ kind = "union" if index is None: index = self.counter["all"]+1 if subindex is None: subindex = self.counter[kind]+1 # create the object U with U for union U = Union((self.counter["all"]+1,self.counter[kind]+1), index=index,subindex=subindex,**variables) # feed USER fields if name not in (None,""): U.name = name # object name (if not defined, default name will be used) if name in self.name: raise NameError('the name "%s" is already used' % name) if beadtype is not None: U.beadtype = beadtype # bead type (if not defined, default index will apply) U.USER.ID = "$"+U.name # add $ to prevent its execution U.USER.side, U.USER.move, U.USER.units, U.USER.rotate, U.USER.open = "","","","","" # build arguments based on regID nregID = len(regID) if nregID<2: raise ValueError('two objects must be given at least for an union') args = [None] # the number of arguments is not known yet validID = range(nregID) for ireg in validID: if isinstance(regID[ireg],int): if regID[ireg] in validID: args.append(self.names[regID[ireg]]) else: raise IndexError(f"the index {regID[ireg]} exceeds the number of objects {len(self)}") elif isinstance(regID[ireg],str): if regID[ireg] in self: args.append(regID[ireg]) else: raise KeyError(f'the object "{regID[ireg]}" does not exist') else: raise KeyError(f"the {ireg+1}th object should be given as a string or an index") # prevent the creation of atoms merged (avoid duplicates) self.objects[regID[ireg]].FLAGSECTIONS["create"] = False args[0] = len(regID) U.USER.args = args # args = [....] as defined in the class Union # Create the object if not fake if fake: return U else: self.counter["all"] += 1 self.counter[kind] +=1 self.objects[name] = U self.nobjects += 1 return None
class regioncollection (*obj, **kwobj)
-
regioncollection class container (not to be called directly)
constructor
Expand source code
class regioncollection(struct): """ regioncollection class container (not to be called directly) """ _type = "collect" # object type _fulltype = "Collections" # full name _ftype = "collection" # field name def __init__(self,*obj,**kwobj): # store the objects with their alias super().__init__(**kwobj) # store objects with their real names for o in obj: if isinstance(o,region): s = struct.dict2struct(o.objects) list_s = s.keys() for i in range(len(list_s)): self.setattr(list_s[i], s[i].copy()) elif o!=None: self.setattr(o.name, o.copy())
Ancestors
- pizza.private.mstruct.struct
class regiondata (sortdefinitions=False, **kwargs)
-
class of script parameters Typical constructor: DEFINITIONS = regiondata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it
constructor
Expand source code
class regiondata(paramauto): """ class of script parameters Typical constructor: DEFINITIONS = regiondata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it """ _type = "RD" _fulltype = "region data" _ftype = "definition" def generatorforlammps(self,verbose=False,hasvariables=False): """ generate LAMMPS code from regiondata (struct) generatorforlammps(verbose,hasvariables) hasvariables = False is used to prevent a call of generatorforLammps() for scripts others than LammpsGeneric ones """ nk = len(self) if nk>0: self.sortdefinitions(raiseerror=False) s = self.tostruct() ik = 0 fmt = "variable %s equal %s" cmd = "\n#"+"_"*40+"\n"+f"#[{str(datetime.now())}]\n" if verbose else "" cmd += f"\n# Definition of {nk} variables (URL: https://docs.lammps.org/variable.html)\n" if hasvariables: for k in s.keys(): ik += 1 end = "\n" if ik<nk else "\n"*2 v = getattr(s,k) if v is None: v = "NULL" if isinstance(v,(int,float)) or v == None: cmd += fmt % (k,v)+end elif isinstance(v,str): cmd += fmt % (k,f'{v}')+end elif isinstance(v,(list,tuple)): cmd += fmt % (k,span(v))+end else: raise TypeError(f"unsupported type for the variable {k} set to {v}") if verbose: cmd += "#"+"_"*40+"\n" return cmd
Ancestors
- pizza.private.mstruct.paramauto
- pizza.private.mstruct.param
- pizza.private.mstruct.struct
Subclasses
Methods
def generatorforlammps(self, verbose=False, hasvariables=False)
-
generate LAMMPS code from regiondata (struct) generatorforlammps(verbose,hasvariables) hasvariables = False is used to prevent a call of generatorforLammps() for scripts others than LammpsGeneric ones
Expand source code
def generatorforlammps(self,verbose=False,hasvariables=False): """ generate LAMMPS code from regiondata (struct) generatorforlammps(verbose,hasvariables) hasvariables = False is used to prevent a call of generatorforLammps() for scripts others than LammpsGeneric ones """ nk = len(self) if nk>0: self.sortdefinitions(raiseerror=False) s = self.tostruct() ik = 0 fmt = "variable %s equal %s" cmd = "\n#"+"_"*40+"\n"+f"#[{str(datetime.now())}]\n" if verbose else "" cmd += f"\n# Definition of {nk} variables (URL: https://docs.lammps.org/variable.html)\n" if hasvariables: for k in s.keys(): ik += 1 end = "\n" if ik<nk else "\n"*2 v = getattr(s,k) if v is None: v = "NULL" if isinstance(v,(int,float)) or v == None: cmd += fmt % (k,v)+end elif isinstance(v,str): cmd += fmt % (k,f'{v}')+end elif isinstance(v,(list,tuple)): cmd += fmt % (k,span(v))+end else: raise TypeError(f"unsupported type for the variable {k} set to {v}") if verbose: cmd += "#"+"_"*40+"\n" return cmd
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
- pizza.forcefield.none
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Class variables
var description
var name
var userid
var version
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
- pizza.forcefield.tlsph
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Class variables
var description
var name
var userid
var version
class scatter
-
generic top scatter class
The scatter class provides an easy constructor to distribute in space objects according to their positions x,y,z size r (radius) and beadtype.
The class is used to derive emulsions.
Returns
None.
Expand source code
class scatter(): """ generic top scatter class """ def __init__(self): """ The scatter class provides an easy constructor to distribute in space objects according to their positions x,y,z size r (radius) and beadtype. The class is used to derive emulsions. Returns ------- None. """ self.x = np.array([],dtype=int) self.y = np.array([],dtype=int) self.z = np.array([],dtype=int) self.r = np.array([],dtype=int) self.beadtype = [] @property def n(self): return len(self.x) def pairdist(self,x,y,z): """ pair distance to the surface of all disks/spheres """ if self.n==0: return np.Inf else: return np.sqrt((x-self.x)**2+(y-self.y)**2+(z-self.z)**2)-self.r
Subclasses
Instance variables
var n
-
Expand source code
@property def n(self): return len(self.x)
Methods
def pairdist(self, x, y, z)
-
pair distance to the surface of all disks/spheres
Expand source code
def pairdist(self,x,y,z): """ pair distance to the surface of all disks/spheres """ if self.n==0: return np.Inf else: return np.sqrt((x-self.x)**2+(y-self.y)**2+(z-self.z)**2)-self.r
class script (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
script: A Core Class for Flexible LAMMPS Script Generation
The
script
class provides a flexible framework for generating dynamic LAMMPS script sections. It supports various LAMMPS sections such as "GLOBAL", "INITIALIZE", "GEOMETRY", "INTERACTIONS", and more, while allowing users to define custom sections with variable definitions, templates, and dynamic evaluation of script content.Key Features:
- Dynamic Script Generation: Easily define and manage script sections, using templates and definitions to dynamically generate LAMMPS-compatible scripts.
- Script Concatenation: Combine multiple script sections while managing variable precedence and ensuring that definitions propagate as expected.
- Flexible Variable Management: Separate
DEFINITIONS
for static variables andUSER
for user-defined variables, with clear rules for inheritance and precedence. - Operators for Advanced Script Handling: Use
+
,&
,>>
,|
, and**
operators for script merging, static execution, right-shifting of definitions, and more. - Pipeline Support: Integrate scripts into pipelines, with full support for staged execution, variable inheritance, and reordering of script sections.
Practical Use Cases:
- LAMMPS Automation: Automate the generation of complex LAMMPS scripts by defining reusable script sections with variables and templates.
- Multi-Step Simulations: Manage multi-step simulations by splitting large scripts into smaller, manageable sections and combining them as needed.
- Advanced Script Control: Dynamically modify script behavior by overriding variables or using advanced operators to concatenate, pipe, or merge scripts.
Methods:
init(self, persistentfile=True, persistentfolder=None, printflag=False, verbose=False, **userdefinitions): Initializes a new
script
object, with optional user-defined variables passed asuserdefinitions
.do(self, printflag=None, verbose=None): Generates the LAMMPS script based on the current configuration, evaluating templates and definitions to produce the final output.
script(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Generate the final LAMMPS script from the pipeline or a subset of the pipeline.
add(self, s): Overloads the
+
operator to concatenate script objects, merging definitions and templates while maintaining variable precedence.and(self, s): Overloads the
&
operator for static execution, combining the generated scripts of two script objects without merging their definitions.mul(self, ntimes): Overloads the
*
operator to repeat the scriptntimes
, returning a new script object with repeated sections.pow(self, ntimes): Overloads the
**
operator to concatenate the script with itselfntimes
, similar to the&
operator, but repeated.or(self, pipe): Overloads the pipe (
|
) operator to integrate the script into a pipeline, returning apipescript
object.write(self, file, printflag=True, verbose=False): Writes the generated script to a file, including headers with metadata.
tmpwrite(self): Writes the script to a temporary file, creating both a full version and a clean version without comments.
printheader(txt, align="^", width=80, filler="~"): Static method to print formatted headers, useful for organizing output.
copy(self): Creates a shallow copy of the script object.
deepcopy(self, memo): Creates a deep copy of the script object, duplicating all internal variables.
Additional Features:
- Customizable Templates: Use string templates with variable placeholders
(e.g.,
${value}
) to dynamically generate script lines. - Static and User-Defined Variables: Manage global
DEFINITIONS
for static variables andUSER
variables for dynamic, user-defined settings. - Advanced Operators: Leverage a range of operators (
+
,>>
,|
,&
) to manipulate script content, inherit definitions, and control variable precedence. - Verbose Output: Control verbosity to include detailed comments and debugging information in generated scripts.
Original Content:
The
script
class supports LAMMPS section generation and variable management with features such as: - Dynamic Evaluation of Scripts: Definitions and templates are evaluated at runtime, allowing for flexible and reusable scripts. - Inheritance of Definitions: Variable definitions can be inherited from previous sections, allowing for modular script construction. - Precedence Rules for Variables: When scripts are concatenated, definitions from the left take precedence, ensuring that the first defined values are preserved. - Instance and Global Variables: Instance variables are set via theUSER
object, while global variables (shared across instances) are managed inDEFINITIONS
. - Script Pipelines: Scripts can be integrated into pipelines for sequential execution and dynamic variable propagation. - Flexible Output Formats: Lists are expanded into space-separated strings, while tuples are expanded with commas, making the output more readable.Example Usage:
from pizza.script import script, scriptdata class example_section(script): DEFINITIONS = scriptdata( X = 10, Y = 20, result = "${X} + ${Y}" ) TEMPLATE = "${result} = ${X} + ${Y}" s1 = example_section() s1.USER.X = 5 s1.do()
The output for
s1.do()
will be:25 = 5 + 20
With additional sections, scripts can be concatenated and executed as a single entity, with inheritance of variables and customizable behavior.
-------------------------------------- OVERVIEW ANDE DETAILED FEATURES -------------------------------------- The class script enables to generate dynamically LAMMPS sections "NONE","GLOBAL","INITIALIZE","GEOMETRY","DISCRETIZATION", "BOUNDARY","INTERACTIONS","INTEGRATION","DUMP","STATUS","RUN" # %% This the typical construction for a class class XXXXsection(script): "" " LAMMPS script: XXXX session "" " name = "XXXXXX" description = name+" section" position = 0 section = 0 userid = "example" version = 0.1 DEFINITIONS = scriptdata( value= 1, expression= "${value+1}", text= "$my text" ) TEMPLATE = "" " # :UNDEF SECTION: # to be defined LAMMPS code with ${value}, ${expression}, ${text} "" " DEFINTIONS can be inherited from a previous section DEFINITIONS = previousection.DEFINTIONS + scriptdata( value= 1, expression= "${value+1}", text= "$my text" ) Recommandation: Split a large script into a small classes or actions An example of use could be: move1 = translation(displacement=10)+rotation(angle=30) move2 = shear(rate=0.1)+rotation(angle=20) bigmove = move1+move2+move1 script = bigmove.do() generates the script NOTE1: Use the print() and the method do() to get the script interpreted NOTE2: DEFINITIONS can be pretified using DEFINITIONS.generator() NOTE3: Variables can extracted from a template using TEMPLATE.scan() NOTE4: Scripts can be joined (from top down to bottom). The first definitions keep higher precedence. Please do not use a variable twice with different contents. myscript = s1 + s2 + s3 will propagate the definitions without overwritting previous values). myscript will be defined as s1 (same name, position, userid, etc.) myscript += s appends the script section s to myscript NOTE5: rules of precedence when script are concatenated The attributes from the class (name, description...) are kept from the left The values of the right overwrite all DEFINITIONS NOTE6: user variables (instance variables) can set with USER or at the construction myclass_instance = myclass(myvariable = myvalue) myclass_instance.USER.myvariable = myvalue NOTE7: how to change variables for all instances at once? In the example below, let x is a global variable (instance independent) and y a local variable (instance dependent) instance1 = myclass(y=1) --> y=1 in instance1 instance2 = myclass(y=2) --> y=2 in instance2 instance3.USER.y=3 --> y=3 in instance3 instance1.DEFINITIONS.x = 10 --> x=10 in all instances (1,2,3) If x is also defined in the USER section, its value will be used Setting instance3.USER.x = 30 will assign x=30 only in instance3 NOTE8: if a the script is used with different values for a same parameter use the operator & to concatenate the results instead of the script example: load(file="myfile1") & load(file="myfile2) & load(file="myfile3")+... NOTE9: lists (e.g., [1,2,'a',3] are expanded ("1 2 a 3") tuples (e.g. (1,2)) are expanded ("1,2") It is easier to read ["lost","ignore"] than "$ lost ignore" NOTE 10: New operators >> and || extend properties + merge all scripts but overwrite definitions & execute statically script content >> pass only DEFINITIONS but not TEMPLATE to the right | pipe execution such as in Bash, the result is a pipeline NOTE 11: Scripts in pipelines are very flexible, they support full indexing à la Matlab, including staged executions method do(idx) generates the script corresponding to indices idx method script(idx) generates the corresponding script object --------------------------[ FULL EXAMPLE ]----------------------------- # Import the class from pizza.script import * # Override the class globalsection class scriptexample(globalsection): description = "demonstrate commutativity of additions" verbose = True DEFINITIONS = scriptdata( X = 10, Y = 20, R1 = "${X}+${Y}", R2 = "${Y}+${X}" ) TEMPLATE = "" " # Property of the addition ${R1} = ${X} + ${Y} ${R2} = ${Y} + ${X} "" " # derived from scriptexample, X and Y are reused class scriptexample2(scriptexample): description = "demonstrate commutativity of multiplications" verbose = True DEFINITIONS = scriptexample.DEFINITIONS + scriptdata( R3 = "${X} * ${Y}", R4 = "${Y} * ${X}", ) TEMPLATE = "" " # Property of the multiplication ${R3} = ${X} * ${Y} ${R4} = ${Y} * ${X} "" " # call the first class and override the values X and Y s1 = scriptexample() s1.USER.X = 1 # method 1 of override s1.USER.Y = 2 s1.do() # call the second class and override the values X and Y s2 = scriptexample2(X=1000,Y=2000) # method 2 s2.do() # Merge the two scripts s = s1+s2 print("this is my full script") s.description s.do() # The result for s1 is 3 = 1 + 2 3 = 2 + 1 # The result for s2 is 2000000 = 1000 * 2000 2000000 = 2000 * 1000 # The result for s=s1+s2 is # Property of the addition 3000 = 1000 + 2000 3000 = 2000 + 1000 # Property of the multiplication 2000000 = 1000 * 2000 2000000 = 2000 * 1000
constructor adding instance definitions stored in USER
Expand source code
class script: """ script: A Core Class for Flexible LAMMPS Script Generation The `script` class provides a flexible framework for generating dynamic LAMMPS script sections. It supports various LAMMPS sections such as "GLOBAL", "INITIALIZE", "GEOMETRY", "INTERACTIONS", and more, while allowing users to define custom sections with variable definitions, templates, and dynamic evaluation of script content. Key Features: ------------- - **Dynamic Script Generation**: Easily define and manage script sections, using templates and definitions to dynamically generate LAMMPS-compatible scripts. - **Script Concatenation**: Combine multiple script sections while managing variable precedence and ensuring that definitions propagate as expected. - **Flexible Variable Management**: Separate `DEFINITIONS` for static variables and `USER` for user-defined variables, with clear rules for inheritance and precedence. - **Operators for Advanced Script Handling**: Use `+`, `&`, `>>`, `|`, and `**` operators for script merging, static execution, right-shifting of definitions, and more. - **Pipeline Support**: Integrate scripts into pipelines, with full support for staged execution, variable inheritance, and reordering of script sections. Practical Use Cases: -------------------- - **LAMMPS Automation**: Automate the generation of complex LAMMPS scripts by defining reusable script sections with variables and templates. - **Multi-Step Simulations**: Manage multi-step simulations by splitting large scripts into smaller, manageable sections and combining them as needed. - **Advanced Script Control**: Dynamically modify script behavior by overriding variables or using advanced operators to concatenate, pipe, or merge scripts. Methods: -------- __init__(self, persistentfile=True, persistentfolder=None, printflag=False, verbose=False, **userdefinitions): Initializes a new `script` object, with optional user-defined variables passed as `userdefinitions`. do(self, printflag=None, verbose=None): Generates the LAMMPS script based on the current configuration, evaluating templates and definitions to produce the final output. script(self, idx=None, printflag=True, verbosity=2, verbose=None, forced=False): Generate the final LAMMPS script from the pipeline or a subset of the pipeline. add(self, s): Overloads the `+` operator to concatenate script objects, merging definitions and templates while maintaining variable precedence. and(self, s): Overloads the `&` operator for static execution, combining the generated scripts of two script objects without merging their definitions. __mul__(self, ntimes): Overloads the `*` operator to repeat the script `ntimes`, returning a new script object with repeated sections. __pow__(self, ntimes): Overloads the `**` operator to concatenate the script with itself `ntimes`, similar to the `&` operator, but repeated. __or__(self, pipe): Overloads the pipe (`|`) operator to integrate the script into a pipeline, returning a `pipescript` object. write(self, file, printflag=True, verbose=False): Writes the generated script to a file, including headers with metadata. tmpwrite(self): Writes the script to a temporary file, creating both a full version and a clean version without comments. printheader(txt, align="^", width=80, filler="~"): Static method to print formatted headers, useful for organizing output. __copy__(self): Creates a shallow copy of the script object. __deepcopy__(self, memo): Creates a deep copy of the script object, duplicating all internal variables. Additional Features: -------------------- - **Customizable Templates**: Use string templates with variable placeholders (e.g., `${value}`) to dynamically generate script lines. - **Static and User-Defined Variables**: Manage global `DEFINITIONS` for static variables and `USER` variables for dynamic, user-defined settings. - **Advanced Operators**: Leverage a range of operators (`+`, `>>`, `|`, `&`) to manipulate script content, inherit definitions, and control variable precedence. - **Verbose Output**: Control verbosity to include detailed comments and debugging information in generated scripts. Original Content: ----------------- The `script` class supports LAMMPS section generation and variable management with features such as: - **Dynamic Evaluation of Scripts**: Definitions and templates are evaluated at runtime, allowing for flexible and reusable scripts. - **Inheritance of Definitions**: Variable definitions can be inherited from previous sections, allowing for modular script construction. - **Precedence Rules for Variables**: When scripts are concatenated, definitions from the left take precedence, ensuring that the first defined values are preserved. - **Instance and Global Variables**: Instance variables are set via the `USER` object, while global variables (shared across instances) are managed in `DEFINITIONS`. - **Script Pipelines**: Scripts can be integrated into pipelines for sequential execution and dynamic variable propagation. - **Flexible Output Formats**: Lists are expanded into space-separated strings, while tuples are expanded with commas, making the output more readable. Example Usage: -------------- ``` from pizza.script import script, scriptdata class example_section(script): DEFINITIONS = scriptdata( X = 10, Y = 20, result = "${X} + ${Y}" ) TEMPLATE = "${result} = ${X} + ${Y}" s1 = example_section() s1.USER.X = 5 s1.do() ``` The output for `s1.do()` will be: ``` 25 = 5 + 20 ``` With additional sections, scripts can be concatenated and executed as a single entity, with inheritance of variables and customizable behavior. -------------------------------------- OVERVIEW ANDE DETAILED FEATURES -------------------------------------- The class script enables to generate dynamically LAMMPS sections "NONE","GLOBAL","INITIALIZE","GEOMETRY","DISCRETIZATION", "BOUNDARY","INTERACTIONS","INTEGRATION","DUMP","STATUS","RUN" # %% This the typical construction for a class class XXXXsection(script): "" " LAMMPS script: XXXX session "" " name = "XXXXXX" description = name+" section" position = 0 section = 0 userid = "example" version = 0.1 DEFINITIONS = scriptdata( value= 1, expression= "${value+1}", text= "$my text" ) TEMPLATE = "" " # :UNDEF SECTION: # to be defined LAMMPS code with ${value}, ${expression}, ${text} "" " DEFINTIONS can be inherited from a previous section DEFINITIONS = previousection.DEFINTIONS + scriptdata( value= 1, expression= "${value+1}", text= "$my text" ) Recommandation: Split a large script into a small classes or actions An example of use could be: move1 = translation(displacement=10)+rotation(angle=30) move2 = shear(rate=0.1)+rotation(angle=20) bigmove = move1+move2+move1 script = bigmove.do() generates the script NOTE1: Use the print() and the method do() to get the script interpreted NOTE2: DEFINITIONS can be pretified using DEFINITIONS.generator() NOTE3: Variables can extracted from a template using TEMPLATE.scan() NOTE4: Scripts can be joined (from top down to bottom). The first definitions keep higher precedence. Please do not use a variable twice with different contents. myscript = s1 + s2 + s3 will propagate the definitions without overwritting previous values). myscript will be defined as s1 (same name, position, userid, etc.) myscript += s appends the script section s to myscript NOTE5: rules of precedence when script are concatenated The attributes from the class (name, description...) are kept from the left The values of the right overwrite all DEFINITIONS NOTE6: user variables (instance variables) can set with USER or at the construction myclass_instance = myclass(myvariable = myvalue) myclass_instance.USER.myvariable = myvalue NOTE7: how to change variables for all instances at once? In the example below, let x is a global variable (instance independent) and y a local variable (instance dependent) instance1 = myclass(y=1) --> y=1 in instance1 instance2 = myclass(y=2) --> y=2 in instance2 instance3.USER.y=3 --> y=3 in instance3 instance1.DEFINITIONS.x = 10 --> x=10 in all instances (1,2,3) If x is also defined in the USER section, its value will be used Setting instance3.USER.x = 30 will assign x=30 only in instance3 NOTE8: if a the script is used with different values for a same parameter use the operator & to concatenate the results instead of the script example: load(file="myfile1") & load(file="myfile2) & load(file="myfile3")+... NOTE9: lists (e.g., [1,2,'a',3] are expanded ("1 2 a 3") tuples (e.g. (1,2)) are expanded ("1,2") It is easier to read ["lost","ignore"] than "$ lost ignore" NOTE 10: New operators >> and || extend properties + merge all scripts but overwrite definitions & execute statically script content >> pass only DEFINITIONS but not TEMPLATE to the right | pipe execution such as in Bash, the result is a pipeline NOTE 11: Scripts in pipelines are very flexible, they support full indexing à la Matlab, including staged executions method do(idx) generates the script corresponding to indices idx method script(idx) generates the corresponding script object --------------------------[ FULL EXAMPLE ]----------------------------- # Import the class from pizza.script import * # Override the class globalsection class scriptexample(globalsection): description = "demonstrate commutativity of additions" verbose = True DEFINITIONS = scriptdata( X = 10, Y = 20, R1 = "${X}+${Y}", R2 = "${Y}+${X}" ) TEMPLATE = "" " # Property of the addition ${R1} = ${X} + ${Y} ${R2} = ${Y} + ${X} "" " # derived from scriptexample, X and Y are reused class scriptexample2(scriptexample): description = "demonstrate commutativity of multiplications" verbose = True DEFINITIONS = scriptexample.DEFINITIONS + scriptdata( R3 = "${X} * ${Y}", R4 = "${Y} * ${X}", ) TEMPLATE = "" " # Property of the multiplication ${R3} = ${X} * ${Y} ${R4} = ${Y} * ${X} "" " # call the first class and override the values X and Y s1 = scriptexample() s1.USER.X = 1 # method 1 of override s1.USER.Y = 2 s1.do() # call the second class and override the values X and Y s2 = scriptexample2(X=1000,Y=2000) # method 2 s2.do() # Merge the two scripts s = s1+s2 print("this is my full script") s.description s.do() # The result for s1 is 3 = 1 + 2 3 = 2 + 1 # The result for s2 is 2000000 = 1000 * 2000 2000000 = 2000 * 1000 # The result for s=s1+s2 is # Property of the addition 3000 = 1000 + 2000 3000 = 2000 + 1000 # Property of the multiplication 2000000 = 1000 * 2000 2000000 = 2000 * 1000 """ # metadata metadata = get_metadata() # retrieve all metadata type = "script" # type (class name) name = "empty script" # name description = "it is an empty script" # description position = 0 # 0 = root section = 0 # section (0=undef) userid = "undefined" # user name version = metadata["version"] # version license = metadata["license"] email = metadata["email"] # email verbose = False # set it to True to force verbosity _contact = ("INRAE\SAYFOOD\olivier.vitrac@agroparistech.fr", "INRAE\SAYFOOD\william.jenkinson@agroparistech.fr", "INRAE\SAYFOOD\han.chen@inrae.fr") SECTIONS = ["NONE","GLOBAL","INITIALIZE","GEOMETRY","DISCRETIZATION", "BOUNDARY","INTERACTIONS","INTEGRATION","DUMP","STATUS","RUN"] # Main class variables # These definitions are for instances DEFINITIONS = scriptdata() TEMPLATE = """ # empty LAMMPS script """ # constructor def __init__(self,persistentfile=True, persistentfolder = None, printflag = False, verbose = False, verbosity = None, **userdefinitions): """ constructor adding instance definitions stored in USER """ if persistentfolder is None: persistentfolder = get_tmp_location() self.persistentfile = persistentfile self.persistentfolder = persistentfolder self.printflag = printflag self.verbose = verbose if verbosity is None else verbosity>0 self.verbosity = 0 if not verbose else verbosity self.USER = scriptdata(**userdefinitions) # 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))) # String representation def __str__(self): """ string representation """ return f"{self.type}:{self.name}:{self.userid}" # Display/representation method def __repr__(self): """ disp method """ stamp = str(self) self.printheader(f"{stamp} | version={self.version}",filler="/") self.printheader("POSITION & DESCRIPTION",filler="-",align=">") print(f" position: {self.position}") print(f" role: {self.role} (section={self.section})") print(f" description: {self.description}") self.printheader("DEFINITIONS",filler="-",align=">") if len(self.DEFINITIONS)<15: self.DEFINITIONS.__repr__() else: print("too many definitions: ",self.DEFINITIONS) if self.verbose: self.printheader("USER",filler="-",align=">") self.USER.__repr__() self.printheader("TEMPLATE",filler="-",align=">") print(self.TEMPLATE) self.printheader("SCRIPT",filler="-",align=">") print(self.do(printflag=False)) 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))} # Generate the script def do(self,printflag=None,verbose=None): """ Generate the LAMMPS script based on the current configuration. This method generates a LAMMPS-compatible script from the templates and definitions stored in the `script` object. The generated script can be displayed, returned, and optionally include comments for debugging or clarity. Parameters: - printflag (bool, optional): If True, the generated script is printed to the console. Default is True. - verbose (bool, optional): If True, comments and additional information are included in the generated script. If False, comments are removed. Default is True. Returns: - str: The generated LAMMPS script. Method Behavior: - The method first collects all key-value pairs from `DEFINITIONS` and `USER` objects, which store the configuration data for the script. - Lists and tuples in the collected data are formatted into a readable string with proper separators (space for lists, comma for tuples) and prefixed with a '%' to indicate comments. - The generated command template is formatted and evaluated using the collected data. - If `verbose` is set to False, comments in the generated script are removed. - The script is then printed if `printflag` is True. - Finally, the formatted script is returned as a string. Example Usage: -------------- >>> s = script() >>> s.do(printflag=True, verbose=True) units si dimension 3 boundary f f f # Additional script commands... >>> s.do(printflag=False, verbose=False) 'units si\ndimension 3\nboundary f f f\n# Additional script commands...' Notes: - Comments are indicated in the script with '%' or '#'. - The [position {self.position}:{self.userid}] marker is inserted for tracking script sections or modifications. """ printflag = self.printflag if printflag is None else printflag verbose = self.verbose if verbose is None else verbose inputs = self.DEFINITIONS + self.USER for k in inputs.keys(): if isinstance(inputs.getattr(k),list): inputs.setattr(k,"% "+span(inputs.getattr(k))) elif isinstance(inputs.getattr(k),tuple): inputs.setattr(k,"% "+span(inputs.getattr(k),sep=",")) cmd = inputs.formateval(self.TEMPLATE) cmd = cmd.replace("[comment]",f"[position {self.position}:{self.userid}]") if not verbose: cmd=remove_comments(cmd) if printflag: print(cmd) return cmd # Return the role of the script (based on section) @property def role(self): """ convert section index into a role (section name) """ if self.section in range(len(self.SECTIONS)): return self.SECTIONS[self.section] else: return "" # override + def __add__(self,s): """ overload addition operator """ from pizza.dscript import dscript from pizza.group import group, groupcollection from pizza.region import region if isinstance(s,script): dup = duplicate(self) dup.DEFINITIONS = dup.DEFINITIONS + s.DEFINITIONS dup.USER = dup.USER + s.USER dup.TEMPLATE = "\n".join([dup.TEMPLATE,s.TEMPLATE]) return dup elif isinstance(s,pipescript): return pipescript(self, printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) | s elif isinstance(s,dscript): return self + s.script(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) elif isinstance(s, scriptobjectgroup): return self + s.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) elif isinstance(s, group): return self + s.script(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) elif isinstance(s, groupcollection): return self + s.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) elif isinstance(s,region): return self + s.script(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) raise TypeError(f"the second operand in + must a script, pipescript, scriptobjectgroup,\n group, groupcollection or region object not {type(s)}") # override += def _iadd__(self,s): """ overload addition operator """ if isinstance(s,script): self.DEFINITIONS = self.DEFINITIONS + s.DEFINITIONS self.USER = self.USER + s.USER self.TEMPLATE = "\n".join([self.TEMPLATE,s.TEMPLATE]) else: raise TypeError("the second operand must a script object") # override >> def __rshift__(self,s): """ overload right shift operator (keep only the last template) """ if isinstance(s,script): dup = duplicate(self) dup.DEFINITIONS = dup.DEFINITIONS + s.DEFINITIONS dup.USER = dup.USER + s.USER dup.TEMPLATE = s.TEMPLATE return dup else: raise TypeError(f"the second operand in >> must a script object not {type(s)}") # override & def __and__(self,s): """ overload and operator """ if isinstance(s,script): dup = duplicate(self) dup.TEMPLATE = "\n".join([self.do(printflag=False,verbose=False),s.do(printflag=False,verbose=False)]) return dup raise TypeError(f"the second operand in & must a script object not {type(s)}") # override * def __mul__(self,ntimes): """ overload * operator """ if isinstance(ntimes, int) and ntimes>0: res = duplicate(self) if ntimes>1: for n in range(1,ntimes): res += self return res raise ValueError("multiplicator should be a strictly positive integer") # override ** def __pow__(self,ntimes): """ overload ** operator """ if isinstance(ntimes, int) and ntimes>0: res = duplicate(self) if ntimes>1: for n in range(1,ntimes): res = res & self return res raise ValueError("multiplicator should be a strictly positive integer") # pipe scripts def __or__(self,pipe): """ overload | or for pipe """ from pizza.dscript import dscript from pizza.group import group, groupcollection from pizza.region import region if isinstance(pipe, dscript): rightarg = pipe.pipescript(printflag=pipe.printflag,verbose=pipe.verbose,verbosity=pipe.verbosity) elif isinstance(pipe,group): rightarg = pipe.script(printflag=pipe.printflag,verbose=pipe.verbose,verbosity=pipe.verbosity) elif isinstance(pipe,groupcollection): rightarg = pipe.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) elif isinstance(pipe,region): rightarg = pipe.pscript(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) else: rightarg = pipe if isinstance(rightarg,(pipescript,script,scriptobject,scriptobjectgroup)): return pipescript(self,printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) | rightarg else: raise ValueError("the argument in | must a pipescript, a scriptobject or a scriptobjectgroup not {type(s)}") def header(self, verbose=True, verbosity=None, style=2): """ Generate a formatted header for the script file. Parameters: verbosity (bool, optional): If specified, overrides the instance's `verbose` setting. Defaults to the instance's `verbose`. style (int from 1 to 6, optional): ASCII style to frame the header (default=2) Returns: str: A formatted string representing the script's metadata and initialization details. Returns an empty string if verbosity is False. The header includes: - Script version, license, and contact email. - User ID and the number of initialized definitions. - Current system user, hostname, and working directory. - Persistent filename and folder path. - Timestamp of the header generation. """ verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity if not verbose: return "" # Prepare the header content lines = [ f"PIZZA.SCRIPT FILE v{script.version} | License: {script.license} | Email: {script.email}", "", f"<{str(self)}>", f"Initialized with {len(self.USER)} definitions | Verbosity: {verbosity}", f"Persistent file: \"{self.persistentfile}\" | Folder: \"{self.persistentfolder}\"", "", f"Generated on: {getpass.getuser()}@{socket.gethostname()}:{os.getcwd()}", f"{datetime.datetime.now().strftime('%A, %B %d, %Y at %H:%M:%S')}", ] # Use the shared method to format the header return frame_header(lines,style=style) # write file def write(self, file, printflag=True, verbose=False, overwrite=False, style=2): """ Write the script to a file. Parameters: - file (str): The file path where the script will be saved. - printflag (bool): Flag to enable/disable printing of details. - verbose (bool): Flag to enable/disable verbose mode. - overwrite (bool): Whether to overwrite the file if it already exists. - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `2` (frame with rounded corners). Returns: str: The full absolute path of the file written. Raises: FileExistsError: If the file already exists and overwrite is False. """ # Resolve full path full_path = os.path.abspath(file) if os.path.exists(full_path) and not overwrite: raise FileExistsError(f"The file '{full_path}' already exists. Use overwrite=True to overwrite it.") if os.path.exists(full_path) and overwrite and verbose: print(f"Warning: Overwriting the existing file '{full_path}'.") # Generate the script and write to the file cmd = self.do(printflag=printflag, verbose=verbose) with open(full_path, "w") as f: print(self.header(verbosity=verbose, style=style), "\n", file=f) print(cmd, file=f) # Return the full path of the written file return full_path def tmpwrite(self, verbose=False, style=1): """ Write the script to a temporary file and create optional persistent copies. Parameters: verbose (bool, optional): Controls verbosity during script generation. Defaults to False. The method: - Creates a temporary file for the script, with platform-specific behavior: - On Windows (`os.name == 'nt'`), the file is not automatically deleted. - On other systems, the file is temporary and deleted upon closure. - Writes a header and the script content into the temporary file. - Optionally creates a persistent copy in the `self.persistentfolder` directory: - `script.preview.<suffix>`: A persistent copy of the temporary file. - `script.preview.clean.<suffix>`: A clean copy with comments and empty lines removed. - Handles cleanup and exceptions gracefully to avoid leaving orphaned files. - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `1` (basic box). Returns: TemporaryFile: The temporary file handle (non-Windows systems only). None: On Windows, the file is closed and not returned. Raises: Exception: If there is an error creating or writing to the temporary file. """ try: # OS-specific temporary file behavior if os.name == 'nt': # Windows ftmp = tempfile.NamedTemporaryFile(mode="w+b", prefix="script_", suffix=".txt", delete=False) else: # Other platforms ftmp = tempfile.NamedTemporaryFile(mode="w+b", prefix="script_", suffix=".txt") # Generate header and content header = ( f"# TEMPORARY PIZZA.SCRIPT FILE\n" f"# {'-' * 40}\n" f"{self.header(verbosity=verbose, style=style)}" ) content = ( header + "\n# This is a temporary file (it will be deleted automatically)\n\n" + self.do(printflag=False, verbose=verbose) ) # Write content to the temporary file ftmp.write(BOM_UTF8 + content.encode('utf-8')) ftmp.seek(0) # Reset file pointer to the beginning except Exception as e: # Handle errors gracefully ftmp.close() os.remove(ftmp.name) # Clean up the temporary file raise Exception(f"Failed to write to or handle the temporary file: {e}") from None print("\nTemporary File Header:\n", header, "\n") print("A temporary file has been generated here:\n", ftmp.name) # Persistent copy creation if self.persistentfile: ftmpname = os.path.basename(ftmp.name) fcopyname = os.path.join(self.persistentfolder, f"script.preview.{ftmpname.rsplit('_', 1)[1]}") copyfile(ftmp.name, fcopyname) print("A persistent copy has been created here:\n", fcopyname) # Create a clean copy without empty lines or comments with open(ftmp.name, "r") as f: lines = f.readlines() bom_utf8_str = BOM_UTF8.decode("utf-8") clean_lines = [ line for line in lines if line.strip() and not line.lstrip().startswith("#") and not line.startswith(bom_utf8_str) ] fcleanname = os.path.join(self.persistentfolder, f"script.preview.clean.{ftmpname.rsplit('_', 1)[1]}") with open(fcleanname, "w") as f: f.writelines(clean_lines) print("A clean copy has been created here:\n", fcleanname) # Handle file closure for Windows if os.name == 'nt': ftmp.close() return None else: return ftmp # Note that it was not the original intent to copy scripts 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, deepduplicate(v, memo)) return copie def detect_variables(self): """ Detects variables in the content of the template using the pattern r'\$\{(\w+)\}'. Returns: -------- list A list of unique variable names detected in the content. """ # Regular expression to match variables in the format ${varname} variable_pattern = re.compile(r'\$\{(\w+)\}') # Ensure TEMPLATE is iterable (split string into lines if needed) if isinstance(self.TEMPLATE, str): lines = self.TEMPLATE.splitlines() # Split string into lines elif isinstance(self.TEMPLATE, list): lines = self.TEMPLATE else: raise TypeError("TEMPLATE must be a string or a list of strings.") # Detect variables from all lines detected_vars = {variable for line in lines for variable in variable_pattern.findall(line)} # Return the list of unique variables return list(detected_vars)
Subclasses
- pizza.dscript.lamdaScript
- pizza.region.LammpsGeneric
- pizza.script.boundarysection
- pizza.script.discretizationsection
- pizza.script.dumpsection
- pizza.script.geometrysection
- pizza.script.globalsection
- pizza.script.initializesection
- pizza.script.integrationsection
- pizza.script.interactionsection
- pizza.script.runsection
- pizza.script.statussection
- LammpsGeneric
Class variables
var DEFINITIONS
var SECTIONS
var TEMPLATE
var description
var email
var license
var metadata
var name
var position
var section
var type
var userid
var verbose
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)))
Instance variables
var role
-
convert section index into a role (section name)
Expand source code
@property def role(self): """ convert section index into a role (section name) """ if self.section in range(len(self.SECTIONS)): return self.SECTIONS[self.section] else: return ""
Methods
def detect_variables(self)
-
Detects variables in the content of the template using the pattern r'\${(\w+)}'.
Returns:
list A list of unique variable names detected in the content.
Expand source code
def detect_variables(self): """ Detects variables in the content of the template using the pattern r'\$\{(\w+)\}'. Returns: -------- list A list of unique variable names detected in the content. """ # Regular expression to match variables in the format ${varname} variable_pattern = re.compile(r'\$\{(\w+)\}') # Ensure TEMPLATE is iterable (split string into lines if needed) if isinstance(self.TEMPLATE, str): lines = self.TEMPLATE.splitlines() # Split string into lines elif isinstance(self.TEMPLATE, list): lines = self.TEMPLATE else: raise TypeError("TEMPLATE must be a string or a list of strings.") # Detect variables from all lines detected_vars = {variable for line in lines for variable in variable_pattern.findall(line)} # Return the list of unique variables return list(detected_vars)
def do(self, printflag=None, verbose=None)
-
Generate the LAMMPS script based on the current configuration.
This method generates a LAMMPS-compatible script from the templates and definitions stored in the <code><a title="region.script" href="#region.script">script</a></code> object. The generated script can be displayed, returned, and optionally include comments for debugging or clarity. Parameters: - printflag (bool, optional): If True, the generated script is printed to the console. Default is True. - verbose (bool, optional): If True, comments and additional information are included in the generated script. If False, comments are removed. Default is True. Returns: - str: The generated LAMMPS script. Method Behavior: - The method first collects all key-value pairs from <code>DEFINITIONS</code> and <code>USER</code> objects, which store the configuration data for the script. - Lists and tuples in the collected data are formatted into a readable string with proper separators (space for lists, comma for tuples) and prefixed with a '%' to indicate comments. - The generated command template is formatted and evaluated using the collected data. - If <code>verbose</code> is set to False, comments in the generated script are removed. - The script is then printed if <code>printflag</code> is True. - Finally, the formatted script is returned as a string. Example Usage: -------------- >>> s = script() >>> s.do(printflag=True, verbose=True) units si dimension 3 boundary f f f # Additional script commands... >>> s.do(printflag=False, verbose=False) 'units si
dimension 3 boundary f f f
Additional script commands…'
Notes: - Comments are indicated in the script with '%' or '#'. - The [position {self.position}:{self.userid}] marker is inserted for tracking script sections or modifications.
Expand source code
def do(self,printflag=None,verbose=None): """ Generate the LAMMPS script based on the current configuration. This method generates a LAMMPS-compatible script from the templates and definitions stored in the `script` object. The generated script can be displayed, returned, and optionally include comments for debugging or clarity. Parameters: - printflag (bool, optional): If True, the generated script is printed to the console. Default is True. - verbose (bool, optional): If True, comments and additional information are included in the generated script. If False, comments are removed. Default is True. Returns: - str: The generated LAMMPS script. Method Behavior: - The method first collects all key-value pairs from `DEFINITIONS` and `USER` objects, which store the configuration data for the script. - Lists and tuples in the collected data are formatted into a readable string with proper separators (space for lists, comma for tuples) and prefixed with a '%' to indicate comments. - The generated command template is formatted and evaluated using the collected data. - If `verbose` is set to False, comments in the generated script are removed. - The script is then printed if `printflag` is True. - Finally, the formatted script is returned as a string. Example Usage: -------------- >>> s = script() >>> s.do(printflag=True, verbose=True) units si dimension 3 boundary f f f # Additional script commands... >>> s.do(printflag=False, verbose=False) 'units si\ndimension 3\nboundary f f f\n# Additional script commands...' Notes: - Comments are indicated in the script with '%' or '#'. - The [position {self.position}:{self.userid}] marker is inserted for tracking script sections or modifications. """ printflag = self.printflag if printflag is None else printflag verbose = self.verbose if verbose is None else verbose inputs = self.DEFINITIONS + self.USER for k in inputs.keys(): if isinstance(inputs.getattr(k),list): inputs.setattr(k,"% "+span(inputs.getattr(k))) elif isinstance(inputs.getattr(k),tuple): inputs.setattr(k,"% "+span(inputs.getattr(k),sep=",")) cmd = inputs.formateval(self.TEMPLATE) cmd = cmd.replace("[comment]",f"[position {self.position}:{self.userid}]") if not verbose: cmd=remove_comments(cmd) if printflag: print(cmd) return cmd
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 header(self, verbose=True, verbosity=None, style=2)
-
Generate a formatted header for the script file.
Parameters
verbosity (bool, optional): If specified, overrides the instance's
verbose
setting. Defaults to the instance'sverbose
. style (int from 1 to 6, optional): ASCII style to frame the header (default=2)Returns
str
- A formatted string representing the script's metadata and initialization details. Returns an empty string if verbosity is False.
The header includes: - Script version, license, and contact email. - User ID and the number of initialized definitions. - Current system user, hostname, and working directory. - Persistent filename and folder path. - Timestamp of the header generation.
Expand source code
def header(self, verbose=True, verbosity=None, style=2): """ Generate a formatted header for the script file. Parameters: verbosity (bool, optional): If specified, overrides the instance's `verbose` setting. Defaults to the instance's `verbose`. style (int from 1 to 6, optional): ASCII style to frame the header (default=2) Returns: str: A formatted string representing the script's metadata and initialization details. Returns an empty string if verbosity is False. The header includes: - Script version, license, and contact email. - User ID and the number of initialized definitions. - Current system user, hostname, and working directory. - Persistent filename and folder path. - Timestamp of the header generation. """ verbose = verbosity > 0 if verbosity is not None else (self.verbose if verbose is None else verbose) verbosity = 0 if not verbose else verbosity if not verbose: return "" # Prepare the header content lines = [ f"PIZZA.SCRIPT FILE v{script.version} | License: {script.license} | Email: {script.email}", "", f"<{str(self)}>", f"Initialized with {len(self.USER)} definitions | Verbosity: {verbosity}", f"Persistent file: \"{self.persistentfile}\" | Folder: \"{self.persistentfolder}\"", "", f"Generated on: {getpass.getuser()}@{socket.gethostname()}:{os.getcwd()}", f"{datetime.datetime.now().strftime('%A, %B %d, %Y at %H:%M:%S')}", ] # Use the shared method to format the header return frame_header(lines,style=style)
def tmpwrite(self, verbose=False, style=1)
-
Write the script to a temporary file and create optional persistent copies.
Parameters
verbose (bool, optional): Controls verbosity during script generation. Defaults to False.
The method: - Creates a temporary file for the script, with platform-specific behavior: - On Windows (
os.name == 'nt'
), the file is not automatically deleted. - On other systems, the file is temporary and deleted upon closure. - Writes a header and the script content into the temporary file. - Optionally creates a persistent copy in theself.persistentfolder
directory: -script.preview.<suffix>
: A persistent copy of the temporary file. -script.preview.clean.<suffix>
: A clean copy with comments and empty lines removed. - Handles cleanup and exceptions gracefully to avoid leaving orphaned files. - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with+
,-
, and|
2. Double-line frame with╔
,═
, and║
3. Rounded corners with.
,'
,-
, and|
4. Thick outer frame with#
,=
, and#
5. Box drawing characters with┌
,─
, and│
6. Minimalist dotted frame with.
,:
, and.
Default is1
(basic box).Returns
TemporaryFile
- The temporary file handle (non-Windows systems only).
None
- On Windows, the file is closed and not returned.
Raises
Exception
- If there is an error creating or writing to the temporary file.
Expand source code
def tmpwrite(self, verbose=False, style=1): """ Write the script to a temporary file and create optional persistent copies. Parameters: verbose (bool, optional): Controls verbosity during script generation. Defaults to False. The method: - Creates a temporary file for the script, with platform-specific behavior: - On Windows (`os.name == 'nt'`), the file is not automatically deleted. - On other systems, the file is temporary and deleted upon closure. - Writes a header and the script content into the temporary file. - Optionally creates a persistent copy in the `self.persistentfolder` directory: - `script.preview.<suffix>`: A persistent copy of the temporary file. - `script.preview.clean.<suffix>`: A clean copy with comments and empty lines removed. - Handles cleanup and exceptions gracefully to avoid leaving orphaned files. - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `1` (basic box). Returns: TemporaryFile: The temporary file handle (non-Windows systems only). None: On Windows, the file is closed and not returned. Raises: Exception: If there is an error creating or writing to the temporary file. """ try: # OS-specific temporary file behavior if os.name == 'nt': # Windows ftmp = tempfile.NamedTemporaryFile(mode="w+b", prefix="script_", suffix=".txt", delete=False) else: # Other platforms ftmp = tempfile.NamedTemporaryFile(mode="w+b", prefix="script_", suffix=".txt") # Generate header and content header = ( f"# TEMPORARY PIZZA.SCRIPT FILE\n" f"# {'-' * 40}\n" f"{self.header(verbosity=verbose, style=style)}" ) content = ( header + "\n# This is a temporary file (it will be deleted automatically)\n\n" + self.do(printflag=False, verbose=verbose) ) # Write content to the temporary file ftmp.write(BOM_UTF8 + content.encode('utf-8')) ftmp.seek(0) # Reset file pointer to the beginning except Exception as e: # Handle errors gracefully ftmp.close() os.remove(ftmp.name) # Clean up the temporary file raise Exception(f"Failed to write to or handle the temporary file: {e}") from None print("\nTemporary File Header:\n", header, "\n") print("A temporary file has been generated here:\n", ftmp.name) # Persistent copy creation if self.persistentfile: ftmpname = os.path.basename(ftmp.name) fcopyname = os.path.join(self.persistentfolder, f"script.preview.{ftmpname.rsplit('_', 1)[1]}") copyfile(ftmp.name, fcopyname) print("A persistent copy has been created here:\n", fcopyname) # Create a clean copy without empty lines or comments with open(ftmp.name, "r") as f: lines = f.readlines() bom_utf8_str = BOM_UTF8.decode("utf-8") clean_lines = [ line for line in lines if line.strip() and not line.lstrip().startswith("#") and not line.startswith(bom_utf8_str) ] fcleanname = os.path.join(self.persistentfolder, f"script.preview.clean.{ftmpname.rsplit('_', 1)[1]}") with open(fcleanname, "w") as f: f.writelines(clean_lines) print("A clean copy has been created here:\n", fcleanname) # Handle file closure for Windows if os.name == 'nt': ftmp.close() return None else: return ftmp
def write(self, file, printflag=True, verbose=False, overwrite=False, style=2)
-
Write the script to a file.
Parameters
- file (str): The file path where the script will be saved.
- printflag (bool): Flag to enable/disable printing of details.
- verbose (bool): Flag to enable/disable verbose mode.
- overwrite (bool): Whether to overwrite the file if it already exists.
- style (int, optional):
Defines the ASCII frame style for the header.
Valid values are integers from 1 to 6, corresponding to predefined styles:
1. Basic box with
+
,-
, and|
2. Double-line frame with╔
,═
, and║
3. Rounded corners with.
,'
,-
, and|
4. Thick outer frame with#
,=
, and#
5. Box drawing characters with┌
,─
, and│
6. Minimalist dotted frame with.
,:
, and.
Default is2
(frame with rounded corners).
Returns
str
- The full absolute path of the file written.
Raises
FileExistsError
- If the file already exists and overwrite is False.
Expand source code
def write(self, file, printflag=True, verbose=False, overwrite=False, style=2): """ Write the script to a file. Parameters: - file (str): The file path where the script will be saved. - printflag (bool): Flag to enable/disable printing of details. - verbose (bool): Flag to enable/disable verbose mode. - overwrite (bool): Whether to overwrite the file if it already exists. - style (int, optional): Defines the ASCII frame style for the header. Valid values are integers from 1 to 6, corresponding to predefined styles: 1. Basic box with `+`, `-`, and `|` 2. Double-line frame with `╔`, `═`, and `║` 3. Rounded corners with `.`, `'`, `-`, and `|` 4. Thick outer frame with `#`, `=`, and `#` 5. Box drawing characters with `┌`, `─`, and `│` 6. Minimalist dotted frame with `.`, `:`, and `.` Default is `2` (frame with rounded corners). Returns: str: The full absolute path of the file written. Raises: FileExistsError: If the file already exists and overwrite is False. """ # Resolve full path full_path = os.path.abspath(file) if os.path.exists(full_path) and not overwrite: raise FileExistsError(f"The file '{full_path}' already exists. Use overwrite=True to overwrite it.") if os.path.exists(full_path) and overwrite and verbose: print(f"Warning: Overwriting the existing file '{full_path}'.") # Generate the script and write to the file cmd = self.do(printflag=printflag, verbose=verbose) with open(full_path, "w") as f: print(self.header(verbosity=verbose, style=style), "\n", file=f) print(cmd, file=f) # Return the full path of the written file return full_path
class scriptdata (sortdefinitions=False, **kwargs)
-
class of script parameters Typical constructor: DEFINITIONS = scriptdata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it
constructor
Expand source code
class scriptdata(param): """ class of script parameters Typical constructor: DEFINITIONS = scriptdata( var1 = value1, var2 = value2 ) See script, struct, param to get review all methods attached to it """ _type = "SD" _fulltype = "script data" _ftype = "definition"
Ancestors
- pizza.private.mstruct.param
- pizza.private.mstruct.struct
class scriptobject (beadtype=1, name=None, fullname='', filename='', style='smd', mass=1.0, forcefield=LAMMPS:SMD:none:walls, group=[], USER=script data (SD object) with 0 definitions)
-
scriptobject: A Class for Managing Script Objects in LAMMPS
The
scriptobject
class is designed to represent individual objects in LAMMPS scripts, such as beads, atoms, or other components. Each object is associated with aforcefield
instance that defines the physical interactions of the object, and the class supports a variety of properties for detailed object definition. Additionally,scriptobject
instances can be grouped together and compared based on their properties, such asbeadtype
andname
.Key Features:
- Forcefield Integration: Each
scriptobject
is associated with aforcefield
instance, allowing for customized physical interactions. Forcefields can be passed via theUSER
keyword for dynamic parameterization. - Grouping: Multiple
scriptobject
instances can be combined into ascriptobjectgroup
using the+
operator, allowing for complex collections of objects. - Object Comparison:
scriptobject
instances can be compared and sorted based on theirbeadtype
andname
, enabling efficient organization and manipulation of objects. - Piping and Execution: Supports the pipe (
|
) operator, allowingscriptobject
instances to be used in script pipelines alongside other script elements.
Practical Use Cases:
- Object Definition in LAMMPS: Use
scriptobject
to represent individual objects in a simulation, including their properties and associated forcefields. - Forcefield Parameterization: Pass customized parameters to the forcefield via the
USER
keyword to dynamically adjust the physical interactions. - Grouping and Sorting: Combine multiple objects into groups, or sort them based
on their properties (e.g.,
beadtype
) for easier management in complex simulations.
Methods:
init(self, beadtype=1, name="undefined", fullname="", filename="", style="smd", forcefield=rigidwall(), group=[], USER=scriptdata()): Initializes a new
scriptobject
with the specified properties, includingbeadtype
,name
,forcefield
, and optionalgroup
.str(self): Returns a string representation of the
scriptobject
, showing itsbeadtype
andname
.add(self, SO): Combines two
scriptobject
instances or ascriptobject
with ascriptobjectgroup
. Raises an error if the two objects have the samename
or if the second operand is not a validscriptobject
orscriptobjectgroup
.or(self, pipe): Overloads the pipe (
|
) operator to integrate thescriptobject
into a pipeline.eq(self, SO): Compares two
scriptobject
instances, returningTrue
if they have the samebeadtype
andname
.ne(self, SO): Returns
True
if the twoscriptobject
instances differ in eitherbeadtype
orname
.lt(self, SO): Compares the
beadtype
of twoscriptobject
instances, returningTrue
if the left object'sbeadtype
is less than the right object's.gt(self, SO): Compares the
beadtype
of twoscriptobject
instances, returningTrue
if the left object'sbeadtype
is greater than the right object's.le(self, SO): Returns
True
if thebeadtype
of the leftscriptobject
is less than or equal to the rightscriptobject
.ge(self, SO): Returns
True
if thebeadtype
of the leftscriptobject
is greater than or equal to the rightscriptobject
.Attributes:
beadtype : int The type of bead or object, used for distinguishing between different types in the simulation. name : str A short name for the object, useful for quick identification. fullname : str A comprehensive name for the object. If not provided, defaults to the
name
with "object definition". filename : str The path to the file containing the input data for the object. style : str The style of the object (e.g., "smd" for smoothed dynamics). forcefield : forcefield The forcefield instance associated with the object, defining its physical interactions. group : list A list of otherscriptobject
instances that are grouped with this object. USER : scriptdata A collection of user-defined variables for customizing the forcefield or other properties.Original Content:
The
scriptobject
class enables the definition of objects within LAMMPS scripts, providing: - Beadtype and Naming: Objects are distinguished by theirbeadtype
andname
, allowing for comparison and sorting based on these properties. - Forcefield Support: Objects are linked to a forcefield instance, and user-defined forcefield parameters can be passed through theUSER
keyword. - Group Management: Multiple objects can be grouped together using the+
operator, forming ascriptobjectgroup
. - Comparison Operators: Objects can be compared based on theirbeadtype
andname
, using standard comparison operators (==
,<
,>
, etc.). - Pipelines:scriptobject
instances can be integrated into pipelines, supporting the|
operator for use in sequential script execution.Example Usage:
from pizza.scriptobject import scriptobject, rigidwall, scriptdata # Define a script object with custom properties obj1 = scriptobject(beadtype=1, name="bead1", forcefield=rigidwall(USER=scriptdata(param1=10))) # Combine two objects into a group obj2 = scriptobject(beadtype=2, name="bead2") group = obj1 + obj2 # Print object information print(obj1) print(group)
The output will be:
script object | type=1 | name=bead1 scriptobjectgroup containing 2 objects
Overview
class of script object OBJ = scriptobject(...) Implemented properties: beadtype=1,2,... name="short name" fullname = "comprehensive name" filename = "/path/to/your/inputfile" style = "smd" forcefield = any valid forcefield instance (default = rigidwall()) mass = 1.0 note: use a forcefield instance with the keywork USER to pass user FF parameters examples: rigidwall(USER=scriptdata(...)) solidfood(USER==scriptdata(...)) water(USER==scriptdata(...)) group objects with OBJ1+OBJ2... into scriptobjectgroups objects can be compared and sorted based on beadtype and name
constructor
Expand source code
class scriptobject(struct): """ scriptobject: A Class for Managing Script Objects in LAMMPS The `scriptobject` class is designed to represent individual objects in LAMMPS scripts, such as beads, atoms, or other components. Each object is associated with a `forcefield` instance that defines the physical interactions of the object, and the class supports a variety of properties for detailed object definition. Additionally, `scriptobject` instances can be grouped together and compared based on their properties, such as `beadtype` and `name`. Key Features: ------------- - **Forcefield Integration**: Each `scriptobject` is associated with a `forcefield` instance, allowing for customized physical interactions. Forcefields can be passed via the `USER` keyword for dynamic parameterization. - **Grouping**: Multiple `scriptobject` instances can be combined into a `scriptobjectgroup` using the `+` operator, allowing for complex collections of objects. - **Object Comparison**: `scriptobject` instances can be compared and sorted based on their `beadtype` and `name`, enabling efficient organization and manipulation of objects. - **Piping and Execution**: Supports the pipe (`|`) operator, allowing `scriptobject` instances to be used in script pipelines alongside other script elements. Practical Use Cases: -------------------- - **Object Definition in LAMMPS**: Use `scriptobject` to represent individual objects in a simulation, including their properties and associated forcefields. - **Forcefield Parameterization**: Pass customized parameters to the forcefield via the `USER` keyword to dynamically adjust the physical interactions. - **Grouping and Sorting**: Combine multiple objects into groups, or sort them based on their properties (e.g., `beadtype`) for easier management in complex simulations. Methods: -------- __init__(self, beadtype=1, name="undefined", fullname="", filename="", style="smd", forcefield=rigidwall(), group=[], USER=scriptdata()): Initializes a new `scriptobject` with the specified properties, including `beadtype`, `name`, `forcefield`, and optional `group`. __str__(self): Returns a string representation of the `scriptobject`, showing its `beadtype` and `name`. __add__(self, SO): Combines two `scriptobject` instances or a `scriptobject` with a `scriptobjectgroup`. Raises an error if the two objects have the same `name` or if the second operand is not a valid `scriptobject` or `scriptobjectgroup`. __or__(self, pipe): Overloads the pipe (`|`) operator to integrate the `scriptobject` into a pipeline. __eq__(self, SO): Compares two `scriptobject` instances, returning `True` if they have the same `beadtype` and `name`. __ne__(self, SO): Returns `True` if the two `scriptobject` instances differ in either `beadtype` or `name`. __lt__(self, SO): Compares the `beadtype` of two `scriptobject` instances, returning `True` if the left object's `beadtype` is less than the right object's. __gt__(self, SO): Compares the `beadtype` of two `scriptobject` instances, returning `True` if the left object's `beadtype` is greater than the right object's. __le__(self, SO): Returns `True` if the `beadtype` of the left `scriptobject` is less than or equal to the right `scriptobject`. __ge__(self, SO): Returns `True` if the `beadtype` of the left `scriptobject` is greater than or equal to the right `scriptobject`. Attributes: ----------- beadtype : int The type of bead or object, used for distinguishing between different types in the simulation. name : str A short name for the object, useful for quick identification. fullname : str A comprehensive name for the object. If not provided, defaults to the `name` with "object definition". filename : str The path to the file containing the input data for the object. style : str The style of the object (e.g., "smd" for smoothed dynamics). forcefield : forcefield The forcefield instance associated with the object, defining its physical interactions. group : list A list of other `scriptobject` instances that are grouped with this object. USER : scriptdata A collection of user-defined variables for customizing the forcefield or other properties. Original Content: ----------------- The `scriptobject` class enables the definition of objects within LAMMPS scripts, providing: - **Beadtype and Naming**: Objects are distinguished by their `beadtype` and `name`, allowing for comparison and sorting based on these properties. - **Forcefield Support**: Objects are linked to a forcefield instance, and user-defined forcefield parameters can be passed through the `USER` keyword. - **Group Management**: Multiple objects can be grouped together using the `+` operator, forming a `scriptobjectgroup`. - **Comparison Operators**: Objects can be compared based on their `beadtype` and `name`, using standard comparison operators (`==`, `<`, `>`, etc.). - **Pipelines**: `scriptobject` instances can be integrated into pipelines, supporting the `|` operator for use in sequential script execution. Example Usage: -------------- ``` from pizza.scriptobject import scriptobject, rigidwall, scriptdata # Define a script object with custom properties obj1 = scriptobject(beadtype=1, name="bead1", forcefield=rigidwall(USER=scriptdata(param1=10))) # Combine two objects into a group obj2 = scriptobject(beadtype=2, name="bead2") group = obj1 + obj2 # Print object information print(obj1) print(group) ``` The output will be: ``` script object | type=1 | name=bead1 scriptobjectgroup containing 2 objects ``` OVERVIEW -------------- class of script object OBJ = scriptobject(...) Implemented properties: beadtype=1,2,... name="short name" fullname = "comprehensive name" filename = "/path/to/your/inputfile" style = "smd" forcefield = any valid forcefield instance (default = rigidwall()) mass = 1.0 note: use a forcefield instance with the keywork USER to pass user FF parameters examples: rigidwall(USER=scriptdata(...)) solidfood(USER==scriptdata(...)) water(USER==scriptdata(...)) group objects with OBJ1+OBJ2... into scriptobjectgroups objects can be compared and sorted based on beadtype and name """ _type = "SO" _fulltype = "script object" _ftype = "propertie" def __init__(self, beadtype = 1, name = None, fullname="", filename="", style="smd", mass=1.0, # added on 2024-11-29 forcefield=rigidwall(), group=[], USER = scriptdata() ): name = f"beadtype={beadtype}" if name is None else name if not isinstance(name,str): TypeError(f"name must a string or None got {type(name)}") if fullname=="": fullname = name + " object definition" if not isinstance(group,list): group = [group] forcefield.beadtype = beadtype forcefield.userid = name forcefield.USER = USER super(scriptobject,self).__init__( beadtype = beadtype, name = name, fullname = fullname, filename = filename, style = style, forcefield = forcefield, mass = mass, group = group, USER = USER ) def __str__(self): """ string representation """ return f"{self._fulltype} | type={self.beadtype} | name={self.name}" def __add__(self, SO): if isinstance(SO,scriptobject): if SO.name != self.name: if SO.beadtype == self.beadtype: SO.beadtype = self.beadtype+1 return scriptobjectgroup(self,SO) else: raise ValueError('the object "%s" already exists' % SO.name) elif isinstance(SO,scriptobjectgroup): return scriptobjectgroup(self)+SO else: return ValueError("The object should a script object or its container") def __or__(self, pipe): """ overload | or for pipe """ if isinstance(pipe,(pipescript,script,scriptobject,scriptobjectgroup)): return pipescript(self) | pipe else: raise ValueError("the argument must a pipescript, a scriptobject or a scriptobjectgroup") def __eq__(self, SO): return isinstance(SO,scriptobject) and (self.beadtype == SO.beadtype) and (self.mass == SO.mass) \ and (self.name == SO.name) def __ne__(self, SO): return not isinstance(SO,scriptobject) or (self.beadtype != SO.beadtype) or (self.mass != SO.mass) or (self.name != SO.name) def __lt__(self, SO): return self.beadtype < SO.beadtype def __gt__(self, SO): return self.beadtype > SO.beadtype def __le__(self, SO): return self.beadtype <= SO.beadtype def __ge__(self, SO): return self.beadtype >= SO.beadtype
Ancestors
- pizza.private.mstruct.struct
- Forcefield Integration: Each
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
- pizza.forcefield.forcefield
Subclasses
- pizza.forcefield.none
- pizza.forcefield.tlsph
- pizza.forcefield.ulsph
Class variables
var PAIR_STYLE
var description
var name
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
- pizza.forcefield.tlsph
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Class variables
var description
var name
var userid
var version
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
- regioncollection
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
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Subclasses
- pizza.forcefield.saltTLSPH
- pizza.forcefield.solidfood
Class variables
var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name
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
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Subclasses
- pizza.forcefield.water
Class variables
var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var description
var name
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
- pizza.forcefield.ulsph
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Class variables
var description
var name
var userid
var version