Module script
The class script() and derived facilitate the coding in LAMMPS Each section is remplaced by a template as a class inherited from script()
The class include two important attribues:
TEMPLATE is a string efines between the LAMMPS code
The variables used by TEMPLATE are stored in DEFINITIONS.
DEFINITIONS is a scripdata() object accepting scalar, mathematical expressions,
text almost as in LAMMPS.
Variables can be inherited between sections using + or += operator
Toy example
G = globalsection()
print(G)
c = initializesection()
print(c)
g = geometrysection()
print(g)
d = discretizationsection()
print(d)
b = boundarysection()
print(b)
i = interactionsection()
print(i)
t = integrationsection()
print(t)
d = dumpsection()
print(d)
s = statussection()
print(s)
r = runsection()
print(r)
# all sections as a single script
myscript = G+c+g+d+b+i+t+d+s+r
print("
"4,'='80,'
this is the full script
','='*80,' ') print(myscript.do())
Additional classes: scriptobject(), scriptobjectgroup(), pipescript()
They generate dynamic scripts from objects, collection of objects or scripts
Variables (DEFINITIONS and USER) are stored in scriptdata() objects
How the variables are stored and used.
STATIC: set in the script class (with the attribute DEFINITIONS)
GLOBAL: set in the instance of the script during construction
or within the USER scriptdata(). These values can be changed
at runtime but the values are overwritten if the script are
combined with the operator +
LOCAL: set (bypass) in the pipeline with the keyword USER[step]
Example with pipelines:
Build pipelines with:
p = G | c | g # using the symbol pipe "|"
p = pipescript(G)*4 # using the constructor pipescript()
Pipeline with 4 scripts and
D(STATIC:GLOBAL:LOCAL) DEFINITIONS
------------:----------------------------------------
[-] 00: script:global:example with D(19: 0: 0)
[-] 01: script:global:example with D(19: 0: 0)
[-] 02: script:global:example with D(19: 0: 0)
[-] 03: script:global:example with D(19: 0: 0)
------------:----------------------------------------
Change the GLOBAL variables for script with idx=0
p.scripts[0].USER.a=1 # set a=1 for all scripts onwards
p.scripts[0].USER.b=2 # set b=2
Change the LOCAL variables for script with idx=0
p.USER[0].a=10 # set a=10 for the script 00
------------:----------------------------------------
[-] 00: script:global:example with D(19: 2: 1)
[-] 01: script:global:example with D(19: 0: 0)
[-] 02: script:global:example with D(19: 0: 0)
[-] 03: script:global:example with D(19: 0: 0)
------------:----------------------------------------
Summary of pipeline indexing and scripting
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()
Created on Sat Feb 19 11:00:43 2022
@author: olivi
Expand source code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
The class script() and derived facilitate the coding in LAMMPS
Each section is remplaced by a template as a class inherited from script()
The class include two important attribues:
TEMPLATE is a string efines between """ """ the LAMMPS code
The variables used by TEMPLATE are stored in DEFINITIONS.
DEFINITIONS is a scripdata() object accepting scalar, mathematical expressions,
text almost as in LAMMPS.
Variables can be inherited between sections using + or += operator
Toy example
G = globalsection()
print(G)
c = initializesection()
print(c)
g = geometrysection()
print(g)
d = discretizationsection()
print(d)
b = boundarysection()
print(b)
i = interactionsection()
print(i)
t = integrationsection()
print(t)
d = dumpsection()
print(d)
s = statussection()
print(s)
r = runsection()
print(r)
# all sections as a single script
myscript = G+c+g+d+b+i+t+d+s+r
print("\n"*4,'='*80,'\n\n this is the full script\n\n','='*80,'\n')
print(myscript.do())
Additional classes: scriptobject(), scriptobjectgroup(), pipescript()
They generate dynamic scripts from objects, collection of objects or scripts
Variables (DEFINITIONS and USER) are stored in scriptdata() objects
How the variables are stored and used.
STATIC: set in the script class (with the attribute DEFINITIONS)
GLOBAL: set in the instance of the script during construction
or within the USER scriptdata(). These values can be changed
at runtime but the values are overwritten if the script are
combined with the operator +
LOCAL: set (bypass) in the pipeline with the keyword USER[step]
Example with pipelines:
Build pipelines with:
p = G | c | g # using the symbol pipe "|"
p = pipescript(G)*4 # using the constructor pipescript()
Pipeline with 4 scripts and
D(STATIC:GLOBAL:LOCAL) DEFINITIONS
------------:----------------------------------------
[-] 00: script:global:example with D(19: 0: 0)
[-] 01: script:global:example with D(19: 0: 0)
[-] 02: script:global:example with D(19: 0: 0)
[-] 03: script:global:example with D(19: 0: 0)
------------:----------------------------------------
Change the GLOBAL variables for script with idx=0
p.scripts[0].USER.a=1 # set a=1 for all scripts onwards
p.scripts[0].USER.b=2 # set b=2
Change the LOCAL variables for script with idx=0
p.USER[0].a=10 # set a=10 for the script 00
------------:----------------------------------------
[-] 00: script:global:example with D(19: 2: 1)
[-] 01: script:global:example with D(19: 0: 0)
[-] 02: script:global:example with D(19: 0: 0)
[-] 03: script:global:example with D(19: 0: 0)
------------:----------------------------------------
Summary of pipeline indexing and scripting
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()
Created on Sat Feb 19 11:00:43 2022
@author: olivi
"""
__project__ = "Pizza3"
__author__ = "Olivier Vitrac"
__copyright__ = "Copyright 2022"
__credits__ = ["Olivier Vitrac"]
__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
# Revision history
# 2022-02-20 RC with documentation and 10 section templates
# 2022-02-21 add + += operators, expand help
# 2022-02-26 add USER to enable instance variables with higher precendence
# 2022-02-27 add write() method and overload & operator
# 2022-02-28 overload * (+ expansion) and ** (& expansion) operators
# 2022-03-01 expand lists (with space as separator) and tupples (with ,)
# 2022-03-02 first implementation of scriptobject(), object container pending
# 2022-03-03 extensions of scriptobject() and scriptobjectgroup()
# 2022-03-04 consolidation of scriptobject() and scriptobjectgroup()
# 2022-03-05 [major update] scriptobjectgroup() can generate scripts for loading, setting groups and forcefields
# 2022-03-12 [major update] pipescript() enables to store scripts in dynamic pipelines
# 2022-03-15 implement a userspace within the pipeline
# 2022-03-15 fix scriptobject | pipescript, smooth and document syntaxes with pipescripts
# 2022-03-16 overload +=, *, several fixes and update help for pipescript
# 2022-03-18 modification to script method, \ttype specified for groups
# 2022-03-19 standardized pizza path
# 2023-01-09 update __repr__() for scripts to show both DEFINITIONS and USER if verbose = True
# 2023-01-19 fix do() for pipescripts when the pipe has been already fully executed (statically)
# 2023-01-20 add picker() and possibility to get list indices in pipe
# 2023-01-26 add pipescript.join()
# 2023-01-27 add tmpwrite()
# 2023-01-27 use % instead of $ for lists in script.do(), in line with the new feature implemented in param.eval()
# 2023-01-31 fix the temporary file on Linux
# 2023-07-14 add and implement persistentfile and peristenfolder in scripts
# 2023-07-20 add header to script.tmpwrite()
# 2023-07-20 add a persident script.preview.clean copy
# 2023-08-17 fix span() when vector is "" or str
# 2024-04-16 fix the method tmpwrite(self) on windows with proper error handling
# 2024-04-18 fix scriptobjectgroup.script for empty and None filename
# 2024-09-01 script accepts persistentfolder=None for inheritance
# 2024-10-09 verbosity handling with script.do() and pscript.do() methods, remove_comments moved to script from dscript (circular reference)
# 2024-10-12 implement | for dscript objects
# 2024-10-14 finalization of dscript integration, improved doc
# 2024-10-18 add dscript() method to generate a dscript object from a pipescript
# 2024-10-19 script.do() convert literal \\n back to \n
# 2024-10-22 fix | for non-native pipescript objects
# 2024-11-12 add flexibility to remove_comments(), comment_chars="#%", continuation_marker="..."
# 2024-11-23 improve write and do() methods (the old pipescript.do() method is available as pipescript.do_legacy() )
# 2024-11-25 clear distinction between pipescript and scrupt headers
# 2024-11-29 improved save features
# 2024-12-01 standarize scripting features, automatically call script/pscript methods
# 2024-12-02 standardize script.header(), pscript.header() and dscript.header()
# 2024-12-09 get-metadata() use globals()
# %% Dependencies
import os, datetime, socket, getpass, tempfile, types, re
from copy import copy as duplicate
from copy import deepcopy as deepduplicate
from shutil import copy as copyfile
# All forcefield parameters are stored à la Matlab in a structure
from pizza.private.mstruct import param,struct
from pizza.forcefield import *
__all__ = ['CallableScript', 'boundarysection', 'discretizationsection', 'dumpsection', 'forcefield', 'frame_header', 'geometrysection', 'get_metadata', 'get_tmp_location', 'globalsection', 'initializesection', 'integrationsection', 'interactionsection', 'none', 'param', 'paramauto', 'parameterforcefield', 'picker', 'pipescript', 'remove_comments', 'rigidwall', 'runsection', 'saltTLSPH', 'script', 'scriptdata', 'scriptobject', 'scriptobjectgroup', 'smd', 'solidfood', 'span', 'statussection', 'struct', 'tlsph', 'ulsph', 'water']
# span vector into a single string
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 ""
# select elements from a list L based on indices as L(indices) in Matlab
def picker(L,indices): return [L[i] for i in indices if (i>=0 and i<len(L))]
# Get the location of the `tmp` directory, in a system-independent way.
get_tmp_location = lambda: tempfile.gettempdir()
# UTF-8 encoded Byte Order Mark (sequence: 0xef, 0xbb, 0xbf)
BOM_UTF8 = b'\xef\xbb\xbf'
# %% Private functions and classes
def remove_comments(content, split_lines=False, emptylines=False, comment_chars="#", continuation_marker="\\\\", remove_continuation_marker=False):
"""
Removes comments from a single or multi-line string, handling quotes, escaped characters, and line continuation.
Parameters:
-----------
content : str
The input string, which may contain multiple lines. Each line will be processed
individually to remove comments, while preserving content inside quotes.
split_lines : bool, optional (default: False)
If True, the function will return a list of processed lines. If False, it will
return a single string with all lines joined by newlines.
emptylines : bool, optional (default: False)
If True, empty lines will be preserved in the output. If False, empty lines
will be removed from the output.
comment_chars : str, optional (default: "#")
A string containing characters to identify the start of a comment.
Any of these characters will mark the beginning of a comment unless within quotes.
continuation_marker : str or None, optional (default: "\\\\")
A string containing characters to indicate line continuation (use `\\` to specify).
Any characters after the continuation marker are ignored as a comment. If set to `None`
or an empty string, line continuation will not be processed.
remove_continuation_marker : bool, optional (default: False)
If True, the continuation marker itself is removed from the processed line, keeping
only the characters before it. If False, the marker is retained as part of the line.
Returns:
--------
str or list of str
The processed content with comments removed. Returns a list of lines if
`split_lines` is True, or a single string if False.
"""
def process_line(line):
"""Remove comments and handle line continuation within a single line while managing quotes and escapes."""
in_single_quote = False
in_double_quote = False
escaped = False
result = []
i = 0
while i < len(line):
char = line[i]
if escaped:
result.append(char)
escaped = False
i += 1
continue
# Handle escape character within quoted strings
if char == '\\' and (in_single_quote or in_double_quote):
escaped = True
result.append(char)
i += 1
continue
# Toggle state for single and double quotes
if char == "'" and not in_double_quote:
in_single_quote = not in_single_quote
elif char == '"' and not in_single_quote:
in_double_quote = not in_double_quote
# Check for line continuation marker if it's set and outside of quotes
if continuation_marker and not in_single_quote and not in_double_quote:
# Check if the remaining part of the line matches the continuation marker
if line[i:].startswith(continuation_marker):
# Optionally remove the continuation marker
if remove_continuation_marker:
result.append(line[:i].rstrip()) # Keep everything before the marker
else:
result.append(line[:i + len(continuation_marker)].rstrip()) # Include the marker itself
return ''.join(result).strip()
# Check for comment start characters outside of quotes
if char in comment_chars and not in_single_quote and not in_double_quote:
break # Stop processing the line when a comment is found
result.append(char)
i += 1
return ''.join(result).strip()
# Split the input content into lines
lines = content.split('\n')
# Process each line, considering the emptylines flag
processed_lines = []
for line in lines:
stripped_line = line.strip()
if not stripped_line and not emptylines:
continue # Skip empty lines if emptylines is False
if any(stripped_line.startswith(c) for c in comment_chars):
continue # Skip lines that are pure comments
processed_line = process_line(line)
if processed_line or emptylines: # Only add non-empty lines if emptylines is False
processed_lines.append(processed_line)
if split_lines:
return processed_lines # Return list of processed lines
else:
return '\n'.join(processed_lines) # Join lines back into a single string
# returns the metadata
def get_metadata():
"""Return a dictionary of explicitly defined metadata."""
# Define the desired metadata keys
metadata_keys = [
"__project__",
"__author__",
"__copyright__",
"__credits__",
"__license__",
"__maintainer__",
"__email__",
"__version__",
]
# Filter only the desired keys from the current module's globals
return {key.strip("_"): globals()[key] for key in metadata_keys if key in globals()}
# frames headers
def frame_header(
lines,
padding=2,
style=1,
corner_symbols=None, # Can be a string or a tuple
horizontal_symbol=None,
vertical_symbol=None,
empty_line_symbol=None,
line_fill_symbol=None,
comment="#"
):
"""
Format the header content into an ASCII framed box with customizable properties.
Parameters:
lines (list or tuple): The lines to include in the header.
- Empty strings "" are replaced with lines of `line_fill_symbol`.
- None values are treated as empty lines.
padding (int, optional): Number of spaces to pad on each side of the content. Default is 2.
style (int, optional): Style index (1 to 6) for predefined frame styles. Default is 1.
corner_symbols (str or tuple, optional): Symbols for the corners (top-left, top-right, bottom-left, bottom-right).
Can be a string (e.g., "+") for uniform corners.
horizontal_symbol (str, optional): Symbol to use for horizontal lines.
vertical_symbol (str, optional): Symbol to use for vertical lines.
empty_line_symbol (str, optional): Symbol to use for empty lines inside the frame.
line_fill_symbol (str, optional): Symbol to fill lines that replace empty strings.
comment (str, optional): Comment symbol to prefix each line. Can be multiple characters. Default is "#".
Returns:
str: The formatted header as a string.
Raises:
ValueError: If the specified style is undefined or `corner_symbols` is invalid.
"""
# Predefined styles
styles = {
1: {
"corner_symbols": ("+", "+", "+", "+"),
"horizontal_symbol": "-",
"vertical_symbol": "|",
"empty_line_symbol": " ",
"line_fill_symbol": "-"
},
2: {
"corner_symbols": ("╔", "╗", "╚", "╝"),
"horizontal_symbol": "═",
"vertical_symbol": "║",
"empty_line_symbol": " ",
"line_fill_symbol": "═"
},
3: {
"corner_symbols": (".", ".", "'", "'"),
"horizontal_symbol": "-",
"vertical_symbol": "|",
"empty_line_symbol": " ",
"line_fill_symbol": "-"
},
4: {
"corner_symbols": ("#", "#", "#", "#"),
"horizontal_symbol": "=",
"vertical_symbol": "#",
"empty_line_symbol": " ",
"line_fill_symbol": "="
},
5: {
"corner_symbols": ("┌", "┐", "└", "┘"),
"horizontal_symbol": "─",
"vertical_symbol": "│",
"empty_line_symbol": " ",
"line_fill_symbol": "─"
},
6: {
"corner_symbols": (".", ".", ".", "."),
"horizontal_symbol": ".",
"vertical_symbol": ":",
"empty_line_symbol": " ",
"line_fill_symbol": "."
}
}
# Validate style and set defaults
if style not in styles:
raise ValueError(f"Undefined style {style}. Valid styles are {list(styles.keys())}.")
selected_style = styles[style]
# Convert corner_symbols to a tuple of 4 values
if isinstance(corner_symbols, str):
corner_symbols = (corner_symbols,) * 4
elif isinstance(corner_symbols, (list, tuple)) and len(corner_symbols) == 1:
corner_symbols = tuple(corner_symbols * 4)
elif isinstance(corner_symbols, (list, tuple)) and len(corner_symbols) == 2:
corner_symbols = (corner_symbols[0], corner_symbols[1], corner_symbols[0], corner_symbols[1])
elif corner_symbols is None:
corner_symbols = selected_style["corner_symbols"]
elif not isinstance(corner_symbols, (list, tuple)) or len(corner_symbols) != 4:
raise ValueError("corner_symbols must be a string or a tuple/list of 1, 2, or 4 elements.")
# Apply overrides or defaults
horizontal_symbol = horizontal_symbol or selected_style["horizontal_symbol"]
vertical_symbol = vertical_symbol or selected_style["vertical_symbol"]
empty_line_symbol = empty_line_symbol or selected_style["empty_line_symbol"]
line_fill_symbol = line_fill_symbol or selected_style["line_fill_symbol"]
# Process lines: Replace "" with line_fill placeholders, None with empty lines
processed_lines = []
max_content_width = 0
for line in lines:
if line == "":
processed_lines.append("<LINE_FILL>")
elif line is None:
processed_lines.append(None)
else:
processed_lines.append(line)
max_content_width = max(max_content_width, len(line))
# Adjust width for padding
frame_width = max_content_width + padding * 2
# Build the top border
top_border = f"{corner_symbols[0]}{horizontal_symbol * frame_width}{corner_symbols[1]}"
# Build content lines with vertical borders
framed_lines = [top_border]
for line in processed_lines:
if line is None:
empty_line = f"{vertical_symbol}{empty_line_symbol * frame_width}{vertical_symbol}"
framed_lines.append(empty_line)
elif line == "<LINE_FILL>":
fill_line = f"{vertical_symbol}{line_fill_symbol * frame_width}{vertical_symbol}"
framed_lines.append(fill_line)
else:
content = line.center(frame_width)
framed_line = f"{vertical_symbol}{content}{vertical_symbol}"
framed_lines.append(framed_line)
# Build the bottom border
bottom_border = f"{corner_symbols[2]}{horizontal_symbol * frame_width}{corner_symbols[3]}"
framed_lines.append(bottom_border)
# Ensure all lines start with the comment symbol
commented_lines = [
line if line.startswith(comment) else f"{comment} {line}" for line in framed_lines
]
return "\n".join(commented_lines)+"\n"
# descriptor for callable script
class CallableScript:
"""
A descriptor that allows the method Interactions to be accessed both as a property and as a callable function.
This class enables a method to behave like a property when accessed without parentheses,
returning a function that can be called with default parameters. It also allows the method
to be called directly with optional parameters, providing flexibility in usage.
Attributes:
-----------
func : function
The original function that is decorated, which will be used for both property access
and direct calls.
Methods:
--------
__get__(self, instance, owner)
Returns a lambda function to call the original function with default parameters
when accessed as a property.
__call__(self, instance, printflag=False, verbosity=2)
Allows the original function to be called directly with specified parameters.
"""
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
# When accessed as a property, return a lambda that calls the original function
return lambda printflag=False, verbosity=2, verbose=None: self.func(instance, printflag=printflag, verbosity=verbosity, verbose=verbose)
def __call__(self, instance, printflag=False, verbosity=2, verbose=None):
# Allow calling the function directly with specified parameters
return self.func(instance, printflag=printflag, verbosity=verbosity)
# %% Top generic classes for storing script data and objects
# they are not intended to be used outside script data and objects
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"
# object data (for scripts)
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
# group of script objects (special kind of list)
class scriptobjectgroup(struct):
"""
scriptobjectgroup: A Class for Managing Groups of Script Objects in LAMMPS
The `scriptobjectgroup` class is designed to represent a group of `scriptobject` instances,
such as beads or atoms in a simulation. This class allows users to group objects together
based on their properties (e.g., beadtype, name), and provides tools to generate scripts
that define interactions, groups, and forcefields for these objects in LAMMPS.
Key Features:
-------------
- **Group Management**: Objects can be combined into a group, where each `beadtype` occurs
once. The class ensures that objects are uniquely identified by their `beadtype` and `name`.
- **Dynamic Properties**: The group’s properties (e.g., `beadtype`, `name`, `groupname`)
are dynamically calculated, ensuring that the group reflects the current state of the objects.
- **Script Generation**: Provides methods to generate scripts based on the group's objects,
including interaction forcefields and group definitions.
- **Interaction Accumulation**: Automatically accumulates and updates all forcefield
interactions for the objects in the group.
Practical Use Cases:
--------------------
- **LAMMPS Group Definitions**: Define groups of objects for use in LAMMPS simulations,
based on properties like `beadtype` and `groupname`.
- **Forcefield Management**: Automatically manage and update interaction forcefields for
objects in the group.
- **Script Generation**: Generate LAMMPS-compatible scripts that include group definitions,
input file handling, and interaction forcefields.
Methods:
--------
__init__(self, *SOgroup):
Initializes a new `scriptobjectgroup` with one or more `scriptobject` instances.
__str__(self):
Returns a string representation of the `scriptobjectgroup`, showing the number of objects
in the group and their `beadtypes`.
__add__(self, SOgroup):
Combines two `scriptobjectgroup` instances or a `scriptobject` with an existing group,
ensuring that `beadtype` values are unique.
__or__(self, pipe):
Overloads the pipe (`|`) operator to integrate the group into a pipeline.
select(self, beadtype=None):
Selects and returns a subset of the group based on the specified `beadtype`.
script(self, printflag=False, verbosity=2, verbose=None):
Generates a script based on the current collection of objects, including input file
handling, group definitions, and interaction forcefields.
interactions(self, printflag=False, verbosity=2, verbose=None):
Updates and accumulates all forcefields for the objects in the group.
group_generator(self, name=None):
Generates and returns a `group` object, based on the existing group structure.
Properties:
-----------
- list : Converts the group into a sorted list of objects.
- zip : Returns a sorted list of tuples containing `beadtype`, `name`, `group`, and `filename`
for each object.
- n : Returns the number of objects in the group.
- beadtype : Returns a list of the `beadtypes` for all objects in the group.
- name : Returns a list of the `names` for all objects in the group.
- groupname : Returns a list of all group names (synonyms).
- filename : Returns a dictionary mapping filenames to the objects that use them.
- str : Returns a string representation of the group's `beadtypes`.
- min : Returns the minimum `beadtype` in the group.
- max : Returns the maximum `beadtype` in the group.
- minmax : Returns a tuple of the minimum and maximum `beadtypes` in the group.
- forcefield : Returns the interaction forcefields for the group.
Original Content:
-----------------
The `scriptobjectgroup` class enables the collection and management of multiple
`scriptobject` instances, providing the following functionalities:
- **Group Creation**: Groups are automatically formed by combining individual objects
using the `+` operator. Each `beadtype` occurs only once in the group, and errors are
raised if an object with the same `name` or `beadtype` already exists.
- **Dynamic Properties**: Properties such as `beadtype`, `name`, `groupname`, and `filename`
are dynamically calculated, reflecting the current state of the objects.
- **Forcefield Handling**: Forcefields are automatically managed for the objects in the group,
including diagonal and off-diagonal terms for pair interactions.
- **Script Generation**: Scripts are generated to define the interactions, groups, and
input file handling for LAMMPS.
Example Usage:
--------------
```
from pizza.scriptobject import scriptobject, scriptobjectgroup, rigidwall, solidfood, water
# Define some script objects
b1 = scriptobject(name="bead 1", group=["A", "B", "C"], filename='myfile1', forcefield=rigidwall())
b2 = scriptobject(name="bead 2", group=["B", "C"], filename='myfile1', forcefield=rigidwall())
b3 = scriptobject(name="bead 3", group=["B", "D", "E"], forcefield=solidfood())
b4 = scriptobject(name="bead 4", group="D", beadtype=1, filename="myfile2", forcefield=water())
# Combine objects into a group
collection = b1 + b2 + b3 + b4
# Select a subset of objects and generate a script
grp_typ1 = collection.select(1)
grpB = collection.group.B
script12 = collection.select([1, 2]).script()
```
Output:
```
script object group with 4 objects (1 2 3 4)
script
```
OVERVIEW:
--------------
class of script object group
script object groups are built from script objects OBJ1, OBJ2,..
GRP = scriptobjectgroup(OBJ1,OBJ2,...)
GRP = OBJ1+OBJ2+...
note: each beadtype occurs once in the group (if not an error message is generated)
List of methods
struct() converts data as structure
select([1,2,4]) selects objects with matching beadtypes
List of properties (dynamically calculated)
converted data: list, str, zip, beadtype, name, groupname, group, filename
numeric: len, min, max, minmax
forcefield related: interactions, forcefield
script: generate the script (load,group,forcefield)
Full syntax (toy example)
b1 = scriptobject(name="bead 1",group = ["A", "B", "C"],filename='myfile1',forcefield=rigidwall())
b2 = scriptobject(name="bead 2", group = ["B", "C"],filename = 'myfile1',forcefield=rigidwall())
b3 = scriptobject(name="bead 3", group = ["B", "D", "E"],forcefield=solidfood())
b4 = scriptobject(name="bead 4", group = "D",beadtype = 1,filename="myfile2",forcefield=water())
note: beadtype are incremented during the collection (effect of order)
# generate a collection, select a typ 1 and a subgroup, generate the script for 1,2
collection = b1+b2+b3+b4
grp_typ1 = collection.select(1)
grpB = collection.group.B
script12 = collection.select([1,2]).script
note: collection.group.B returns a strcture with 6 fields
-----------:----------------------------------------
groupid: 2 <-- automatic group numbering
groupidname: B <-- group name
groupname: ['A', 'B', 'C', 'D', 'E'] <--- snonyms
beadtype: [1, 2, 3] <-- beads belonging to B
name: ['bead 1', 'bead 2', 'bead 3'] <-- their names
str: group B 1 2 3 <-- LAMMPS syntax
-----------:----------------------------------------
"""
_type = "SOG"
_fulltype = "script object group"
_ftype = "object"
_propertyasattribute = True
def __init__(self,*SOgroup):
""" SOG constructor """
super(scriptobjectgroup,self).__init__()
beadtypemax = 0
names = []
for k in range(len(SOgroup)):
if isinstance(SOgroup[k],scriptobject):
if SOgroup[k].beadtype<beadtypemax or SOgroup[k].beadtype==None:
beadtypemax +=1
SOgroup[k].beadtype = beadtypemax
if SOgroup[k].name not in names:
self.setattr(SOgroup[k].name,SOgroup[k])
beadtypemax = SOgroup[k].beadtype
else:
raise ValueError('the script object "%s" already exists' % SOgroup[k].name)
names.append(SOgroup[k].name)
else:
raise ValueError("the argument #%d is not a script object")
def __str__(self):
""" string representation """
return f"{self._fulltype} with {len(self)} {self._ftype}s ({span(self.beadtype)})"
def __add__(self, SOgroup):
""" overload + """
beadlist = self.beadtype
dup = duplicate(self)
if isinstance(SOgroup,scriptobject):
if SOgroup.name not in self.keys():
if SOgroup.beadtype in beadlist and \
(SOgroup.beadtype==None or SOgroup.beadtype==self.min):
SOgroup.beadtype = self.max+1
if SOgroup.beadtype not in beadlist:
dup.setattr(SOgroup.name, SOgroup)
beadlist.append(SOgroup.beadtype)
return dup
else:
raise ValueError('%s (beadtype=%d) is already in use, same beadtype' \
% (SOgroup.name,SOgroup.beadtype))
else:
raise ValueError('the object "%s" is already in the list' % SOgroup.name)
elif isinstance(SOgroup,scriptobjectgroup):
for k in SOgroup.keys():
if k not in dup.keys():
if SOgroup.getattr(k).beadtype not in beadlist:
dup.setattr(k,SOgroup.getattr(k))
beadlist.append(SOgroup.getattr(k).beadtype)
else:
raise ValueError('%s (beadtype=%d) is already in use, same beadtype' \
% (k,SOgroup.getattr(k).beadtype))
else:
raise ValueError('the object "%s" is already in the list' % k)
return dup
else:
raise ValueError("the argument #%d is not a script object or a script object group")
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")
@property
def list(self):
""" convert into a list """
return sorted(self)
@property
def zip(self):
""" zip beadtypes and names """
return sorted( \
[(self.getattr(k).beadtype,self.getattr(k).name,self.getattr(k).group,self.getattr(k).filename) \
for k in self.keys()])
@property
def n(self):
""" returns the number of bead types """
return len(self)
@property
def beadtype(self):
""" returns the beads in the group """
return [x for x,_,_,_ in self.zip]
@property
def name(self):
""" "return the list of names """
return [x for _,x,_,_ in self.zip]
@property
def groupname(self):
""" "return the list of groupnames """
grp = []
for _,_,glist,_ in self.zip:
for g in glist:
if g not in grp: grp.append(g)
return grp
@property
def filename(self):
""" "return the list of names as a dictionary """
files = {}
for _,n,_,fn in self.zip:
if fn != "":
if fn not in files:
files[fn] = [n]
else:
files[fn].append(n)
return files
@property
def str(self):
return span(self.beadtype)
def struct(self,groupid=1,groupidname="undef"):
""" create a group with name """
return struct(
groupid = groupid,
groupidname = groupidname,
groupname = self.groupname, # meaning is synonyms
beadtype = self.beadtype,
name = self.name,
str = "group %s %s" % (groupidname, span(self.beadtype))
)
@property
def minmax(self):
""" returns the min,max of beadtype """
return self.min,self.max
@property
def min(self):
""" returns the min of beadtype """
return min(self.beadtype)
@property
def max(self):
""" returns the max of beadtype """
return max(self.beadtype)
def select(self,beadtype=None):
""" select bead from a keep beadlist """
if beadtype==None: beadtype = list(range(self.min,self.max+1))
if not isinstance(beadtype,(list,tuple)): beadtype = [beadtype]
dup = scriptobjectgroup()
for b,n,_,_ in self.zip:
if b in beadtype:
dup = dup + self.getattr(n)
dup.getattr(n).USER = self.getattr(n).USER
dup.getattr(n).forcefield = self.getattr(n).forcefield
return dup
@property
def group(self):
""" build groups from group (groupname contains synonyms) """
groupdef = struct()
gid = 0
bng = self.zip
for g in self.groupname:
gid +=1
b =[x for x,_,gx,_ in bng if g in gx]
groupdef.setattr(g,self.select(b).struct(groupid = gid, groupidname = g))
return groupdef
@CallableScript
def interactions(self, printflag=False, verbosity=2, verbose=None):
""" update and accumulate all forcefields """
verbosity = 0 if verbose is False else verbosity
FF = []
for b in self.beadtype:
selection = deepduplicate(self.select(b)[0])
selection.forcefield.beadtype = selection.beadtype
selection.forcefield.userid = selection.name
FF.append(selection.forcefield)
# initialize interactions with pair_style
TEMPLATE = "\n# ===== [ BEGIN FORCEFIELD SECTION ] "+"="*80 if verbosity>0 else ""
TEMPLATE = FF[0].pair_style(verbose=verbosity>0)
# pair diagonal terms
for i in range(len(FF)):
TEMPLATE += FF[i].pair_diagcoeff(verbose=verbosity>0)
# pair off-diagonal terms
for j in range(1,len(FF)):
for i in range(0,j):
TEMPLATE += FF[i].pair_offdiagcoeff(o=FF[j],verbose=verbosity>0)
# end
TEMPLATE += "\n# ===== [ END FORCEFIELD SECTION ] "+"="*82+"\n" if verbosity>0 else ""
return FF,TEMPLATE
@property
def forcefield(self):
""" interaction forcefields """
FF,_ = self.interactions
return FF
@CallableScript
def script(self, printflag=False, verbosity=None, verbose=None):
"""
Generate a script based on the current collection of script objects
Parameters:
-----------
printflag : bool, optional, default=False
If True, prints the generated script.
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
Returns:
--------
script
The generated script describing the interactions between script objects.
"""
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
TEMPFILES = ""
isfirst = True
files_added = False
if self.filename:
for fn, cfn in self.filename.items():
if fn and cfn:
if not files_added:
files_added = True
TEMPFILES += "\n# ===== [ BEGIN INPUT FILES SECTION ] " + "=" * 79 + "\n" if verbosity>0 else ""
TEMPFILES += span(cfn, sep=", ", left="\n# load files for objects: ", right="\n") if verbosity>1 else ""
if isfirst:
isfirst = False
TEMPFILES += f"\tread_data {fn}\n" # First file, no append
else:
TEMPFILES += f"\tread_data {fn} add append\n" # Subsequent files, append
# define groups
TEMPGRP = "\n# ===== [ BEGIN GROUP SECTION ] "+"="*85 + "\n" if verbosity>0 else ""
for g in self.group:
TEMPGRP += f'\n\t#\tDefinition of group {g.groupid}:{g.groupidname}\n' if verbosity>1 else ""
TEMPGRP += f'\t#\t={span(g.name,sep=", ")}\n' if verbosity>1 else ""
TEMPGRP += f'\t#\tSimilar groups: {span(g.groupname,sep=", ")}\n' if verbosity>1 else ""
TEMPGRP += f'\tgroup \t {g.groupidname} \ttype \t {span(g.beadtype)}\n'
TEMPGRP += "\n# ===== [ END GROUP SECTION ] "+"="*87+"\n\n" if verbosity>0 else ""
# define interactions
_,TEMPFF = self.interactions(printflag=printflag, verbosity=verbosity)
# chain strings into a script
tscript = script(printflag=False,verbose=verbosity>1)
tscript.name = "scriptobject script" # name
tscript.description = str(self) # description
tscript.userid = "scriptobject" # user name
tscript.TEMPLATE = TEMPFILES+TEMPGRP+TEMPFF
if verbosity==0:
tscript.TEMPLATE = remove_comments(tscript.TEMPLATE)
if printflag:
repr(tscript)
return tscript
def group_generator(self, name=None):
"""
Generate and return a group object.
This method creates a new `group` object, optionally with a specified name.
If no name is provided, it generates a default name based on the current
instance's `name` attribute, formatted with the `span` function. The method
then iterates through the existing groups in `self.group`, adding each group
to the new `group` object based on its `groupidname` and `beadtype`.
Parameters:
-----------
name : str, optional
The name for the generated group object. If not provided, a default name
is generated based on the current instance's `name`.
Returns:
--------
group
A newly created `group` object with criteria set based on the existing groups.
"""
from pizza.group import group
# Use the provided name or generate a default name using the span function
G = group(name=name if name is not None else span(self.name, ",", "[", "]"))
# Add criteria for each group in self.group
for g in self.group:
G.add_group_criteria(g.groupidname, type=g.beadtype)
return G
def mass(self, name=None, default_mass="${mass}", printflag=False, verbosity=2, verbose=True):
"""
Generates LAMMPS mass commands for each unique beadtype in the collection.
The method iterates through all `scriptobjectgroup` instances in the collection,
collects unique beadtypes, and ensures that each beadtype has a consistent mass.
If a beadtype has `mass=None`, it assigns a default mass as specified by `default_mass`.
### Parameters:
name (str, optional):
The name to assign to the resulting `script` object. Defaults to a generated name.
default_mass (str, int, or float, optional):
The default mass value to assign when a beadtype's mass is `None`.
Can be a string, integer, or floating-point value. Defaults to `"${mass}"`.
printflag (bool, optional):
If `True`, prints the representation of the resulting `script` object. Defaults to `False`.
verbosity (int, optional):
The verbosity level for logging or debugging. Higher values indicate more detailed output.
Defaults to `2`.
verbose (bool, optional):
If `True`, includes a comment header in the output. Overrides `verbosity` when `False`.
Defaults to `True`.
### Returns:
script: A `script` object containing the mass commands for each beadtype, formatted as follows:
```
mass 1 1.0
mass 2 ${mass}
mass 3 2.5
```
The `TEMPLATE` attribute of the `script` object holds the formatted mass commands as a single string.
### Raises:
ValueError: If a beadtype has inconsistent mass values across different `scriptobjectgroup` instances.
### Example:
```python
# Create scriptobjectgroup instances
obj1 = scriptobjectgroup(beadtype=1, group=["all", "A"], mass=1.0)
obj2 = scriptobjectgroup(beadtype=2, group=["all", "B", "C"])
obj3 = scriptobjectgroup(beadtype=3, group="C", mass=2.5)
# Initialize a script group with the scriptobjectgroup instances
G = scriptobjectgroup([obj1, obj2, obj3])
# Generate mass commands
M = G.mass()
print(M.do())
```
**Output:**
```
# <script:group:mass> definitions for 3 beads
mass 1 1.0
mass 2 ${mass}
mass 3 2.5
```
"""
verbosity = 0 if verbose is False else verbosity
beadtype_mass = {}
for iobj in range(0,len(self)):
obj = self[iobj]
bt = obj.beadtype
mass = obj.mass if obj.mass is not None else default_mass
if bt in beadtype_mass:
if beadtype_mass[bt] != mass:
raise ValueError(
f"Inconsistent masses for beadtype {bt}: {beadtype_mass[bt]} vs {mass}"
)
else:
beadtype_mass[bt] = mass
# Sort beadtypes for consistent ordering
sorted_beadtypes = sorted(beadtype_mass.keys())
# Generate mass commands
lines = [f"mass {bt} {beadtype_mass[bt]}" for bt in sorted_beadtypes]
# return a script object
nameid = f"<script:group:{self.name}:mass>"
description = f"{nameid} definitions for {len(self)} beads"
if verbose:
lines.insert(0, "# "+description)
mscript = script(printflag=False,verbose=verbosity>1)
mscript.name = nameid if name is None else name
mscript.description = description
mscript.userid = "scriptobject" # user name
mscript.TEMPLATE = "\n".join(lines)
mscript.DEFINITIONS.mass = default_mass
if printflag:
repr(mscript)
return mscript
# %% script core class
# note: please derive this class when you use it, do not alter it
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)
# %% pipe script
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)
# %% Child classes of script sessions (to be derived)
# navigate with the outline tab through the different classes
# globalsection()
# initializesection()
# geometrysection()
# discretizationsection()
# interactionsection()
# integrationsection()
# dumpsection()
# statussection()
# runsection()
# %% Global section template
class globalsection(script):
""" LAMMPS script: global session """
name = "global"
description = name+" section"
position = 0
section = 1
userid = "example"
version = 0.1
DEFINITIONS = scriptdata(
outputfile= "$dump.mouthfeel_v5_long # from the project of the same name",
tsim= "500000 # may be too long",
outstep= 10
)
MATERIALS = scriptdata(
rho_saliva= "1000 # mass density saliva",
rho_obj= "1300 # mass density solid objects",
c0= "10.0 # speed of sound for saliva",
E= "5*${c0}*${c0}*${rho_saliva} # Young's modulus for solid objects",
Etongue1= "10*${E} # Young's modulus for tongue",
Etongue2= "2*${Etongue1} # Young's modulus for tongue",
nu= "0.3 # Poisson ratio for solid objects",
sigma_yield= "0.1*${E} # plastic yield stress for solid objects",
hardening_food= "0 # plastic hardening parameter for solid food",
hardening_tongue= "1 # plastic hardening parameter for solid tongue",
contact_stiffness= "2.5*${c0}^2*${rho_saliva} # contact force amplitude",
contact_wall= "100*${contact_stiffness} # contact with wall (avoid interpenetration)",
q1= "1.0 # artificial viscosity",
q2= "0.0 # artificial viscosity",
Hg= "10 # Hourglass control coefficient for solid objects",
Cp= "1.0 # heat capacity -- not used here"
)
DEFINITIONS += MATERIALS # append MATERIALS data
TEMPLATE = """
# :GLOBAL SECTION:
# avoid to set variables in LAMMPS script
# use DEFINITIONS field to set properties.
# If you need to define them, use the following syntax
# ####################################################################################################
# # GLOBAL
# ####################################################################################################
variable outputfile string "${outputfile}"
variable tsim equal ${tsim}
variable outstep equal ${outstep}
# ####################################################################################################
# # MATERIAL PARAMETERS
# ####################################################################################################
# variable rho_saliva equal 1000 # mass density saliva
# variable rho_obj equal 1300 # mass density solid objects
# variable c0 equal 10.0 # speed of sound for saliva
# variable E equal 5*${c0}*${c0}*${rho_saliva} # Young's modulus for solid objects
# variable Etongue1 equal 10*${E} # Young's modulus for tongue
# variable Etongue2 equal 2*${Etongue1} # Young's modulus for tongue
# variable nu equal 0.3 # Poisson ratio for solid objects
# variable sigma_yield equal 0.1*${E} # plastic yield stress for solid objects
# variable hardening_food equal 0 # plastic hardening parameter for solid food
# variable hardening_tongue equal 1 # plastic hardening parameter for solid tongue
# variable contact_stiffness equal 2.5*${c0}^2*${rho_saliva} # contact force amplitude
# variable contact_wall equal 100*${contact_stiffness} # contact with wall (avoid interpenetration)
# variable q1 equal 1.0 # artificial viscosity
# variable q2 equal 0.0 # artificial viscosity
# variable Hg equal 10 # Hourglass control coefficient for solid objects
# variable Cp equal 1.0 # heat capacity -- not used here
"""
# %% Initialize section template
class initializesection(script):
""" LAMMPS script: global session """
name = "initialize"
description = name+" section"
position = 1
section = 2
userid = "example"
version = 0.1
DEFINITIONS = scriptdata(
units= "$ si",
dimension= 2,
boundary= "$ sm sm p",
atom_style= "$smd",
neigh_modify_every= 5,
neigh_modify_delay= 0,
comm_modify= "$ vel yes",
newton= "$ off",
atom_modify= "$ map array",
comm_style= "$ tiled"
)
TEMPLATE = """
# :INITIALIZE SECTION:
# initialize styles, dimensions, boundaries and communivation
####################################################################################################
# INITIALIZE LAMMPS
####################################################################################################
units ${units}
dimension ${dimension}
boundary ${boundary}
atom_style ${atom_style}
neigh_modify every ${neigh_modify_every} delay ${neigh_modify_delay} check yes
comm_modify ${comm_modify}
newton ${newton}
atom_modify ${atom_modify}
comm_style ${comm_style}
"""
# %% Geometry section template
class geometrysection(script):
""" LAMMPS script: global session """
name = "geometry"
description = name+" section"
position = 2
section = 3
userid = "example"
version = 0.1
DEFINITIONS = scriptdata(
l0= 0.05,
hgap= "0.25 # gap to prevent direct contact at t=0 (too much enery)",
hsmallgap= "0.1 # gap to prevent direct contact at t=0 (too much enery)",
hto1= "0.8 # height of to1 (the tongue to1, note 1 not l)",
hto2= "0.5 # height of to2 (the tongue to2)",
rsph= "0.3 # radius of spherical food particles",
lpar= "0.6 # size of prismatic particles ",
yfloor1= "${hgap} # bottom position of to1, position of the first floor",
yroof1= "${yfloor1}+${hto1} # bottom position of to1, position of the first floor",
yfloor2a= "${yroof1}+${hsmallgap} # position of the second floor / level a",
yroof2a= "${yfloor2a}+${lpar} # position of the second floor / level a",
yfloor2b= "${yroof2a}+${hsmallgap} # position of the second floor / level b",
yroof2b= "${yfloor2b}+${lpar} # position of the second floor / level b",
yfloor2c= "${yfloor2a}+${rsph} # position of the second floor / level c",
yroof2c= "${yfloor2c}+${rsph} # position of the second floor / level c",
yfloor2d= "${yroof2c}+${rsph}+${hsmallgap} # position of the second floor / level d",
yroof2d= "${yfloor2d}+${rsph} # position of the second floor / level d",
yfloor3= 5.0,
yroof3= "${yfloor3}+${hto2} # bottom position of to1",
yfloor3a= "${yfloor3}-0.6",
yroof3a= "${yfloor3}",
crunchl= "${yfloor3}-${yfloor2a}-0.8",
crunchp= 3,
crunchw= "2*pi/${crunchp}",
crunchd= "2*(sin((${crunchp}*${crunchw})/4)^2)/${crunchw}",
crunchv= "${crunchl}/${crunchd}"
)
TEMPLATE = """
# :GEOMETRY SECTION:
# Build geometry (very specific example)
####################################################################################################
# CREATE INITIAL GEOMETRY
# note there are 4 groups (create_box 5 box)
# groupID 1 = saliva
# groupID 2 = food
# groupID 3 = mouth walls
# groupID 4 = tongue alike (part1)
# groupID 5 = also tongue but palate infact (part2)
####################################################################################################
# create simulation box, a mouth, and a saliva column
region box block 0 12 0 8 -0.01 0.01 units box
create_box 5 box
region saliva1 block 0.25 1.8 1.25 3.5 EDGE EDGE units box
region saliva2 block 10 11.65 1.25 4 EDGE EDGE units box
region mouth block 0.15 11.85 0.15 8 -0.01 0.01 units box side out # mouth
lattice sq ${l0}
create_atoms 1 region saliva1
create_atoms 1 region saliva2
group saliva type 1
create_atoms 3 region mouth
group mouth type 3
print "Crunch distance:${crunchl}" # 3.65
print "Crunch distance:${crunchv}" # 0.1147
# bottom part of the tongue: to1 (real tongue)
# warning: all displacements are relative to the bottom part
region to1 block 1 11 ${yfloor1} ${yroof1} EDGE EDGE units box
region to2part1 block 0.5 11.5 ${yfloor3} ${yroof3} EDGE EDGE units box
region to2part2 block 5.5 6 ${yfloor3a} ${yroof3a} EDGE EDGE units box
region to2 union 2 to2part1 to2part2
create_atoms 4 region to1
create_atoms 5 region to2
group tongue1 type 4
group tongue2 type 5
# create some solid objects to be pushed around
region pr1 prism 2 2.6 ${yfloor2a} ${yroof2a} EDGE EDGE 0.3 0 0 units box
region bl1 block 3 3.6 ${yfloor2a} ${yroof2a} EDGE EDGE units box
region sp1 sphere 4.3 ${yfloor2c} 0 ${rsph} units box
region sp2 sphere 5 ${yfloor2c} 0 ${rsph} units box
region sp3 sphere 5.7 ${yfloor2c} 0 ${rsph} units box
region sp4 sphere 6.4 ${yfloor2c} 0 ${rsph} units box
region sp5 sphere 7.1 ${yfloor2c} 0 ${rsph} units box
region sp6 sphere 6.05 ${yfloor2d} 0 ${rsph} units box
region br2 block 3 3.6 ${yfloor2b} ${yroof2b} EDGE EDGE units box
# fill the regions with atoms (note that atoms = smoothed hydrodynamics particles)
create_atoms 2 region pr1
create_atoms 2 region bl1
create_atoms 2 region sp1
create_atoms 2 region sp2
create_atoms 2 region sp3
create_atoms 2 region sp4
create_atoms 2 region sp5
create_atoms 2 region sp6
create_atoms 2 region br2
# atoms of objects are grouped with two id
# fix apply only to groups
group solidfoods type 2
group tlsph type 2
# group heavy
group allheavy type 1:4
"""
# %% Discretization section template
class discretizationsection(script):
""" LAMMPS script: discretization session """
name = "discretization"
description = name+" section"
position = 3
section = 4
userid = "example"
version = 0.1
# inherit properties from geometrysection
DEFINITIONS = geometrysection.DEFINITIONS + scriptdata(
h= "2.5*${l0} # SPH kernel diameter",
vol_one= "${l0}^2 # initial particle volume for 2d simulation",
rho_saliva= 1000,
rho_obj= 1300,
skin= "${h} # Verlet list range",
contact_scale= 1.5
)
TEMPLATE = """
# :DISCRETIZATION SECTION:
# discretization
####################################################################################################
# DISCRETIZATION PARAMETERS
####################################################################################################
set group all diameter ${h}
set group all smd/contact/radius ${l0}
set group all volume ${vol_one}
set group all smd/mass/density ${rho_saliva}
set group solidfoods smd/mass/density ${rho_obj}
set group tongue1 smd/mass/density ${rho_obj}
set group tongue2 smd/mass/density ${rho_obj}
neighbor ${skin} bin
"""
# %% Boundary section template
class boundarysection(script):
""" LAMMPS script: boundary session """
name = "boundary"
description = name+" section"
position = 4
section = 5
userid = "example"
version = 0.1
# inherit properties from geometrysection
DEFINITIONS = geometrysection.DEFINITIONS + scriptdata(
gravity = -9.81,
vector = "$ 0 1 0"
)
TEMPLATE = """
# :BOUNDARY SECTION:
# boundary section
####################################################################################################
# DEFINE BOUNDARY CONDITIONS
#
# note that the the particles constituting the mouth are simply not integrated in time,
# thus these particles never move. This is equivalent to a fixed displacement boundary condition.
####################################################################################################
fix gfix allheavy gravity ${gravity} vector ${vector} # add gravity
####################################################################################################
# moving top "tongue" (to2)
####################################################################################################
variable vmouth equal -${crunchv}*sin(${crunchw}*time)
fix move_fix_tongue2 tongue2 smd/setvel 0 v_vmouth 0
"""
# %% Interactions section template
class interactionsection(script):
""" LAMMPS script: interaction session """
name = "interactions"
description = name+" section"
position = 5
section = 6
userid = "example"
version = 0.1
DEFINITIONS = globalsection.DEFINITIONS + \
geometrysection.DEFINITIONS + \
discretizationsection.DEFINITIONS
TEMPLATE = """
# :INTERACTIONS SECTION:
# Please use forcefield() to make a robust code
####################################################################################################
# INTERACTION PHYSICS / MATERIAL MODEL
# 3 different pair styles are used:
# - updated Lagrangian SPH for saliva
# - total Lagrangian SPH for solid objects
# - a repulsive Hertzian potential for contact forces between different physical bodies
####################################################################################################
pair_style hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION &
smd/tlsph smd/hertz ${contact_scale}
pair_coeff 1 1 smd/ulsph *COMMON ${rho_saliva} ${c0} ${q1} ${Cp} 0 &
*EOS_TAIT 7.0 &
*END
pair_coeff 2 2 smd/tlsph *COMMON ${rho_obj} ${E} ${nu} ${q1} ${q2} ${Hg} ${Cp} &
*STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_food} &
*EOS_LINEAR &
*END
pair_coeff 4 4 smd/tlsph *COMMON ${rho_obj} ${Etongue1} ${nu} ${q1} ${q2} ${Hg} ${Cp} &
*STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_tongue} &
*EOS_LINEAR &
*END
pair_coeff 5 5 smd/tlsph *COMMON ${rho_obj} ${Etongue2} ${nu} ${q1} ${q2} ${Hg} ${Cp} &
*STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_tongue} &
*EOS_LINEAR &
*END
pair_coeff 3 3 none # wall-wall
pair_coeff 1 2 smd/hertz ${contact_stiffness} # saliva-food
pair_coeff 1 3 smd/hertz ${contact_wall} # saliva-wall
pair_coeff 2 3 smd/hertz ${contact_wall} # food-wall
pair_coeff 2 2 smd/hertz ${contact_stiffness} # food-food
# add 4 (to1)
pair_coeff 1 4 smd/hertz ${contact_stiffness} # saliva-tongue1
pair_coeff 2 4 smd/hertz ${contact_stiffness} # food-tongue1
pair_coeff 3 4 smd/hertz ${contact_wall} # wall-tongue1
pair_coeff 4 4 smd/hertz ${contact_stiffness} # tongue1-tongue1
# add 5 (to2)
pair_coeff 1 5 smd/hertz ${contact_stiffness} # saliva-tongue2
pair_coeff 2 5 smd/hertz ${contact_stiffness} # food-tongue2
pair_coeff 3 5 smd/hertz ${contact_wall} # wall-tongue2
pair_coeff 4 5 smd/hertz ${contact_stiffness} # tongue1-tongue2
pair_coeff 5 5 smd/hertz ${contact_stiffness} # tongue2-tongue2
"""
# %% Time integration section template
class integrationsection(script):
""" LAMMPS script: time integration session """
name = "time integration"
description = name+" section"
position = 6
section = 7
userid = "example"
version = 0.1
DEFINITIONS = scriptdata(
dt = 0.1,
adjust_redius = "$ 1.01 10 15"
)
TEMPLATE = """
# :INTEGRATION SECTION:
# Time integration conditions
fix dtfix tlsph smd/adjust_dt ${dt} # dynamically adjust time increment every step
fix integration_fix_water saliva smd/integrate_ulsph adjust_radius ${adjust_redius}
fix integration_fix_solids solidfoods smd/integrate_tlsph
fix integration_fix_tongue1 tongue1 smd/integrate_tlsph
fix integration_fix_tongue2 tongue2 smd/integrate_tlsph
"""
# %% Dump section template
class dumpsection(script):
""" LAMMPS script: dump session """
name = "dump"
description = name+" section"
position = 7
section = 8
userid = "example"
version = 0.1
DEFINITIONS = globalsection().DEFINITIONS
TEMPLATE = """
# :DUMP SECTION:
# Dump configuration
####################################################################################################
# SPECIFY TRAJECTORY OUTPUT
####################################################################################################
compute eint all smd/internal/energy
compute contact_radius all smd/contact/radius
compute S solidfoods smd/tlsph/stress
compute nn saliva smd/ulsph/num/neighs
compute epl solidfoods smd/plastic/strain
compute vol all smd/vol
compute rho all smd/rho
dump dump_id all custom ${outstep} ${outputfile} id type x y &
fx fy vx vy c_eint c_contact_radius mol &
c_S[1] c_S[2] c_S[4] mass radius c_epl c_vol c_rho c_nn proc
dump_modify dump_id first yes
"""
# %% Status section template
class statussection(script):
""" LAMMPS script: status session """
name = "status"
description = name+" section"
position = 8
section = 9
userid = "example"
version = 0.1
DEFINITIONS = scriptdata(
thermo = 100
)
TEMPLATE = """
# :STATUS SECTION:
# Status configuration
####################################################################################################
# STATUS OUTPUT
####################################################################################################
compute alleint all reduce sum c_eint
variable etot equal pe+ke+c_alleint+f_gfix # total energy of the system
thermo ${thermo}
thermo_style custom step ke pe v_etot c_alleint f_dtfix dt
thermo_modify lost ignore
"""
# %% Run section template
class runsection(script):
""" LAMMPS script: run session """
name = "run"
description = name+" section"
position = 9
section = 10
userid = "example"
version = 0.1
DEFINITIONS = globalsection.DEFINITIONS + scriptdata(
balance = "$ 500 0.9 rcb"
)
TEMPLATE = """
# :RUN SECTION:
# run configuration
####################################################################################################
# RUN SIMULATION
####################################################################################################
fix balance_fix all balance ${balance} # load balancing for MPI
run ${tsim}
"""
# %% DEBUG
# ===================================================
# main()
# ===================================================
# for debugging purposes (code called as a script)
# the code is called from here
# ===================================================
if __name__ == '__main__':
# example for debugging
# from pizza.region import region
# R = region(name="my region")
# 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()
# p = R.E2.script
# code = p.do(0)
# example of scriptobject()
b1 = scriptobject(name="bead 1",group = ["A", "B", "C"],filename='myfile1',forcefield=rigidwall())
b2 = scriptobject(name="bead 2", group = ["B", "C"],filename = 'myfile1',forcefield=rigidwall())
b3 = scriptobject(name="bead 3", group = ["B", "D", "E"],forcefield=solidfood())
b4 = scriptobject(name="bead 4", group = "D",beadtype = 1,filename="myfile2",forcefield=water())
collection = b1+b2+b3+b4
grp_typ1 = collection.select(1)
grpB = collection.group.B
collection.interactions
# main example of script()
G = globalsection()
print(G)
c = initializesection()
print(c)
g = geometrysection()
print(g)
d = discretizationsection()
print(d)
b = boundarysection()
print(b)
i = interactionsection()
print(i)
t = integrationsection()
print(t)
d = dumpsection()
print(d)
s = statussection()
print(s)
r = runsection()
print(r)
# # all sections as a single script
myscript = G+c+g+d+b+i+t+d+s+r
p = pipescript()
p | i
p = collection | G
p | i
p[0]
q = p | p
q[0] = []
p[0:1] = q[0:1]
print("\n"*4,'='*80,'\n\n this is the full script\n\n','='*80,'\n')
print(myscript.do())
# pipe full demo
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
p[0:2]=p[0]*2
Functions
def frame_header(lines, padding=2, style=1, corner_symbols=None, horizontal_symbol=None, vertical_symbol=None, empty_line_symbol=None, line_fill_symbol=None, comment='#')
-
Format the header content into an ASCII framed box with customizable properties.
Parameters
lines (list or tuple): The lines to include in the header. - Empty strings "" are replaced with lines of
line_fill_symbol
. - None values are treated as empty lines.padding (int, optional): Number of spaces to pad on each side of the content. Default is 2. style (int, optional): Style index (1 to 6) for predefined frame styles. Default is 1. corner_symbols (str or tuple, optional): Symbols for the corners (top-left, top-right, bottom-left, bottom-right). Can be a string (e.g., "+") for uniform corners. horizontal_symbol (str, optional): Symbol to use for horizontal lines. vertical_symbol (str, optional): Symbol to use for vertical lines. empty_line_symbol (str, optional): Symbol to use for empty lines inside the frame. line_fill_symbol (str, optional): Symbol to fill lines that replace empty strings. comment (str, optional): Comment symbol to prefix each line. Can be multiple characters. Default is "#".
Returns
str
- The formatted header as a string.
Raises
ValueError
- If the specified style is undefined or
corner_symbols
is invalid.
Expand source code
def frame_header( lines, padding=2, style=1, corner_symbols=None, # Can be a string or a tuple horizontal_symbol=None, vertical_symbol=None, empty_line_symbol=None, line_fill_symbol=None, comment="#" ): """ Format the header content into an ASCII framed box with customizable properties. Parameters: lines (list or tuple): The lines to include in the header. - Empty strings "" are replaced with lines of `line_fill_symbol`. - None values are treated as empty lines. padding (int, optional): Number of spaces to pad on each side of the content. Default is 2. style (int, optional): Style index (1 to 6) for predefined frame styles. Default is 1. corner_symbols (str or tuple, optional): Symbols for the corners (top-left, top-right, bottom-left, bottom-right). Can be a string (e.g., "+") for uniform corners. horizontal_symbol (str, optional): Symbol to use for horizontal lines. vertical_symbol (str, optional): Symbol to use for vertical lines. empty_line_symbol (str, optional): Symbol to use for empty lines inside the frame. line_fill_symbol (str, optional): Symbol to fill lines that replace empty strings. comment (str, optional): Comment symbol to prefix each line. Can be multiple characters. Default is "#". Returns: str: The formatted header as a string. Raises: ValueError: If the specified style is undefined or `corner_symbols` is invalid. """ # Predefined styles styles = { 1: { "corner_symbols": ("+", "+", "+", "+"), "horizontal_symbol": "-", "vertical_symbol": "|", "empty_line_symbol": " ", "line_fill_symbol": "-" }, 2: { "corner_symbols": ("╔", "╗", "╚", "╝"), "horizontal_symbol": "═", "vertical_symbol": "║", "empty_line_symbol": " ", "line_fill_symbol": "═" }, 3: { "corner_symbols": (".", ".", "'", "'"), "horizontal_symbol": "-", "vertical_symbol": "|", "empty_line_symbol": " ", "line_fill_symbol": "-" }, 4: { "corner_symbols": ("#", "#", "#", "#"), "horizontal_symbol": "=", "vertical_symbol": "#", "empty_line_symbol": " ", "line_fill_symbol": "=" }, 5: { "corner_symbols": ("┌", "┐", "└", "┘"), "horizontal_symbol": "─", "vertical_symbol": "│", "empty_line_symbol": " ", "line_fill_symbol": "─" }, 6: { "corner_symbols": (".", ".", ".", "."), "horizontal_symbol": ".", "vertical_symbol": ":", "empty_line_symbol": " ", "line_fill_symbol": "." } } # Validate style and set defaults if style not in styles: raise ValueError(f"Undefined style {style}. Valid styles are {list(styles.keys())}.") selected_style = styles[style] # Convert corner_symbols to a tuple of 4 values if isinstance(corner_symbols, str): corner_symbols = (corner_symbols,) * 4 elif isinstance(corner_symbols, (list, tuple)) and len(corner_symbols) == 1: corner_symbols = tuple(corner_symbols * 4) elif isinstance(corner_symbols, (list, tuple)) and len(corner_symbols) == 2: corner_symbols = (corner_symbols[0], corner_symbols[1], corner_symbols[0], corner_symbols[1]) elif corner_symbols is None: corner_symbols = selected_style["corner_symbols"] elif not isinstance(corner_symbols, (list, tuple)) or len(corner_symbols) != 4: raise ValueError("corner_symbols must be a string or a tuple/list of 1, 2, or 4 elements.") # Apply overrides or defaults horizontal_symbol = horizontal_symbol or selected_style["horizontal_symbol"] vertical_symbol = vertical_symbol or selected_style["vertical_symbol"] empty_line_symbol = empty_line_symbol or selected_style["empty_line_symbol"] line_fill_symbol = line_fill_symbol or selected_style["line_fill_symbol"] # Process lines: Replace "" with line_fill placeholders, None with empty lines processed_lines = [] max_content_width = 0 for line in lines: if line == "": processed_lines.append("<LINE_FILL>") elif line is None: processed_lines.append(None) else: processed_lines.append(line) max_content_width = max(max_content_width, len(line)) # Adjust width for padding frame_width = max_content_width + padding * 2 # Build the top border top_border = f"{corner_symbols[0]}{horizontal_symbol * frame_width}{corner_symbols[1]}" # Build content lines with vertical borders framed_lines = [top_border] for line in processed_lines: if line is None: empty_line = f"{vertical_symbol}{empty_line_symbol * frame_width}{vertical_symbol}" framed_lines.append(empty_line) elif line == "<LINE_FILL>": fill_line = f"{vertical_symbol}{line_fill_symbol * frame_width}{vertical_symbol}" framed_lines.append(fill_line) else: content = line.center(frame_width) framed_line = f"{vertical_symbol}{content}{vertical_symbol}" framed_lines.append(framed_line) # Build the bottom border bottom_border = f"{corner_symbols[2]}{horizontal_symbol * frame_width}{corner_symbols[3]}" framed_lines.append(bottom_border) # Ensure all lines start with the comment symbol commented_lines = [ line if line.startswith(comment) else f"{comment} {line}" for line in framed_lines ] return "\n".join(commented_lines)+"\n"
def get_metadata()
-
Return a dictionary of explicitly defined metadata.
Expand source code
def get_metadata(): """Return a dictionary of explicitly defined metadata.""" # Define the desired metadata keys metadata_keys = [ "__project__", "__author__", "__copyright__", "__credits__", "__license__", "__maintainer__", "__email__", "__version__", ] # Filter only the desired keys from the current module's globals return {key.strip("_"): globals()[key] for key in metadata_keys if key in globals()}
def get_tmp_location()
-
Expand source code
get_tmp_location = lambda: tempfile.gettempdir()
def picker(L, indices)
-
Expand source code
def picker(L,indices): return [L[i] for i in indices if (i>=0 and i<len(L))]
def remove_comments(content, split_lines=False, emptylines=False, comment_chars='#', continuation_marker='\\\\', remove_continuation_marker=False)
-
Removes comments from a single or multi-line string, handling quotes, escaped characters, and line continuation.
Parameters:
content : str The input string, which may contain multiple lines. Each line will be processed individually to remove comments, while preserving content inside quotes. split_lines : bool, optional (default: False) If True, the function will return a list of processed lines. If False, it will return a single string with all lines joined by newlines. emptylines : bool, optional (default: False) If True, empty lines will be preserved in the output. If False, empty lines will be removed from the output. comment_chars : str, optional (default: "#") A string containing characters to identify the start of a comment. Any of these characters will mark the beginning of a comment unless within quotes. continuation_marker : str or None, optional (default: "\") A string containing characters to indicate line continuation (use
\
to specify). Any characters after the continuation marker are ignored as a comment. If set toNone
or an empty string, line continuation will not be processed. remove_continuation_marker : bool, optional (default: False) If True, the continuation marker itself is removed from the processed line, keeping only the characters before it. If False, the marker is retained as part of the line.Returns:
str or list of str The processed content with comments removed. Returns a list of lines if
split_lines
is True, or a single string if False.Expand source code
def remove_comments(content, split_lines=False, emptylines=False, comment_chars="#", continuation_marker="\\\\", remove_continuation_marker=False): """ Removes comments from a single or multi-line string, handling quotes, escaped characters, and line continuation. Parameters: ----------- content : str The input string, which may contain multiple lines. Each line will be processed individually to remove comments, while preserving content inside quotes. split_lines : bool, optional (default: False) If True, the function will return a list of processed lines. If False, it will return a single string with all lines joined by newlines. emptylines : bool, optional (default: False) If True, empty lines will be preserved in the output. If False, empty lines will be removed from the output. comment_chars : str, optional (default: "#") A string containing characters to identify the start of a comment. Any of these characters will mark the beginning of a comment unless within quotes. continuation_marker : str or None, optional (default: "\\\\") A string containing characters to indicate line continuation (use `\\` to specify). Any characters after the continuation marker are ignored as a comment. If set to `None` or an empty string, line continuation will not be processed. remove_continuation_marker : bool, optional (default: False) If True, the continuation marker itself is removed from the processed line, keeping only the characters before it. If False, the marker is retained as part of the line. Returns: -------- str or list of str The processed content with comments removed. Returns a list of lines if `split_lines` is True, or a single string if False. """ def process_line(line): """Remove comments and handle line continuation within a single line while managing quotes and escapes.""" in_single_quote = False in_double_quote = False escaped = False result = [] i = 0 while i < len(line): char = line[i] if escaped: result.append(char) escaped = False i += 1 continue # Handle escape character within quoted strings if char == '\\' and (in_single_quote or in_double_quote): escaped = True result.append(char) i += 1 continue # Toggle state for single and double quotes if char == "'" and not in_double_quote: in_single_quote = not in_single_quote elif char == '"' and not in_single_quote: in_double_quote = not in_double_quote # Check for line continuation marker if it's set and outside of quotes if continuation_marker and not in_single_quote and not in_double_quote: # Check if the remaining part of the line matches the continuation marker if line[i:].startswith(continuation_marker): # Optionally remove the continuation marker if remove_continuation_marker: result.append(line[:i].rstrip()) # Keep everything before the marker else: result.append(line[:i + len(continuation_marker)].rstrip()) # Include the marker itself return ''.join(result).strip() # Check for comment start characters outside of quotes if char in comment_chars and not in_single_quote and not in_double_quote: break # Stop processing the line when a comment is found result.append(char) i += 1 return ''.join(result).strip() # Split the input content into lines lines = content.split('\n') # Process each line, considering the emptylines flag processed_lines = [] for line in lines: stripped_line = line.strip() if not stripped_line and not emptylines: continue # Skip empty lines if emptylines is False if any(stripped_line.startswith(c) for c in comment_chars): continue # Skip lines that are pure comments processed_line = process_line(line) if processed_line or emptylines: # Only add non-empty lines if emptylines is False processed_lines.append(processed_line) if split_lines: return processed_lines # Return list of processed lines else: return '\n'.join(processed_lines) # Join lines back into a single string
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 ""
Classes
class CallableScript (func)
-
A descriptor that allows the method Interactions to be accessed both as a property and as a callable function.
This class enables a method to behave like a property when accessed without parentheses, returning a function that can be called with default parameters. It also allows the method to be called directly with optional parameters, providing flexibility in usage.
Attributes:
func : function The original function that is decorated, which will be used for both property access and direct calls.
Methods:
get(self, instance, owner) Returns a lambda function to call the original function with default parameters when accessed as a property.
call(self, instance, printflag=False, verbosity=2) Allows the original function to be called directly with specified parameters.
Expand source code
class CallableScript: """ A descriptor that allows the method Interactions to be accessed both as a property and as a callable function. This class enables a method to behave like a property when accessed without parentheses, returning a function that can be called with default parameters. It also allows the method to be called directly with optional parameters, providing flexibility in usage. Attributes: ----------- func : function The original function that is decorated, which will be used for both property access and direct calls. Methods: -------- __get__(self, instance, owner) Returns a lambda function to call the original function with default parameters when accessed as a property. __call__(self, instance, printflag=False, verbosity=2) Allows the original function to be called directly with specified parameters. """ def __init__(self, func): self.func = func def __get__(self, instance, owner): # When accessed as a property, return a lambda that calls the original function return lambda printflag=False, verbosity=2, verbose=None: self.func(instance, printflag=printflag, verbosity=verbosity, verbose=verbose) def __call__(self, instance, printflag=False, verbosity=2, verbose=None): # Allow calling the function directly with specified parameters return self.func(instance, printflag=printflag, verbosity=verbosity)
class boundarysection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: boundary session
constructor adding instance definitions stored in USER
Expand source code
class boundarysection(script): """ LAMMPS script: boundary session """ name = "boundary" description = name+" section" position = 4 section = 5 userid = "example" version = 0.1 # inherit properties from geometrysection DEFINITIONS = geometrysection.DEFINITIONS + scriptdata( gravity = -9.81, vector = "$ 0 1 0" ) TEMPLATE = """ # :BOUNDARY SECTION: # boundary section #################################################################################################### # DEFINE BOUNDARY CONDITIONS # # note that the the particles constituting the mouth are simply not integrated in time, # thus these particles never move. This is equivalent to a fixed displacement boundary condition. #################################################################################################### fix gfix allheavy gravity ${gravity} vector ${vector} # add gravity #################################################################################################### # moving top "tongue" (to2) #################################################################################################### variable vmouth equal -${crunchv}*sin(${crunchw}*time) fix move_fix_tongue2 tongue2 smd/setvel 0 v_vmouth 0 """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class discretizationsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: discretization session
constructor adding instance definitions stored in USER
Expand source code
class discretizationsection(script): """ LAMMPS script: discretization session """ name = "discretization" description = name+" section" position = 3 section = 4 userid = "example" version = 0.1 # inherit properties from geometrysection DEFINITIONS = geometrysection.DEFINITIONS + scriptdata( h= "2.5*${l0} # SPH kernel diameter", vol_one= "${l0}^2 # initial particle volume for 2d simulation", rho_saliva= 1000, rho_obj= 1300, skin= "${h} # Verlet list range", contact_scale= 1.5 ) TEMPLATE = """ # :DISCRETIZATION SECTION: # discretization #################################################################################################### # DISCRETIZATION PARAMETERS #################################################################################################### set group all diameter ${h} set group all smd/contact/radius ${l0} set group all volume ${vol_one} set group all smd/mass/density ${rho_saliva} set group solidfoods smd/mass/density ${rho_obj} set group tongue1 smd/mass/density ${rho_obj} set group tongue2 smd/mass/density ${rho_obj} neighbor ${skin} bin """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class dumpsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: dump session
constructor adding instance definitions stored in USER
Expand source code
class dumpsection(script): """ LAMMPS script: dump session """ name = "dump" description = name+" section" position = 7 section = 8 userid = "example" version = 0.1 DEFINITIONS = globalsection().DEFINITIONS TEMPLATE = """ # :DUMP SECTION: # Dump configuration #################################################################################################### # SPECIFY TRAJECTORY OUTPUT #################################################################################################### compute eint all smd/internal/energy compute contact_radius all smd/contact/radius compute S solidfoods smd/tlsph/stress compute nn saliva smd/ulsph/num/neighs compute epl solidfoods smd/plastic/strain compute vol all smd/vol compute rho all smd/rho dump dump_id all custom ${outstep} ${outputfile} id type x y & fx fy vx vy c_eint c_contact_radius mol & c_S[1] c_S[2] c_S[4] mass radius c_epl c_vol c_rho c_nn proc dump_modify dump_id first yes """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
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 geometrysection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: global session
constructor adding instance definitions stored in USER
Expand source code
class geometrysection(script): """ LAMMPS script: global session """ name = "geometry" description = name+" section" position = 2 section = 3 userid = "example" version = 0.1 DEFINITIONS = scriptdata( l0= 0.05, hgap= "0.25 # gap to prevent direct contact at t=0 (too much enery)", hsmallgap= "0.1 # gap to prevent direct contact at t=0 (too much enery)", hto1= "0.8 # height of to1 (the tongue to1, note 1 not l)", hto2= "0.5 # height of to2 (the tongue to2)", rsph= "0.3 # radius of spherical food particles", lpar= "0.6 # size of prismatic particles ", yfloor1= "${hgap} # bottom position of to1, position of the first floor", yroof1= "${yfloor1}+${hto1} # bottom position of to1, position of the first floor", yfloor2a= "${yroof1}+${hsmallgap} # position of the second floor / level a", yroof2a= "${yfloor2a}+${lpar} # position of the second floor / level a", yfloor2b= "${yroof2a}+${hsmallgap} # position of the second floor / level b", yroof2b= "${yfloor2b}+${lpar} # position of the second floor / level b", yfloor2c= "${yfloor2a}+${rsph} # position of the second floor / level c", yroof2c= "${yfloor2c}+${rsph} # position of the second floor / level c", yfloor2d= "${yroof2c}+${rsph}+${hsmallgap} # position of the second floor / level d", yroof2d= "${yfloor2d}+${rsph} # position of the second floor / level d", yfloor3= 5.0, yroof3= "${yfloor3}+${hto2} # bottom position of to1", yfloor3a= "${yfloor3}-0.6", yroof3a= "${yfloor3}", crunchl= "${yfloor3}-${yfloor2a}-0.8", crunchp= 3, crunchw= "2*pi/${crunchp}", crunchd= "2*(sin((${crunchp}*${crunchw})/4)^2)/${crunchw}", crunchv= "${crunchl}/${crunchd}" ) TEMPLATE = """ # :GEOMETRY SECTION: # Build geometry (very specific example) #################################################################################################### # CREATE INITIAL GEOMETRY # note there are 4 groups (create_box 5 box) # groupID 1 = saliva # groupID 2 = food # groupID 3 = mouth walls # groupID 4 = tongue alike (part1) # groupID 5 = also tongue but palate infact (part2) #################################################################################################### # create simulation box, a mouth, and a saliva column region box block 0 12 0 8 -0.01 0.01 units box create_box 5 box region saliva1 block 0.25 1.8 1.25 3.5 EDGE EDGE units box region saliva2 block 10 11.65 1.25 4 EDGE EDGE units box region mouth block 0.15 11.85 0.15 8 -0.01 0.01 units box side out # mouth lattice sq ${l0} create_atoms 1 region saliva1 create_atoms 1 region saliva2 group saliva type 1 create_atoms 3 region mouth group mouth type 3 print "Crunch distance:${crunchl}" # 3.65 print "Crunch distance:${crunchv}" # 0.1147 # bottom part of the tongue: to1 (real tongue) # warning: all displacements are relative to the bottom part region to1 block 1 11 ${yfloor1} ${yroof1} EDGE EDGE units box region to2part1 block 0.5 11.5 ${yfloor3} ${yroof3} EDGE EDGE units box region to2part2 block 5.5 6 ${yfloor3a} ${yroof3a} EDGE EDGE units box region to2 union 2 to2part1 to2part2 create_atoms 4 region to1 create_atoms 5 region to2 group tongue1 type 4 group tongue2 type 5 # create some solid objects to be pushed around region pr1 prism 2 2.6 ${yfloor2a} ${yroof2a} EDGE EDGE 0.3 0 0 units box region bl1 block 3 3.6 ${yfloor2a} ${yroof2a} EDGE EDGE units box region sp1 sphere 4.3 ${yfloor2c} 0 ${rsph} units box region sp2 sphere 5 ${yfloor2c} 0 ${rsph} units box region sp3 sphere 5.7 ${yfloor2c} 0 ${rsph} units box region sp4 sphere 6.4 ${yfloor2c} 0 ${rsph} units box region sp5 sphere 7.1 ${yfloor2c} 0 ${rsph} units box region sp6 sphere 6.05 ${yfloor2d} 0 ${rsph} units box region br2 block 3 3.6 ${yfloor2b} ${yroof2b} EDGE EDGE units box # fill the regions with atoms (note that atoms = smoothed hydrodynamics particles) create_atoms 2 region pr1 create_atoms 2 region bl1 create_atoms 2 region sp1 create_atoms 2 region sp2 create_atoms 2 region sp3 create_atoms 2 region sp4 create_atoms 2 region sp5 create_atoms 2 region sp6 create_atoms 2 region br2 # atoms of objects are grouped with two id # fix apply only to groups group solidfoods type 2 group tlsph type 2 # group heavy group allheavy type 1:4 """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class globalsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: global session
constructor adding instance definitions stored in USER
Expand source code
class globalsection(script): """ LAMMPS script: global session """ name = "global" description = name+" section" position = 0 section = 1 userid = "example" version = 0.1 DEFINITIONS = scriptdata( outputfile= "$dump.mouthfeel_v5_long # from the project of the same name", tsim= "500000 # may be too long", outstep= 10 ) MATERIALS = scriptdata( rho_saliva= "1000 # mass density saliva", rho_obj= "1300 # mass density solid objects", c0= "10.0 # speed of sound for saliva", E= "5*${c0}*${c0}*${rho_saliva} # Young's modulus for solid objects", Etongue1= "10*${E} # Young's modulus for tongue", Etongue2= "2*${Etongue1} # Young's modulus for tongue", nu= "0.3 # Poisson ratio for solid objects", sigma_yield= "0.1*${E} # plastic yield stress for solid objects", hardening_food= "0 # plastic hardening parameter for solid food", hardening_tongue= "1 # plastic hardening parameter for solid tongue", contact_stiffness= "2.5*${c0}^2*${rho_saliva} # contact force amplitude", contact_wall= "100*${contact_stiffness} # contact with wall (avoid interpenetration)", q1= "1.0 # artificial viscosity", q2= "0.0 # artificial viscosity", Hg= "10 # Hourglass control coefficient for solid objects", Cp= "1.0 # heat capacity -- not used here" ) DEFINITIONS += MATERIALS # append MATERIALS data TEMPLATE = """ # :GLOBAL SECTION: # avoid to set variables in LAMMPS script # use DEFINITIONS field to set properties. # If you need to define them, use the following syntax # #################################################################################################### # # GLOBAL # #################################################################################################### variable outputfile string "${outputfile}" variable tsim equal ${tsim} variable outstep equal ${outstep} # #################################################################################################### # # MATERIAL PARAMETERS # #################################################################################################### # variable rho_saliva equal 1000 # mass density saliva # variable rho_obj equal 1300 # mass density solid objects # variable c0 equal 10.0 # speed of sound for saliva # variable E equal 5*${c0}*${c0}*${rho_saliva} # Young's modulus for solid objects # variable Etongue1 equal 10*${E} # Young's modulus for tongue # variable Etongue2 equal 2*${Etongue1} # Young's modulus for tongue # variable nu equal 0.3 # Poisson ratio for solid objects # variable sigma_yield equal 0.1*${E} # plastic yield stress for solid objects # variable hardening_food equal 0 # plastic hardening parameter for solid food # variable hardening_tongue equal 1 # plastic hardening parameter for solid tongue # variable contact_stiffness equal 2.5*${c0}^2*${rho_saliva} # contact force amplitude # variable contact_wall equal 100*${contact_stiffness} # contact with wall (avoid interpenetration) # variable q1 equal 1.0 # artificial viscosity # variable q2 equal 0.0 # artificial viscosity # variable Hg equal 10 # Hourglass control coefficient for solid objects # variable Cp equal 1.0 # heat capacity -- not used here """
Ancestors
Class variables
var DEFINITIONS
var MATERIALS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class initializesection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: global session
constructor adding instance definitions stored in USER
Expand source code
class initializesection(script): """ LAMMPS script: global session """ name = "initialize" description = name+" section" position = 1 section = 2 userid = "example" version = 0.1 DEFINITIONS = scriptdata( units= "$ si", dimension= 2, boundary= "$ sm sm p", atom_style= "$smd", neigh_modify_every= 5, neigh_modify_delay= 0, comm_modify= "$ vel yes", newton= "$ off", atom_modify= "$ map array", comm_style= "$ tiled" ) TEMPLATE = """ # :INITIALIZE SECTION: # initialize styles, dimensions, boundaries and communivation #################################################################################################### # INITIALIZE LAMMPS #################################################################################################### units ${units} dimension ${dimension} boundary ${boundary} atom_style ${atom_style} neigh_modify every ${neigh_modify_every} delay ${neigh_modify_delay} check yes comm_modify ${comm_modify} newton ${newton} atom_modify ${atom_modify} comm_style ${comm_style} """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class integrationsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: time integration session
constructor adding instance definitions stored in USER
Expand source code
class integrationsection(script): """ LAMMPS script: time integration session """ name = "time integration" description = name+" section" position = 6 section = 7 userid = "example" version = 0.1 DEFINITIONS = scriptdata( dt = 0.1, adjust_redius = "$ 1.01 10 15" ) TEMPLATE = """ # :INTEGRATION SECTION: # Time integration conditions fix dtfix tlsph smd/adjust_dt ${dt} # dynamically adjust time increment every step fix integration_fix_water saliva smd/integrate_ulsph adjust_radius ${adjust_redius} fix integration_fix_solids solidfoods smd/integrate_tlsph fix integration_fix_tongue1 tongue1 smd/integrate_tlsph fix integration_fix_tongue2 tongue2 smd/integrate_tlsph """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class interactionsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: interaction session
constructor adding instance definitions stored in USER
Expand source code
class interactionsection(script): """ LAMMPS script: interaction session """ name = "interactions" description = name+" section" position = 5 section = 6 userid = "example" version = 0.1 DEFINITIONS = globalsection.DEFINITIONS + \ geometrysection.DEFINITIONS + \ discretizationsection.DEFINITIONS TEMPLATE = """ # :INTERACTIONS SECTION: # Please use forcefield() to make a robust code #################################################################################################### # INTERACTION PHYSICS / MATERIAL MODEL # 3 different pair styles are used: # - updated Lagrangian SPH for saliva # - total Lagrangian SPH for solid objects # - a repulsive Hertzian potential for contact forces between different physical bodies #################################################################################################### pair_style hybrid/overlay smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION & smd/tlsph smd/hertz ${contact_scale} pair_coeff 1 1 smd/ulsph *COMMON ${rho_saliva} ${c0} ${q1} ${Cp} 0 & *EOS_TAIT 7.0 & *END pair_coeff 2 2 smd/tlsph *COMMON ${rho_obj} ${E} ${nu} ${q1} ${q2} ${Hg} ${Cp} & *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_food} & *EOS_LINEAR & *END pair_coeff 4 4 smd/tlsph *COMMON ${rho_obj} ${Etongue1} ${nu} ${q1} ${q2} ${Hg} ${Cp} & *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_tongue} & *EOS_LINEAR & *END pair_coeff 5 5 smd/tlsph *COMMON ${rho_obj} ${Etongue2} ${nu} ${q1} ${q2} ${Hg} ${Cp} & *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening_tongue} & *EOS_LINEAR & *END pair_coeff 3 3 none # wall-wall pair_coeff 1 2 smd/hertz ${contact_stiffness} # saliva-food pair_coeff 1 3 smd/hertz ${contact_wall} # saliva-wall pair_coeff 2 3 smd/hertz ${contact_wall} # food-wall pair_coeff 2 2 smd/hertz ${contact_stiffness} # food-food # add 4 (to1) pair_coeff 1 4 smd/hertz ${contact_stiffness} # saliva-tongue1 pair_coeff 2 4 smd/hertz ${contact_stiffness} # food-tongue1 pair_coeff 3 4 smd/hertz ${contact_wall} # wall-tongue1 pair_coeff 4 4 smd/hertz ${contact_stiffness} # tongue1-tongue1 # add 5 (to2) pair_coeff 1 5 smd/hertz ${contact_stiffness} # saliva-tongue2 pair_coeff 2 5 smd/hertz ${contact_stiffness} # food-tongue2 pair_coeff 3 5 smd/hertz ${contact_wall} # wall-tongue2 pair_coeff 4 5 smd/hertz ${contact_stiffness} # tongue1-tongue2 pair_coeff 5 5 smd/hertz ${contact_stiffness} # tongue2-tongue2 """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
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
- 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
- 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 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 runsection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: run session
constructor adding instance definitions stored in USER
Expand source code
class runsection(script): """ LAMMPS script: run session """ name = "run" description = name+" section" position = 9 section = 10 userid = "example" version = 0.1 DEFINITIONS = globalsection.DEFINITIONS + scriptdata( balance = "$ 500 0.9 rcb" ) TEMPLATE = """ # :RUN SECTION: # run configuration #################################################################################################### # RUN SIMULATION #################################################################################################### fix balance_fix all balance ${balance} # load balancing for MPI run ${tsim} """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class saltTLSPH (beadtype=1, userid=None, USER=forcefield (FF object) with 0 parameters)
-
SALTLSPH (smd:tlsph): ongoing "salting" beadtype for rheology control saltTLSPH() saltTLSPH(beadtype=index, userid="salt", USER=…)
override any property with USER.property = value
saltTLSPH forcefield: saltTLSPH(beadtype=index, userid="salt")
Expand source code
class saltTLSPH(tlsph): """ SALTLSPH (smd:tlsph): ongoing "salting" beadtype for rheology control saltTLSPH() saltTLSPH(beadtype=index, userid="salt", USER=...) override any property with USER.property = value """ name = tlsph.name + struct(material="solidfood") description = tlsph.description + struct(material="food beads - solid behavior") userid = '"salt"' version = 0.1 # constructor (do not forgert to include the constuctor) def __init__(self, beadtype=1, userid=None, USER=parameterforcefield()): """ saltTLSPH forcefield: saltTLSPH(beadtype=index, userid="salt") """ # super().__init__() if userid!=None: self.userid = userid self.beadtype = beadtype self.parameters = parameterforcefield( # food-food interactions rho = 1000, c0 = 10.0, E = '5*${c0}^2*${rho}', nu = 0.3, q1 = 1.0, q2 = 0.0, Hg = 10, Cp = 1.0, sigma_yield = '0.1*${E}', hardening = 0, # hertz contacts contact_scale = 1.5, contact_stiffness = '2.5*${c0}^2*${rho}' ) + USER # update with user properties if any
Ancestors
- pizza.forcefield.tlsph
- pizza.forcefield.smd
- pizza.forcefield.forcefield
Class variables
var description
var name
var userid
var version
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
- boundarysection
- discretizationsection
- dumpsection
- geometrysection
- globalsection
- initializesection
- integrationsection
- interactionsection
- runsection
- statussection
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="script.script" href="#script.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 scriptobjectgroup (*SOgroup)
-
scriptobjectgroup: A Class for Managing Groups of Script Objects in LAMMPS
The
scriptobjectgroup
class is designed to represent a group ofscriptobject
instances, such as beads or atoms in a simulation. This class allows users to group objects together based on their properties (e.g., beadtype, name), and provides tools to generate scripts that define interactions, groups, and forcefields for these objects in LAMMPS.Key Features:
- Group Management: Objects can be combined into a group, where each
beadtype
occurs once. The class ensures that objects are uniquely identified by theirbeadtype
andname
. - Dynamic Properties: The group’s properties (e.g.,
beadtype
,name
,groupname
) are dynamically calculated, ensuring that the group reflects the current state of the objects. - Script Generation: Provides methods to generate scripts based on the group's objects, including interaction forcefields and group definitions.
- Interaction Accumulation: Automatically accumulates and updates all forcefield interactions for the objects in the group.
Practical Use Cases:
- LAMMPS Group Definitions: Define groups of objects for use in LAMMPS simulations,
based on properties like
beadtype
andgroupname
. - Forcefield Management: Automatically manage and update interaction forcefields for objects in the group.
- Script Generation: Generate LAMMPS-compatible scripts that include group definitions, input file handling, and interaction forcefields.
Methods:
init(self, *SOgroup): Initializes a new
scriptobjectgroup
with one or morescriptobject
instances.str(self): Returns a string representation of the
scriptobjectgroup
, showing the number of objects in the group and theirbeadtypes
.add(self, SOgroup): Combines two
scriptobjectgroup
instances or ascriptobject
with an existing group, ensuring thatbeadtype
values are unique.or(self, pipe): Overloads the pipe (
|
) operator to integrate the group into a pipeline.select(self, beadtype=None): Selects and returns a subset of the group based on the specified
beadtype
.script(self, printflag=False, verbosity=2, verbose=None): Generates a script based on the current collection of objects, including input file handling, group definitions, and interaction forcefields.
interactions(self, printflag=False, verbosity=2, verbose=None): Updates and accumulates all forcefields for the objects in the group.
group_generator(self, name=None): Generates and returns a
group
object, based on the existing group structure.Properties:
- list : Converts the group into a sorted list of objects.
- zip : Returns a sorted list of tuples containing
beadtype
,name
,group
, andfilename
for each object. - n : Returns the number of objects in the group.
- beadtype : Returns a list of the
beadtypes
for all objects in the group. - name : Returns a list of the
names
for all objects in the group. - groupname : Returns a list of all group names (synonyms).
- filename : Returns a dictionary mapping filenames to the objects that use them.
- str : Returns a string representation of the group's
beadtypes
. - min : Returns the minimum
beadtype
in the group. - max : Returns the maximum
beadtype
in the group. - minmax : Returns a tuple of the minimum and maximum
beadtypes
in the group. - forcefield : Returns the interaction forcefields for the group.
Original Content:
The
scriptobjectgroup
class enables the collection and management of multiplescriptobject
instances, providing the following functionalities: - Group Creation: Groups are automatically formed by combining individual objects using the+
operator. Eachbeadtype
occurs only once in the group, and errors are raised if an object with the samename
orbeadtype
already exists. - Dynamic Properties: Properties such asbeadtype
,name
,groupname
, andfilename
are dynamically calculated, reflecting the current state of the objects. - Forcefield Handling: Forcefields are automatically managed for the objects in the group, including diagonal and off-diagonal terms for pair interactions. - Script Generation: Scripts are generated to define the interactions, groups, and input file handling for LAMMPS.Example Usage:
from pizza.scriptobject import scriptobject, scriptobjectgroup, rigidwall, solidfood, water # Define some script objects b1 = scriptobject(name="bead 1", group=["A", "B", "C"], filename='myfile1', forcefield=rigidwall()) b2 = scriptobject(name="bead 2", group=["B", "C"], filename='myfile1', forcefield=rigidwall()) b3 = scriptobject(name="bead 3", group=["B", "D", "E"], forcefield=solidfood()) b4 = scriptobject(name="bead 4", group="D", beadtype=1, filename="myfile2", forcefield=water()) # Combine objects into a group collection = b1 + b2 + b3 + b4 # Select a subset of objects and generate a script grp_typ1 = collection.select(1) grpB = collection.group.B script12 = collection.select([1, 2]).script()
Output:
script object group with 4 objects (1 2 3 4) script
OVERVIEW:
class of script object group script object groups are built from script objects OBJ1, OBJ2,.. GRP = scriptobjectgroup(OBJ1,OBJ2,...) GRP = OBJ1+OBJ2+... note: each beadtype occurs once in the group (if not an error message is generated) List of methods struct() converts data as structure select([1,2,4]) selects objects with matching beadtypes List of properties (dynamically calculated) converted data: list, str, zip, beadtype, name, groupname, group, filename numeric: len, min, max, minmax forcefield related: interactions, forcefield script: generate the script (load,group,forcefield) Full syntax (toy example)
b1 = scriptobject(name="bead 1",group = ["A", "B", "C"],filename='myfile1',forcefield=rigidwall()) b2 = scriptobject(name="bead 2", group = ["B", "C"],filename = 'myfile1',forcefield=rigidwall()) b3 = scriptobject(name="bead 3", group = ["B", "D", "E"],forcefield=solidfood()) b4 = scriptobject(name="bead 4", group = "D",beadtype = 1,filename="myfile2",forcefield=water())
note: beadtype are incremented during the collection (effect of order) # generate a collection, select a typ 1 and a subgroup, generate the script for 1,2 collection = b1+b2+b3+b4 grp_typ1 = collection.select(1) grpB = collection.group.B script12 = collection.select([1,2]).script note: collection.group.B returns a strcture with 6 fields -----------:---------------------------------------- groupid: 2 <-- automatic group numbering groupidname: B <-- group name groupname: ['A', 'B', 'C', 'D', 'E'] <--- snonyms beadtype: [1, 2, 3] <-- beads belonging to B name: ['bead 1', 'bead 2', 'bead 3'] <-- their names str: group B 1 2 3 <-- LAMMPS syntax -----------:----------------------------------------
SOG constructor
Expand source code
class scriptobjectgroup(struct): """ scriptobjectgroup: A Class for Managing Groups of Script Objects in LAMMPS The `scriptobjectgroup` class is designed to represent a group of `scriptobject` instances, such as beads or atoms in a simulation. This class allows users to group objects together based on their properties (e.g., beadtype, name), and provides tools to generate scripts that define interactions, groups, and forcefields for these objects in LAMMPS. Key Features: ------------- - **Group Management**: Objects can be combined into a group, where each `beadtype` occurs once. The class ensures that objects are uniquely identified by their `beadtype` and `name`. - **Dynamic Properties**: The group’s properties (e.g., `beadtype`, `name`, `groupname`) are dynamically calculated, ensuring that the group reflects the current state of the objects. - **Script Generation**: Provides methods to generate scripts based on the group's objects, including interaction forcefields and group definitions. - **Interaction Accumulation**: Automatically accumulates and updates all forcefield interactions for the objects in the group. Practical Use Cases: -------------------- - **LAMMPS Group Definitions**: Define groups of objects for use in LAMMPS simulations, based on properties like `beadtype` and `groupname`. - **Forcefield Management**: Automatically manage and update interaction forcefields for objects in the group. - **Script Generation**: Generate LAMMPS-compatible scripts that include group definitions, input file handling, and interaction forcefields. Methods: -------- __init__(self, *SOgroup): Initializes a new `scriptobjectgroup` with one or more `scriptobject` instances. __str__(self): Returns a string representation of the `scriptobjectgroup`, showing the number of objects in the group and their `beadtypes`. __add__(self, SOgroup): Combines two `scriptobjectgroup` instances or a `scriptobject` with an existing group, ensuring that `beadtype` values are unique. __or__(self, pipe): Overloads the pipe (`|`) operator to integrate the group into a pipeline. select(self, beadtype=None): Selects and returns a subset of the group based on the specified `beadtype`. script(self, printflag=False, verbosity=2, verbose=None): Generates a script based on the current collection of objects, including input file handling, group definitions, and interaction forcefields. interactions(self, printflag=False, verbosity=2, verbose=None): Updates and accumulates all forcefields for the objects in the group. group_generator(self, name=None): Generates and returns a `group` object, based on the existing group structure. Properties: ----------- - list : Converts the group into a sorted list of objects. - zip : Returns a sorted list of tuples containing `beadtype`, `name`, `group`, and `filename` for each object. - n : Returns the number of objects in the group. - beadtype : Returns a list of the `beadtypes` for all objects in the group. - name : Returns a list of the `names` for all objects in the group. - groupname : Returns a list of all group names (synonyms). - filename : Returns a dictionary mapping filenames to the objects that use them. - str : Returns a string representation of the group's `beadtypes`. - min : Returns the minimum `beadtype` in the group. - max : Returns the maximum `beadtype` in the group. - minmax : Returns a tuple of the minimum and maximum `beadtypes` in the group. - forcefield : Returns the interaction forcefields for the group. Original Content: ----------------- The `scriptobjectgroup` class enables the collection and management of multiple `scriptobject` instances, providing the following functionalities: - **Group Creation**: Groups are automatically formed by combining individual objects using the `+` operator. Each `beadtype` occurs only once in the group, and errors are raised if an object with the same `name` or `beadtype` already exists. - **Dynamic Properties**: Properties such as `beadtype`, `name`, `groupname`, and `filename` are dynamically calculated, reflecting the current state of the objects. - **Forcefield Handling**: Forcefields are automatically managed for the objects in the group, including diagonal and off-diagonal terms for pair interactions. - **Script Generation**: Scripts are generated to define the interactions, groups, and input file handling for LAMMPS. Example Usage: -------------- ``` from pizza.scriptobject import scriptobject, scriptobjectgroup, rigidwall, solidfood, water # Define some script objects b1 = scriptobject(name="bead 1", group=["A", "B", "C"], filename='myfile1', forcefield=rigidwall()) b2 = scriptobject(name="bead 2", group=["B", "C"], filename='myfile1', forcefield=rigidwall()) b3 = scriptobject(name="bead 3", group=["B", "D", "E"], forcefield=solidfood()) b4 = scriptobject(name="bead 4", group="D", beadtype=1, filename="myfile2", forcefield=water()) # Combine objects into a group collection = b1 + b2 + b3 + b4 # Select a subset of objects and generate a script grp_typ1 = collection.select(1) grpB = collection.group.B script12 = collection.select([1, 2]).script() ``` Output: ``` script object group with 4 objects (1 2 3 4) script ``` OVERVIEW: -------------- class of script object group script object groups are built from script objects OBJ1, OBJ2,.. GRP = scriptobjectgroup(OBJ1,OBJ2,...) GRP = OBJ1+OBJ2+... note: each beadtype occurs once in the group (if not an error message is generated) List of methods struct() converts data as structure select([1,2,4]) selects objects with matching beadtypes List of properties (dynamically calculated) converted data: list, str, zip, beadtype, name, groupname, group, filename numeric: len, min, max, minmax forcefield related: interactions, forcefield script: generate the script (load,group,forcefield) Full syntax (toy example) b1 = scriptobject(name="bead 1",group = ["A", "B", "C"],filename='myfile1',forcefield=rigidwall()) b2 = scriptobject(name="bead 2", group = ["B", "C"],filename = 'myfile1',forcefield=rigidwall()) b3 = scriptobject(name="bead 3", group = ["B", "D", "E"],forcefield=solidfood()) b4 = scriptobject(name="bead 4", group = "D",beadtype = 1,filename="myfile2",forcefield=water()) note: beadtype are incremented during the collection (effect of order) # generate a collection, select a typ 1 and a subgroup, generate the script for 1,2 collection = b1+b2+b3+b4 grp_typ1 = collection.select(1) grpB = collection.group.B script12 = collection.select([1,2]).script note: collection.group.B returns a strcture with 6 fields -----------:---------------------------------------- groupid: 2 <-- automatic group numbering groupidname: B <-- group name groupname: ['A', 'B', 'C', 'D', 'E'] <--- snonyms beadtype: [1, 2, 3] <-- beads belonging to B name: ['bead 1', 'bead 2', 'bead 3'] <-- their names str: group B 1 2 3 <-- LAMMPS syntax -----------:---------------------------------------- """ _type = "SOG" _fulltype = "script object group" _ftype = "object" _propertyasattribute = True def __init__(self,*SOgroup): """ SOG constructor """ super(scriptobjectgroup,self).__init__() beadtypemax = 0 names = [] for k in range(len(SOgroup)): if isinstance(SOgroup[k],scriptobject): if SOgroup[k].beadtype<beadtypemax or SOgroup[k].beadtype==None: beadtypemax +=1 SOgroup[k].beadtype = beadtypemax if SOgroup[k].name not in names: self.setattr(SOgroup[k].name,SOgroup[k]) beadtypemax = SOgroup[k].beadtype else: raise ValueError('the script object "%s" already exists' % SOgroup[k].name) names.append(SOgroup[k].name) else: raise ValueError("the argument #%d is not a script object") def __str__(self): """ string representation """ return f"{self._fulltype} with {len(self)} {self._ftype}s ({span(self.beadtype)})" def __add__(self, SOgroup): """ overload + """ beadlist = self.beadtype dup = duplicate(self) if isinstance(SOgroup,scriptobject): if SOgroup.name not in self.keys(): if SOgroup.beadtype in beadlist and \ (SOgroup.beadtype==None or SOgroup.beadtype==self.min): SOgroup.beadtype = self.max+1 if SOgroup.beadtype not in beadlist: dup.setattr(SOgroup.name, SOgroup) beadlist.append(SOgroup.beadtype) return dup else: raise ValueError('%s (beadtype=%d) is already in use, same beadtype' \ % (SOgroup.name,SOgroup.beadtype)) else: raise ValueError('the object "%s" is already in the list' % SOgroup.name) elif isinstance(SOgroup,scriptobjectgroup): for k in SOgroup.keys(): if k not in dup.keys(): if SOgroup.getattr(k).beadtype not in beadlist: dup.setattr(k,SOgroup.getattr(k)) beadlist.append(SOgroup.getattr(k).beadtype) else: raise ValueError('%s (beadtype=%d) is already in use, same beadtype' \ % (k,SOgroup.getattr(k).beadtype)) else: raise ValueError('the object "%s" is already in the list' % k) return dup else: raise ValueError("the argument #%d is not a script object or a script object group") 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") @property def list(self): """ convert into a list """ return sorted(self) @property def zip(self): """ zip beadtypes and names """ return sorted( \ [(self.getattr(k).beadtype,self.getattr(k).name,self.getattr(k).group,self.getattr(k).filename) \ for k in self.keys()]) @property def n(self): """ returns the number of bead types """ return len(self) @property def beadtype(self): """ returns the beads in the group """ return [x for x,_,_,_ in self.zip] @property def name(self): """ "return the list of names """ return [x for _,x,_,_ in self.zip] @property def groupname(self): """ "return the list of groupnames """ grp = [] for _,_,glist,_ in self.zip: for g in glist: if g not in grp: grp.append(g) return grp @property def filename(self): """ "return the list of names as a dictionary """ files = {} for _,n,_,fn in self.zip: if fn != "": if fn not in files: files[fn] = [n] else: files[fn].append(n) return files @property def str(self): return span(self.beadtype) def struct(self,groupid=1,groupidname="undef"): """ create a group with name """ return struct( groupid = groupid, groupidname = groupidname, groupname = self.groupname, # meaning is synonyms beadtype = self.beadtype, name = self.name, str = "group %s %s" % (groupidname, span(self.beadtype)) ) @property def minmax(self): """ returns the min,max of beadtype """ return self.min,self.max @property def min(self): """ returns the min of beadtype """ return min(self.beadtype) @property def max(self): """ returns the max of beadtype """ return max(self.beadtype) def select(self,beadtype=None): """ select bead from a keep beadlist """ if beadtype==None: beadtype = list(range(self.min,self.max+1)) if not isinstance(beadtype,(list,tuple)): beadtype = [beadtype] dup = scriptobjectgroup() for b,n,_,_ in self.zip: if b in beadtype: dup = dup + self.getattr(n) dup.getattr(n).USER = self.getattr(n).USER dup.getattr(n).forcefield = self.getattr(n).forcefield return dup @property def group(self): """ build groups from group (groupname contains synonyms) """ groupdef = struct() gid = 0 bng = self.zip for g in self.groupname: gid +=1 b =[x for x,_,gx,_ in bng if g in gx] groupdef.setattr(g,self.select(b).struct(groupid = gid, groupidname = g)) return groupdef @CallableScript def interactions(self, printflag=False, verbosity=2, verbose=None): """ update and accumulate all forcefields """ verbosity = 0 if verbose is False else verbosity FF = [] for b in self.beadtype: selection = deepduplicate(self.select(b)[0]) selection.forcefield.beadtype = selection.beadtype selection.forcefield.userid = selection.name FF.append(selection.forcefield) # initialize interactions with pair_style TEMPLATE = "\n# ===== [ BEGIN FORCEFIELD SECTION ] "+"="*80 if verbosity>0 else "" TEMPLATE = FF[0].pair_style(verbose=verbosity>0) # pair diagonal terms for i in range(len(FF)): TEMPLATE += FF[i].pair_diagcoeff(verbose=verbosity>0) # pair off-diagonal terms for j in range(1,len(FF)): for i in range(0,j): TEMPLATE += FF[i].pair_offdiagcoeff(o=FF[j],verbose=verbosity>0) # end TEMPLATE += "\n# ===== [ END FORCEFIELD SECTION ] "+"="*82+"\n" if verbosity>0 else "" return FF,TEMPLATE @property def forcefield(self): """ interaction forcefields """ FF,_ = self.interactions return FF @CallableScript def script(self, printflag=False, verbosity=None, verbose=None): """ Generate a script based on the current collection of script objects Parameters: ----------- printflag : bool, optional, default=False If True, prints the generated script. 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 Returns: -------- script The generated script describing the interactions between script objects. """ 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 TEMPFILES = "" isfirst = True files_added = False if self.filename: for fn, cfn in self.filename.items(): if fn and cfn: if not files_added: files_added = True TEMPFILES += "\n# ===== [ BEGIN INPUT FILES SECTION ] " + "=" * 79 + "\n" if verbosity>0 else "" TEMPFILES += span(cfn, sep=", ", left="\n# load files for objects: ", right="\n") if verbosity>1 else "" if isfirst: isfirst = False TEMPFILES += f"\tread_data {fn}\n" # First file, no append else: TEMPFILES += f"\tread_data {fn} add append\n" # Subsequent files, append # define groups TEMPGRP = "\n# ===== [ BEGIN GROUP SECTION ] "+"="*85 + "\n" if verbosity>0 else "" for g in self.group: TEMPGRP += f'\n\t#\tDefinition of group {g.groupid}:{g.groupidname}\n' if verbosity>1 else "" TEMPGRP += f'\t#\t={span(g.name,sep=", ")}\n' if verbosity>1 else "" TEMPGRP += f'\t#\tSimilar groups: {span(g.groupname,sep=", ")}\n' if verbosity>1 else "" TEMPGRP += f'\tgroup \t {g.groupidname} \ttype \t {span(g.beadtype)}\n' TEMPGRP += "\n# ===== [ END GROUP SECTION ] "+"="*87+"\n\n" if verbosity>0 else "" # define interactions _,TEMPFF = self.interactions(printflag=printflag, verbosity=verbosity) # chain strings into a script tscript = script(printflag=False,verbose=verbosity>1) tscript.name = "scriptobject script" # name tscript.description = str(self) # description tscript.userid = "scriptobject" # user name tscript.TEMPLATE = TEMPFILES+TEMPGRP+TEMPFF if verbosity==0: tscript.TEMPLATE = remove_comments(tscript.TEMPLATE) if printflag: repr(tscript) return tscript def group_generator(self, name=None): """ Generate and return a group object. This method creates a new `group` object, optionally with a specified name. If no name is provided, it generates a default name based on the current instance's `name` attribute, formatted with the `span` function. The method then iterates through the existing groups in `self.group`, adding each group to the new `group` object based on its `groupidname` and `beadtype`. Parameters: ----------- name : str, optional The name for the generated group object. If not provided, a default name is generated based on the current instance's `name`. Returns: -------- group A newly created `group` object with criteria set based on the existing groups. """ from pizza.group import group # Use the provided name or generate a default name using the span function G = group(name=name if name is not None else span(self.name, ",", "[", "]")) # Add criteria for each group in self.group for g in self.group: G.add_group_criteria(g.groupidname, type=g.beadtype) return G def mass(self, name=None, default_mass="${mass}", printflag=False, verbosity=2, verbose=True): """ Generates LAMMPS mass commands for each unique beadtype in the collection. The method iterates through all `scriptobjectgroup` instances in the collection, collects unique beadtypes, and ensures that each beadtype has a consistent mass. If a beadtype has `mass=None`, it assigns a default mass as specified by `default_mass`. ### Parameters: name (str, optional): The name to assign to the resulting `script` object. Defaults to a generated name. default_mass (str, int, or float, optional): The default mass value to assign when a beadtype's mass is `None`. Can be a string, integer, or floating-point value. Defaults to `"${mass}"`. printflag (bool, optional): If `True`, prints the representation of the resulting `script` object. Defaults to `False`. verbosity (int, optional): The verbosity level for logging or debugging. Higher values indicate more detailed output. Defaults to `2`. verbose (bool, optional): If `True`, includes a comment header in the output. Overrides `verbosity` when `False`. Defaults to `True`. ### Returns: script: A `script` object containing the mass commands for each beadtype, formatted as follows: ``` mass 1 1.0 mass 2 ${mass} mass 3 2.5 ``` The `TEMPLATE` attribute of the `script` object holds the formatted mass commands as a single string. ### Raises: ValueError: If a beadtype has inconsistent mass values across different `scriptobjectgroup` instances. ### Example: ```python # Create scriptobjectgroup instances obj1 = scriptobjectgroup(beadtype=1, group=["all", "A"], mass=1.0) obj2 = scriptobjectgroup(beadtype=2, group=["all", "B", "C"]) obj3 = scriptobjectgroup(beadtype=3, group="C", mass=2.5) # Initialize a script group with the scriptobjectgroup instances G = scriptobjectgroup([obj1, obj2, obj3]) # Generate mass commands M = G.mass() print(M.do()) ``` **Output:** ``` # <script:group:mass> definitions for 3 beads mass 1 1.0 mass 2 ${mass} mass 3 2.5 ``` """ verbosity = 0 if verbose is False else verbosity beadtype_mass = {} for iobj in range(0,len(self)): obj = self[iobj] bt = obj.beadtype mass = obj.mass if obj.mass is not None else default_mass if bt in beadtype_mass: if beadtype_mass[bt] != mass: raise ValueError( f"Inconsistent masses for beadtype {bt}: {beadtype_mass[bt]} vs {mass}" ) else: beadtype_mass[bt] = mass # Sort beadtypes for consistent ordering sorted_beadtypes = sorted(beadtype_mass.keys()) # Generate mass commands lines = [f"mass {bt} {beadtype_mass[bt]}" for bt in sorted_beadtypes] # return a script object nameid = f"<script:group:{self.name}:mass>" description = f"{nameid} definitions for {len(self)} beads" if verbose: lines.insert(0, "# "+description) mscript = script(printflag=False,verbose=verbosity>1) mscript.name = nameid if name is None else name mscript.description = description mscript.userid = "scriptobject" # user name mscript.TEMPLATE = "\n".join(lines) mscript.DEFINITIONS.mass = default_mass if printflag: repr(mscript) return mscript
Ancestors
- pizza.private.mstruct.struct
Instance variables
var beadtype
-
returns the beads in the group
Expand source code
@property def beadtype(self): """ returns the beads in the group """ return [x for x,_,_,_ in self.zip]
var filename
-
"return the list of names as a dictionary
Expand source code
@property def filename(self): """ "return the list of names as a dictionary """ files = {} for _,n,_,fn in self.zip: if fn != "": if fn not in files: files[fn] = [n] else: files[fn].append(n) return files
var forcefield
-
interaction forcefields
Expand source code
@property def forcefield(self): """ interaction forcefields """ FF,_ = self.interactions return FF
var group
-
build groups from group (groupname contains synonyms)
Expand source code
@property def group(self): """ build groups from group (groupname contains synonyms) """ groupdef = struct() gid = 0 bng = self.zip for g in self.groupname: gid +=1 b =[x for x,_,gx,_ in bng if g in gx] groupdef.setattr(g,self.select(b).struct(groupid = gid, groupidname = g)) return groupdef
var groupname
-
"return the list of groupnames
Expand source code
@property def groupname(self): """ "return the list of groupnames """ grp = [] for _,_,glist,_ in self.zip: for g in glist: if g not in grp: grp.append(g) return grp
var list
-
convert into a list
Expand source code
@property def list(self): """ convert into a list """ return sorted(self)
var max
-
returns the max of beadtype
Expand source code
@property def max(self): """ returns the max of beadtype """ return max(self.beadtype)
var min
-
returns the min of beadtype
Expand source code
@property def min(self): """ returns the min of beadtype """ return min(self.beadtype)
var minmax
-
returns the min,max of beadtype
Expand source code
@property def minmax(self): """ returns the min,max of beadtype """ return self.min,self.max
var n
-
returns the number of bead types
Expand source code
@property def n(self): """ returns the number of bead types """ return len(self)
var name
-
"return the list of names
Expand source code
@property def name(self): """ "return the list of names """ return [x for _,x,_,_ in self.zip]
var str
-
Expand source code
@property def str(self): return span(self.beadtype)
var zip
-
zip beadtypes and names
Expand source code
@property def zip(self): """ zip beadtypes and names """ return sorted( \ [(self.getattr(k).beadtype,self.getattr(k).name,self.getattr(k).group,self.getattr(k).filename) \ for k in self.keys()])
Methods
def group_generator(self, name=None)
-
Generate and return a group object.
This method creates a new
group
object, optionally with a specified name. If no name is provided, it generates a default name based on the current instance'sname
attribute, formatted with thespan()
function. The method then iterates through the existing groups inself.group
, adding each group to the newgroup
object based on itsgroupidname
andbeadtype
.Parameters:
name : str, optional The name for the generated group object. If not provided, a default name is generated based on the current instance's
name
.Returns:
group A newly created
group
object with criteria set based on the existing groups.Expand source code
def group_generator(self, name=None): """ Generate and return a group object. This method creates a new `group` object, optionally with a specified name. If no name is provided, it generates a default name based on the current instance's `name` attribute, formatted with the `span` function. The method then iterates through the existing groups in `self.group`, adding each group to the new `group` object based on its `groupidname` and `beadtype`. Parameters: ----------- name : str, optional The name for the generated group object. If not provided, a default name is generated based on the current instance's `name`. Returns: -------- group A newly created `group` object with criteria set based on the existing groups. """ from pizza.group import group # Use the provided name or generate a default name using the span function G = group(name=name if name is not None else span(self.name, ",", "[", "]")) # Add criteria for each group in self.group for g in self.group: G.add_group_criteria(g.groupidname, type=g.beadtype) return G
def interactions(printflag=False, verbosity=2, verbose=None)
-
Expand source code
return lambda printflag=False, verbosity=2, verbose=None: self.func(instance, printflag=printflag, verbosity=verbosity, verbose=verbose)
def mass(self, name=None, default_mass='${mass}', printflag=False, verbosity=2, verbose=True)
-
Generates LAMMPS mass commands for each unique beadtype in the collection.
The method iterates through all
scriptobjectgroup
instances in the collection, collects unique beadtypes, and ensures that each beadtype has a consistent mass. If a beadtype hasmass=None
, it assigns a default mass as specified bydefault_mass
.Parameters:
name (str, optional): The name to assign to the resulting <code><a title="script.script" href="#script.script">script</a></code> object. Defaults to a generated name. default_mass (str, int, or float, optional): The default mass value to assign when a beadtype's mass is <code>None</code>. Can be a string, integer, or floating-point value. Defaults to `"${mass}"`. printflag (bool, optional): If <code>True</code>, prints the representation of the resulting <code><a title="script.script" href="#script.script">script</a></code> object. Defaults to <code>False</code>. verbosity (int, optional): The verbosity level for logging or debugging. Higher values indicate more detailed output. Defaults to <code>2</code>. verbose (bool, optional): If <code>True</code>, includes a comment header in the output. Overrides <code>verbosity</code> when <code>False</code>. Defaults to <code>True</code>.
Returns:
script: A <code><a title="script.script" href="#script.script">script</a></code> object containing the mass commands for each beadtype, formatted as follows: ``` mass 1 1.0 mass 2 ${mass} mass 3 2.5 ``` The <code>TEMPLATE</code> attribute of the <code><a title="script.script" href="#script.script">script</a></code> object holds the formatted mass commands as a single string.
Raises:
ValueError: If a beadtype has inconsistent mass values across different <code><a title="script.scriptobjectgroup" href="#script.scriptobjectgroup">scriptobjectgroup</a></code> instances.
Example:
```python # Create scriptobjectgroup instances obj1 = scriptobjectgroup(beadtype=1, group=["all", "A"], mass=1.0) obj2 = scriptobjectgroup(beadtype=2, group=["all", "B", "C"]) obj3 = scriptobjectgroup(beadtype=3, group="C", mass=2.5) # Initialize a script group with the scriptobjectgroup instances G = scriptobjectgroup([obj1, obj2, obj3]) # Generate mass commands M = G.mass() print(M.do()) ``` **Output:** ``` # <script:group:mass> definitions for 3 beads mass 1 1.0 mass 2 ${mass} mass 3 2.5 ```
Expand source code
def mass(self, name=None, default_mass="${mass}", printflag=False, verbosity=2, verbose=True): """ Generates LAMMPS mass commands for each unique beadtype in the collection. The method iterates through all `scriptobjectgroup` instances in the collection, collects unique beadtypes, and ensures that each beadtype has a consistent mass. If a beadtype has `mass=None`, it assigns a default mass as specified by `default_mass`. ### Parameters: name (str, optional): The name to assign to the resulting `script` object. Defaults to a generated name. default_mass (str, int, or float, optional): The default mass value to assign when a beadtype's mass is `None`. Can be a string, integer, or floating-point value. Defaults to `"${mass}"`. printflag (bool, optional): If `True`, prints the representation of the resulting `script` object. Defaults to `False`. verbosity (int, optional): The verbosity level for logging or debugging. Higher values indicate more detailed output. Defaults to `2`. verbose (bool, optional): If `True`, includes a comment header in the output. Overrides `verbosity` when `False`. Defaults to `True`. ### Returns: script: A `script` object containing the mass commands for each beadtype, formatted as follows: ``` mass 1 1.0 mass 2 ${mass} mass 3 2.5 ``` The `TEMPLATE` attribute of the `script` object holds the formatted mass commands as a single string. ### Raises: ValueError: If a beadtype has inconsistent mass values across different `scriptobjectgroup` instances. ### Example: ```python # Create scriptobjectgroup instances obj1 = scriptobjectgroup(beadtype=1, group=["all", "A"], mass=1.0) obj2 = scriptobjectgroup(beadtype=2, group=["all", "B", "C"]) obj3 = scriptobjectgroup(beadtype=3, group="C", mass=2.5) # Initialize a script group with the scriptobjectgroup instances G = scriptobjectgroup([obj1, obj2, obj3]) # Generate mass commands M = G.mass() print(M.do()) ``` **Output:** ``` # <script:group:mass> definitions for 3 beads mass 1 1.0 mass 2 ${mass} mass 3 2.5 ``` """ verbosity = 0 if verbose is False else verbosity beadtype_mass = {} for iobj in range(0,len(self)): obj = self[iobj] bt = obj.beadtype mass = obj.mass if obj.mass is not None else default_mass if bt in beadtype_mass: if beadtype_mass[bt] != mass: raise ValueError( f"Inconsistent masses for beadtype {bt}: {beadtype_mass[bt]} vs {mass}" ) else: beadtype_mass[bt] = mass # Sort beadtypes for consistent ordering sorted_beadtypes = sorted(beadtype_mass.keys()) # Generate mass commands lines = [f"mass {bt} {beadtype_mass[bt]}" for bt in sorted_beadtypes] # return a script object nameid = f"<script:group:{self.name}:mass>" description = f"{nameid} definitions for {len(self)} beads" if verbose: lines.insert(0, "# "+description) mscript = script(printflag=False,verbose=verbosity>1) mscript.name = nameid if name is None else name mscript.description = description mscript.userid = "scriptobject" # user name mscript.TEMPLATE = "\n".join(lines) mscript.DEFINITIONS.mass = default_mass if printflag: repr(mscript) return mscript
def script(printflag=False, verbosity=2, verbose=None)
-
Expand source code
return lambda printflag=False, verbosity=2, verbose=None: self.func(instance, printflag=printflag, verbosity=verbosity, verbose=verbose)
def select(self, beadtype=None)
-
select bead from a keep beadlist
Expand source code
def select(self,beadtype=None): """ select bead from a keep beadlist """ if beadtype==None: beadtype = list(range(self.min,self.max+1)) if not isinstance(beadtype,(list,tuple)): beadtype = [beadtype] dup = scriptobjectgroup() for b,n,_,_ in self.zip: if b in beadtype: dup = dup + self.getattr(n) dup.getattr(n).USER = self.getattr(n).USER dup.getattr(n).forcefield = self.getattr(n).forcefield return dup
def struct(self, groupid=1, groupidname='undef')
-
create a group with name
Expand source code
def struct(self,groupid=1,groupidname="undef"): """ create a group with name """ return struct( groupid = groupid, groupidname = groupidname, groupname = self.groupname, # meaning is synonyms beadtype = self.beadtype, name = self.name, str = "group %s %s" % (groupidname, span(self.beadtype)) )
- Group Management: Objects can be combined into a group, where 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 statussection (persistentfile=True, persistentfolder=None, printflag=False, verbose=False, verbosity=None, **userdefinitions)
-
LAMMPS script: status session
constructor adding instance definitions stored in USER
Expand source code
class statussection(script): """ LAMMPS script: status session """ name = "status" description = name+" section" position = 8 section = 9 userid = "example" version = 0.1 DEFINITIONS = scriptdata( thermo = 100 ) TEMPLATE = """ # :STATUS SECTION: # Status configuration #################################################################################################### # STATUS OUTPUT #################################################################################################### compute alleint all reduce sum c_eint variable etot equal pe+ke+c_alleint+f_gfix # total energy of the system thermo ${thermo} thermo_style custom step ke pe v_etot c_alleint f_dtfix dt thermo_modify lost ignore """
Ancestors
Class variables
var DEFINITIONS
var TEMPLATE
var description
var name
var position
var section
var userid
var version
Inherited members
class struct (**kwargs)
-
Class:
struct
A lightweight class that mimics Matlab-like structures, with additional features such as dynamic field creation, indexing, concatenation, and compatibility with evaluated parameters (
param
).
Features
- Dynamic creation of fields.
- Indexing and iteration support for fields.
- Concatenation and subtraction of structures.
- Conversion to and from dictionaries.
- Compatible with
param
andparamauto
for evaluation and dependency handling.
Examples
Basic Usage
s = struct(a=1, b=2, c='${a} + ${b} # evaluate me if you can') print(s.a) # 1 s.d = 11 # Append a new field delattr(s, 'd') # Delete the field
Using
param
for Evaluationp = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can') p.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 3) # --------
Concatenation and Subtraction
Fields from the right-most structure overwrite existing values.
a = struct(a=1, b=2) b = struct(c=3, d="d", e="e") c = a + b e = c - a
Practical Shorthands
Constructing a Structure from Keys
s = struct.fromkeys(["a", "b", "c", "d"]) # Output: # -------- # a: None # b: None # c: None # d: None # --------
Building a Structure from Variables in a String
s = struct.scan("${a} + ${b} * ${c} / ${d} --- ${ee}") s.a = 1 s.b = "test" s.c = [1, "a", 2] s.generator() # Output: # X = struct( # a=1, # b="test", # c=[1, 'a', 2], # d=None, # ee=None # )
Indexing and Iteration
Structures can be indexed or sliced like lists.
c = a + b c[0] # Access the first field c[-1] # Access the last field c[:2] # Slice the structure for field in c: print(field)
Dynamic Dependency Management
struct
provides control over dependencies, sorting, and evaluation.s = struct(d=3, e="${c} + {d}", c='${a} + ${b}', a=1, b=2) s.sortdefinitions() # Output: # -------- # d: 3 # a: 1 # b: 2 # c: ${a} + ${b} # e: ${c} + ${d} # --------
For dynamic evaluation, use
param
:p = param(sortdefinitions=True, d=3, e="${c} + ${d}", c='${a} + ${b}', a=1, b=2) # Output: # -------- # d: 3 # a: 1 # b: 2 # c: ${a} + ${b} (= 3) # e: ${c} + ${d} (= 6) # --------
Overloaded Methods and Operators
Supported Operators
+
: Concatenation of two structures (__add__
).-
: Subtraction of fields (__sub__
).len()
: Number of fields (__len__
).in
: Check for field existence (__contains__
).
Method Overview
Method Description check(default)
Populate fields with defaults if missing. clear()
Remove all fields. dict2struct(dico)
Create a structure from a dictionary. disp()
Display the structure. eval()
Evaluate expressions within fields. fromkeys(keys)
Create a structure from a list of keys. generator()
Generate Python code representing the structure. items()
Return key-value pairs. keys()
Return all keys in the structure. read(file)
Load structure fields from a file. scan(string)
Extract variables from a string and populate fields. sortdefinitions()
Sort fields to resolve dependencies. struct2dict()
Convert the structure to a dictionary. values()
Return all field values. write(file)
Save the structure to a file.
Dynamic Properties
Property Description isempty
True
if the structure is empty.isdefined
True
if all fields are defined.
constructor
Expand source code
class struct(): """ Class: `struct` ================ A lightweight class that mimics Matlab-like structures, with additional features such as dynamic field creation, indexing, concatenation, and compatibility with evaluated parameters (`param`). --- ### Features - Dynamic creation of fields. - Indexing and iteration support for fields. - Concatenation and subtraction of structures. - Conversion to and from dictionaries. - Compatible with `param` and `paramauto` for evaluation and dependency handling. --- ### Examples #### Basic Usage ```python s = struct(a=1, b=2, c='${a} + ${b} # evaluate me if you can') print(s.a) # 1 s.d = 11 # Append a new field delattr(s, 'd') # Delete the field ``` #### Using `param` for Evaluation ```python p = param(a=1, b=2, c='${a} + ${b} # evaluate me if you can') p.eval() # Output: # -------- # a: 1 # b: 2 # c: ${a} + ${b} # evaluate me if you can (= 3) # -------- ``` --- ### Concatenation and Subtraction Fields from the right-most structure overwrite existing values. ```python a = struct(a=1, b=2) b = struct(c=3, d="d", e="e") c = a + b e = c - a ``` --- ### Practical Shorthands #### Constructing a Structure from Keys ```python s = struct.fromkeys(["a", "b", "c", "d"]) # Output: # -------- # a: None # b: None # c: None # d: None # -------- ``` #### Building a Structure from Variables in a String ```python s = struct.scan("${a} + ${b} * ${c} / ${d} --- ${ee}") s.a = 1 s.b = "test" s.c = [1, "a", 2] s.generator() # Output: # X = struct( # a=1, # b="test", # c=[1, 'a', 2], # d=None, # ee=None # ) ``` #### Indexing and Iteration Structures can be indexed or sliced like lists. ```python c = a + b c[0] # Access the first field c[-1] # Access the last field c[:2] # Slice the structure for field in c: print(field) ``` --- ### Dynamic Dependency Management `struct` provides control over dependencies, sorting, and evaluation. ```python s = struct(d=3, e="${c} + {d}", c='${a} + ${b}', a=1, b=2) s.sortdefinitions() # Output: # -------- # d: 3 # a: 1 # b: 2 # c: ${a} + ${b} # e: ${c} + ${d} # -------- ``` For dynamic evaluation, use `param`: ```python p = param(sortdefinitions=True, d=3, e="${c} + ${d}", c='${a} + ${b}', a=1, b=2) # Output: # -------- # d: 3 # a: 1 # b: 2 # c: ${a} + ${b} (= 3) # e: ${c} + ${d} (= 6) # -------- ``` --- ### Overloaded Methods and Operators #### Supported Operators - `+`: Concatenation of two structures (`__add__`). - `-`: Subtraction of fields (`__sub__`). - `len()`: Number of fields (`__len__`). - `in`: Check for field existence (`__contains__`). #### Method Overview | Method | Description | |-----------------------|---------------------------------------------------------| | `check(default)` | Populate fields with defaults if missing. | | `clear()` | Remove all fields. | | `dict2struct(dico)` | Create a structure from a dictionary. | | `disp()` | Display the structure. | | `eval()` | Evaluate expressions within fields. | | `fromkeys(keys)` | Create a structure from a list of keys. | | `generator()` | Generate Python code representing the structure. | | `items()` | Return key-value pairs. | | `keys()` | Return all keys in the structure. | | `read(file)` | Load structure fields from a file. | | `scan(string)` | Extract variables from a string and populate fields. | | `sortdefinitions()` | Sort fields to resolve dependencies. | | `struct2dict()` | Convert the structure to a dictionary. | | `values()` | Return all field values. | | `write(file)` | Save the structure to a file. | --- ### Dynamic Properties | Property | Description | |-------------|----------------------------------------| | `isempty` | `True` if the structure is empty. | | `isdefined` | `True` if all fields are defined. | --- """ # attributes to be overdefined _type = "struct" # object type _fulltype = "structure" # full name _ftype = "field" # field name _evalfeature = False # true if eval() is available _maxdisplay = 40 # maximum number of characters to display (should be even) _propertyasattribute = False # attributes for the iterator method # Please keep it static, duplicate the object before changing _iter_ _iter_ = 0 # excluded attributes (keep the , in the Tupple if it is singleton) _excludedattr = {'_iter_','__class__','_protection','_evaluation','_returnerror'} # used by keys() and len() # Methods def __init__(self,**kwargs): """ constructor """ # Optionally extend _excludedattr here self._excludedattr = self._excludedattr | {'_excludedattr', '_type', '_fulltype','_ftype'} # addition 2024-10-11 self.set(**kwargs) def zip(self): """ zip keys and values """ return zip(self.keys(),self.values()) @staticmethod def dict2struct(dico,makeparam=False): """ create a structure from a dictionary """ if isinstance(dico,dict): s = param() if makeparam else struct() s.set(**dico) return s raise TypeError("the argument must be a dictionary") def struct2dict(self): """ create a dictionary from the current structure """ return dict(self.zip()) def struct2param(self,protection=False,evaluation=True): """ convert an object struct() to param() """ p = param(**self.struct2dict()) for i in range(len(self)): if isinstance(self[i],pstr): p[i] = pstr(p[i]) p._protection = protection p._evaluation = evaluation return p def set(self,**kwargs): """ initialization """ self.__dict__.update(kwargs) def setattr(self,key,value): """ set field and value """ if isinstance(value,list) and len(value)==0 and key in self: delattr(self, key) else: self.__dict__[key] = value def getattr(self,key): """Get attribute override to access both instance attributes and properties if allowed.""" if key in self.__dict__: return self.__dict__[key] elif getattr(self, '_propertyasattribute', False) and \ key not in self._excludedattr and \ key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property): # If _propertyasattribute is True and it's a property, get its value return self.__class__.__dict__[key].fget(self) else: raise AttributeError(f'the {self._ftype} "{key}" does not exist') def hasattr(self, key): """Return true if the field exists, considering properties as regular attributes if allowed.""" return key in self.__dict__ or ( getattr(self, '_propertyasattribute', False) and key not in self._excludedattr and key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property) ) def __getstate__(self): """ getstate for cooperative inheritance / duplication """ return self.__dict__.copy() def __setstate__(self,state): """ setstate for cooperative inheritance / duplication """ self.__dict__.update(state) def __getattr__(self,key): """ get attribute override """ return pstr.eval(self.getattr(key)) def __setattr__(self,key,value): """ set attribute override """ self.setattr(key,value) def __contains__(self,item): """ in override """ return self.hasattr(item) def keys(self): """ return the fields """ # keys() is used by struct() and its iterator return [key for key in self.__dict__.keys() if key not in self._excludedattr] def keyssorted(self,reverse=True): """ sort keys by length() """ klist = self.keys() l = [len(k) for k in klist] return [k for _,k in sorted(zip(l,klist),reverse=reverse)] def values(self): """ return the values """ # values() is used by struct() and its iterator return [pstr.eval(value) for key,value in self.__dict__.items() if key not in self._excludedattr] @staticmethod def fromkeysvalues(keys,values,makeparam=False): """ struct.keysvalues(keys,values) creates a structure from keys and values use makeparam = True to create a param instead of struct """ if keys is None: raise AttributeError("the keys must not empty") if not isinstance(keys,(list,tuple,np.ndarray,np.generic)): keys = [keys] if not isinstance(values,(list,tuple,np.ndarray,np.generic)): values = [values] nk,nv = len(keys), len(values) s = param() if makeparam else struct() if nk>0 and nv>0: iv = 0 for ik in range(nk): s.setattr(keys[ik], values[iv]) iv = min(nv-1,iv+1) for ik in range(nk,nv): s.setattr(f"key{ik}", values[ik]) return s def items(self): """ return all elements as iterable key, value """ return self.zip() def __getitem__(self,idx): """ s[i] returns the ith element of the structure s[:4] returns a structure with the four first fields s[[1,3]] returns the second and fourth elements """ if isinstance(idx,int): if idx<len(self): return self.getattr(self.keys()[idx]) raise IndexError(f"the {self._ftype} index should be comprised between 0 and {len(self)-1}") elif isinstance(idx,slice): return struct.fromkeysvalues(self.keys()[idx], self.values()[idx]) elif isinstance(idx,(list,tuple)): k,v= self.keys(), self.values() nk = len(k) s = param() if isinstance(self,param) else struct() for i in idx: if isinstance(i,int) and i>=0 and i<nk: s.setattr(k[i],v[i]) else: raise IndexError("idx must contains only integers ranged between 0 and %d" % (nk-1)) return s elif isinstance(idx,str): return self.getattr(idx) else: raise TypeError("The index must be an integer or a slice and not a %s" % type(idx).__name__) def __setitem__(self,idx,value): """ set the ith element of the structure """ if isinstance(idx,int): if idx<len(self): self.setattr(self.keys()[idx], value) else: raise IndexError(f"the {self._ftype} index should be comprised between 0 and {len(self)-1}") elif isinstance(idx,slice): k = self.keys()[idx] if len(value)<=1: for i in range(len(k)): self.setattr(k[i], value) elif len(k) == len(value): for i in range(len(k)): self.setattr(k[i], value[i]) else: raise IndexError("the number of values (%d) does not match the number of elements in the slive (%d)" \ % (len(value),len(idx))) elif isinstance(idx,(list,tuple)): if len(value)<=1: for i in range(len(idx)): self[idx[i]]=value elif len(idx) == len(value): for i in range(len(idx)): self[idx[i]]=value[i] else: raise IndexError("the number of values (%d) does not match the number of indices (%d)" \ % (len(value),len(idx))) def __len__(self): """ return the number of fields """ # len() is used by struct() and its iterator return len(self.keys()) def __iter__(self): """ struct iterator """ # note that in the original object _iter_ is a static property not in dup dup = duplicate(self) dup._iter_ = 0 return dup def __next__(self): """ increment iterator """ self._iter_ += 1 if self._iter_<=len(self): return self[self._iter_-1] self._iter_ = 0 raise StopIteration(f"Maximum {self._ftype} iteration reached {len(self)}") def __add__(self,s,sortdefinitions=False,raiseerror=True, silentmode=True): """ add a structure set sortdefintions=True to sort definitions (to maintain executability) """ if not isinstance(s,struct): raise TypeError(f"the second operand must be {self._type}") dup = duplicate(self) dup.__dict__.update(s.__dict__) if sortdefinitions: dup.sortdefinitions(raiseerror=raiseerror,silentmode=silentmode) return dup def __iadd__(self,s,sortdefinitions=False,raiseerror=False, silentmode=True): """ iadd a structure set sortdefintions=True to sort definitions (to maintain executability) """ if not isinstance(s,struct): raise TypeError(f"the second operand must be {self._type}") self.__dict__.update(s.__dict__) if sortdefinitions: self.sortdefinitions(raiseerror=raiseerror,silentmode=silentmode) return self def __sub__(self,s): """ sub a structure """ if not isinstance(s,struct): raise TypeError(f"the second operand must be {self._type}") dup = duplicate(self) listofkeys = dup.keys() for k in s.keys(): if k in listofkeys: delattr(dup,k) return dup def __isub__(self,s): """ isub a structure """ if not isinstance(s,struct): raise TypeError(f"the second operand must be {self._type}") listofkeys = self.keys() for k in s.keys(): if k in listofkeys: delattr(self,k) return self def dispmax(self,content): """ optimize display """ strcontent = str(content) if len(strcontent)>self._maxdisplay: nchar = round(self._maxdisplay/2) return strcontent[:nchar]+" [...] "+strcontent[-nchar:] else: return content def __repr__(self): """ display method """ if self.__dict__=={}: print(f"empty {self._fulltype} ({self._type} object) with no {self._type}s") return f"empty {self._fulltype}" else: tmp = self.eval() if self._evalfeature else [] keylengths = [len(key) for key in self.__dict__] width = max(10,max(keylengths)+2) fmt = "%%%ss:" % width fmteval = fmt[:-1]+"=" fmtcls = fmt[:-1]+":" line = ( fmt % ('-'*(width-2)) ) + ( '-'*(min(40,width*5)) ) print(line) for key,value in self.__dict__.items(): if key not in self._excludedattr: if isinstance(value,(int,float,str,list,tuple,np.ndarray,np.generic)): if isinstance(value,pstr): print(fmt % key,'p"'+self.dispmax(value)+'"') if isinstance(value,str) and value=="": print(fmt % key,'""') else: print(fmt % key,self.dispmax(value)) elif isinstance(value,struct): print(fmt % key,self.dispmax(value.__str__())) elif isinstance(value,type): print(fmt % key,self.dispmax(str(value))) else: print(fmt % key,type(value)) print(fmtcls % "",self.dispmax(str(value))) if self._evalfeature: if isinstance(self,paramauto): try: if isinstance(value,pstr): print(fmteval % "",'p"'+self.dispmax(tmp.getattr(key))+'"') elif isinstance(value,str): if value == "": print(fmteval % "",self.dispmax("<empty string>")) else: print(fmteval % "",self.dispmax(tmp.getattr(key))) except Exception as err: print(fmteval % "",err.message, err.args) else: if isinstance(value,pstr): print(fmteval % "",'p"'+self.dispmax(tmp.getattr(key))+'"') elif isinstance(value,str): if value == "": print(fmteval % "",self.dispmax("<empty string>")) else: print(fmteval % "",self.dispmax(tmp.getattr(key))) print(line) return f"{self._fulltype} ({self._type} object) with {len(self)} {self._ftype}s" def disp(self): """ display method """ self.__repr__() def __str__(self): return f"{self._fulltype} ({self._type} object) with {len(self)} {self._ftype}s" @property def isempty(self): """ isempty is set to True for an empty structure """ return len(self)==0 def clear(self): """ clear() delete all fields while preserving the original class """ for k in self.keys(): delattr(self,k) def format(self,s,escape=False,raiseerror=True): """ format a string with field (use {field} as placeholders) s.replace(string), s.replace(string,escape=True) where: s is a struct object string is a string with possibly ${variable1} escape is a flag to prevent ${} replaced by {} """ if raiseerror: try: if escape: return s.format(**self.__dict__) else: return s.replace("${","{").format(**self.__dict__) except KeyError as kerr: s_ = s.replace("{","${") print(f"WARNING: the {self._ftype} {kerr} is undefined in '{s_}'") return s_ # instead of s (we put back $) - OV 2023/01/27 except Exception as othererr: s_ = s.replace("{","${") raise RuntimeError from othererr else: if escape: return s.format(**self.__dict__) else: return s.replace("${","{").format(**self.__dict__) def fromkeys(self,keys): """ returns a structure from keys """ return self+struct(**dict.fromkeys(keys,None)) @staticmethod def scan(s): """ scan(string) scan a string for variables """ if not isinstance(s,str): raise TypeError("scan() requires a string") tmp = struct() #return tmp.fromkeys(set(re.findall(r"\$\{(.*?)\}",s))) found = re.findall(r"\$\{(.*?)\}",s); uniq = [] for x in found: if x not in uniq: uniq.append(x) return tmp.fromkeys(uniq) @staticmethod def isstrexpression(s): """ isstrexpression(string) returns true if s contains an expression """ if not isinstance(s,str): raise TypeError("s must a string") return re.search(r"\$\{.*?\}",s) is not None @property def isexpression(self): """ same structure with True if it is an expression """ s = param() if isinstance(self,param) else struct() for k,v in self.items(): if isinstance(v,str): s.setattr(k,struct.isstrexpression(v)) else: s.setattr(k,False) return s @staticmethod def isstrdefined(s,ref): """ isstrdefined(string,ref) returns true if it is defined in ref """ if not isinstance(s,str): raise TypeError("s must a string") if not isinstance(ref,struct): raise TypeError("ref must be a structure") if struct.isstrexpression(s): k = struct.scan(s).keys() allfound,i,nk = True,0,len(k) while (i<nk) and allfound: allfound = k[i] in ref i += 1 return allfound else: return False def isdefined(self,ref=None): """ isdefined(ref) returns true if it is defined in ref """ s = param() if isinstance(self,param) else struct() k,v,isexpr = self.keys(), self.values(), self.isexpression.values() nk = len(k) if ref is None: for i in range(nk): if isexpr[i]: s.setattr(k[i],struct.isstrdefined(v[i],self[:i])) else: s.setattr(k[i],True) else: if not isinstance(ref,struct): raise TypeError("ref must be a structure") for i in range(nk): if isexpr[i]: s.setattr(k[i],struct.isstrdefined(v[i],ref)) else: s.setattr(k[i],True) return s def sortdefinitions(self,raiseerror=True,silentmode=False): """ sortdefintions sorts all definitions so that they can be executed as param(). If any inconsistency is found, an error message is generated. Flags = default values raiseerror=True show erros of True silentmode=False no warning if True """ find = lambda xlist: [i for i, x in enumerate(xlist) if x] findnot = lambda xlist: [i for i, x in enumerate(xlist) if not x] k,v,isexpr = self.keys(), self.values(), self.isexpression.values() istatic = findnot(isexpr) idynamic = find(isexpr) static = struct.fromkeysvalues( [ k[i] for i in istatic ], [ v[i] for i in istatic ], makeparam = False) dynamic = struct.fromkeysvalues( [ k[i] for i in idynamic ], [ v[i] for i in idynamic ], makeparam=False) current = static # make static the current structure nmissing, anychange, errorfound = len(dynamic), False, False while nmissing: itst, found = 0, False while itst<nmissing and not found: teststruct = current + dynamic[[itst]] # add the test field found = all(list(teststruct.isdefined())) ifound = itst itst += 1 if found: current = teststruct # we accept the new field dynamic[ifound] = [] nmissing -= 1 anychange = True else: if raiseerror: raise KeyError('unable to interpret %d/%d expressions in "%ss"' % \ (nmissing,len(self),self._ftype)) else: if (not errorfound) and (not silentmode): print('WARNING: unable to interpret %d/%d expressions in "%ss"' % \ (nmissing,len(self),self._ftype)) current = teststruct # we accept the new field (even if it cannot be interpreted) dynamic[ifound] = [] nmissing -= 1 errorfound = True if anychange: self.clear() # reset all fields and assign them in the proper order k,v = current.keys(), current.values() for i in range(len(k)): self.setattr(k[i],v[i]) def generator(self): """ generate Python code of the equivalent structure """ nk = len(self) if nk==0: print("X = struct()") else: ik = 0 fmt = "%%%ss=" % max(10,max([len(k) for k in self.keys()])+2) print("\nX = struct(") for k in self.keys(): ik += 1 end = ",\n" if ik<nk else "\n"+(fmt[:-1] % ")")+"\n" v = getattr(self,k) if isinstance(v,(int,float)) or v == None: print(fmt % k,v,end=end) elif isinstance(v,str): print(fmt % k,f'"{v}"',end=end) elif isinstance(v,(list,tuple)): print(fmt % k,v,end=end) else: print(fmt % k,"/* unsupported type */",end=end) # copy and deep copy methpds for the class def __copy__(self): """ copy method """ cls = self.__class__ copie = cls.__new__(cls) copie.__dict__.update(self.__dict__) return copie def __deepcopy__(self, memo): """ deep copy method """ cls = self.__class__ copie = cls.__new__(cls) memo[id(self)] = copie for k, v in self.__dict__.items(): setattr(copie, k, duplicatedeep(v, memo)) return copie # write a file def write(self, file, overwrite=True, mkdir=False): """ write the equivalent structure (not recursive for nested struct) write(filename, overwrite=True, mkdir=False) Parameters: - file: The file path to write to. - overwrite: Whether to overwrite the file if it exists (default: True). - mkdir: Whether to create the directory if it doesn't exist (default: False). """ # Create a Path object for the file to handle cross-platform paths file_path = Path(file).resolve() # Check if the directory exists or if mkdir is set to True, create it if mkdir: file_path.parent.mkdir(parents=True, exist_ok=True) elif not file_path.parent.exists(): raise FileNotFoundError(f"The directory {file_path.parent} does not exist.") # If overwrite is False and the file already exists, raise an exception if not overwrite and file_path.exists(): raise FileExistsError(f"The file {file_path} already exists, and overwrite is set to False.") # Open and write to the file using the resolved path with file_path.open(mode="w", encoding='utf-8') as f: print(f"# {self._fulltype} with {len(self)} {self._ftype}s\n", file=f) for k, v in self.items(): if v is None: print(k, "=None", file=f, sep="") elif isinstance(v, (int, float)): print(k, "=", v, file=f, sep="") elif isinstance(v, str): print(k, '="', v, '"', file=f, sep="") else: print(k, "=", str(v), file=f, sep="") # read a file @staticmethod def read(file): """ read the equivalent structure read(filename) Parameters: - file: The file path to read from. """ # Create a Path object for the file to handle cross-platform paths file_path = Path(file).resolve() # Check if the parent directory exists, otherwise raise an error if not file_path.parent.exists(): raise FileNotFoundError(f"The directory {file_path.parent} does not exist.") # If the file does not exist, raise an exception if not file_path.exists(): raise FileNotFoundError(f"The file {file_path} does not exist.") # Open and read the file with file_path.open(mode="r", encoding="utf-8") as f: s = struct() # Assuming struct is defined elsewhere while True: line = f.readline() if not line: break line = line.strip() expr = line.split(sep="=") if len(line) > 0 and line[0] != "#" and len(expr) > 0: lhs = expr[0] rhs = "".join(expr[1:]).strip() if len(rhs) == 0 or rhs == "None": v = None else: v = eval(rhs) s.setattr(lhs, v) return s # argcheck def check(self,default): """ populate fields from a default structure check(defaultstruct) missing field, None and [] values are replaced by default ones Note: a.check(b) is equivalent to b+a except for [] and None values """ if not isinstance(default,struct): raise TypeError("the first argument must be a structure") for f in default.keys(): ref = default.getattr(f) if f not in self: self.setattr(f, ref) else: current = self.getattr(f) if ((current is None) or (current==[])) and \ ((ref is not None) and (ref!=[])): self.setattr(f, ref) # update values based on key:value def update(self, **kwargs): """ Update multiple fields at once, while protecting certain attributes. Parameters: ----------- **kwargs : dict The fields to update and their new values. Protected attributes defined in _excludedattr are not updated. Usage: ------ s.update(a=10, b=[1, 2, 3], new_field="new_value") """ protected_attributes = getattr(self, '_excludedattr', ()) for key, value in kwargs.items(): if key in protected_attributes: print(f"Warning: Cannot update protected attribute '{key}'") else: self.setattr(key, value) # override () for subindexing structure with key names def __call__(self, *keys): """ Extract a sub-structure based on the specified keys, keeping the same class type. Parameters: ----------- *keys : str The keys for the fields to include in the sub-structure. Returns: -------- struct A new instance of the same class as the original, containing only the specified keys. Usage: ------ sub_struct = s('key1', 'key2', ...) """ # Create a new instance of the same class sub_struct = self.__class__() # Get the full type and field type for error messages fulltype = getattr(self, '_fulltype', 'structure') ftype = getattr(self, '_ftype', 'field') # Add only the specified keys to the new sub-structure for key in keys: if key in self: sub_struct.setattr(key, self.getattr(key)) else: raise KeyError(f"{fulltype} does not contain the {ftype} '{key}'.") return sub_struct def __delattr__(self, key): """ Delete an instance attribute if it exists and is not a class or excluded attribute. """ if key in self._excludedattr: raise AttributeError(f"Cannot delete excluded attribute '{key}'") elif key in self.__class__.__dict__: # Check if it's a class attribute raise AttributeError(f"Cannot delete class attribute '{key}'") elif key in self.__dict__: # Delete only if in instance's __dict__ del self.__dict__[key] else: raise AttributeError(f"{self._type} has no attribute '{key}'")
Subclasses
- pizza.private.mstruct.param
- pizza.raster.collection
- pizza.region.regioncollection
- pizza.script.scriptobject
- pizza.script.scriptobjectgroup
- scriptobject
- scriptobjectgroup
Static methods
def dict2struct(dico, makeparam=False)
-
create a structure from a dictionary
Expand source code
@staticmethod def dict2struct(dico,makeparam=False): """ create a structure from a dictionary """ if isinstance(dico,dict): s = param() if makeparam else struct() s.set(**dico) return s raise TypeError("the argument must be a dictionary")
def fromkeysvalues(keys, values, makeparam=False)
-
struct.keysvalues(keys,values) creates a structure from keys and values use makeparam = True to create a param instead of struct
Expand source code
@staticmethod def fromkeysvalues(keys,values,makeparam=False): """ struct.keysvalues(keys,values) creates a structure from keys and values use makeparam = True to create a param instead of struct """ if keys is None: raise AttributeError("the keys must not empty") if not isinstance(keys,(list,tuple,np.ndarray,np.generic)): keys = [keys] if not isinstance(values,(list,tuple,np.ndarray,np.generic)): values = [values] nk,nv = len(keys), len(values) s = param() if makeparam else struct() if nk>0 and nv>0: iv = 0 for ik in range(nk): s.setattr(keys[ik], values[iv]) iv = min(nv-1,iv+1) for ik in range(nk,nv): s.setattr(f"key{ik}", values[ik]) return s
def isstrdefined(s, ref)
-
isstrdefined(string,ref) returns true if it is defined in ref
Expand source code
@staticmethod def isstrdefined(s,ref): """ isstrdefined(string,ref) returns true if it is defined in ref """ if not isinstance(s,str): raise TypeError("s must a string") if not isinstance(ref,struct): raise TypeError("ref must be a structure") if struct.isstrexpression(s): k = struct.scan(s).keys() allfound,i,nk = True,0,len(k) while (i<nk) and allfound: allfound = k[i] in ref i += 1 return allfound else: return False
def isstrexpression(s)
-
isstrexpression(string) returns true if s contains an expression
Expand source code
@staticmethod def isstrexpression(s): """ isstrexpression(string) returns true if s contains an expression """ if not isinstance(s,str): raise TypeError("s must a string") return re.search(r"\$\{.*?\}",s) is not None
def read(file)
-
read the equivalent structure read(filename)
Parameters: - file: The file path to read from.
Expand source code
@staticmethod def read(file): """ read the equivalent structure read(filename) Parameters: - file: The file path to read from. """ # Create a Path object for the file to handle cross-platform paths file_path = Path(file).resolve() # Check if the parent directory exists, otherwise raise an error if not file_path.parent.exists(): raise FileNotFoundError(f"The directory {file_path.parent} does not exist.") # If the file does not exist, raise an exception if not file_path.exists(): raise FileNotFoundError(f"The file {file_path} does not exist.") # Open and read the file with file_path.open(mode="r", encoding="utf-8") as f: s = struct() # Assuming struct is defined elsewhere while True: line = f.readline() if not line: break line = line.strip() expr = line.split(sep="=") if len(line) > 0 and line[0] != "#" and len(expr) > 0: lhs = expr[0] rhs = "".join(expr[1:]).strip() if len(rhs) == 0 or rhs == "None": v = None else: v = eval(rhs) s.setattr(lhs, v) return s
def scan(s)
-
scan(string) scan a string for variables
Expand source code
@staticmethod def scan(s): """ scan(string) scan a string for variables """ if not isinstance(s,str): raise TypeError("scan() requires a string") tmp = struct() #return tmp.fromkeys(set(re.findall(r"\$\{(.*?)\}",s))) found = re.findall(r"\$\{(.*?)\}",s); uniq = [] for x in found: if x not in uniq: uniq.append(x) return tmp.fromkeys(uniq)
Instance variables
var isempty
-
isempty is set to True for an empty structure
Expand source code
@property def isempty(self): """ isempty is set to True for an empty structure """ return len(self)==0
var isexpression
-
same structure with True if it is an expression
Expand source code
@property def isexpression(self): """ same structure with True if it is an expression """ s = param() if isinstance(self,param) else struct() for k,v in self.items(): if isinstance(v,str): s.setattr(k,struct.isstrexpression(v)) else: s.setattr(k,False) return s
Methods
def check(self, default)
-
populate fields from a default structure check(defaultstruct) missing field, None and [] values are replaced by default ones
Note: a.check(b) is equivalent to b+a except for [] and None values
Expand source code
def check(self,default): """ populate fields from a default structure check(defaultstruct) missing field, None and [] values are replaced by default ones Note: a.check(b) is equivalent to b+a except for [] and None values """ if not isinstance(default,struct): raise TypeError("the first argument must be a structure") for f in default.keys(): ref = default.getattr(f) if f not in self: self.setattr(f, ref) else: current = self.getattr(f) if ((current is None) or (current==[])) and \ ((ref is not None) and (ref!=[])): self.setattr(f, ref)
def clear(self)
-
clear() delete all fields while preserving the original class
Expand source code
def clear(self): """ clear() delete all fields while preserving the original class """ for k in self.keys(): delattr(self,k)
def disp(self)
-
display method
Expand source code
def disp(self): """ display method """ self.__repr__()
def dispmax(self, content)
-
optimize display
Expand source code
def dispmax(self,content): """ optimize display """ strcontent = str(content) if len(strcontent)>self._maxdisplay: nchar = round(self._maxdisplay/2) return strcontent[:nchar]+" [...] "+strcontent[-nchar:] else: return content
def format(self, s, escape=False, raiseerror=True)
-
format a string with field (use {field} as placeholders) s.replace(string), s.replace(string,escape=True) where: s is a struct object string is a string with possibly ${variable1} escape is a flag to prevent ${} replaced by {}
Expand source code
def format(self,s,escape=False,raiseerror=True): """ format a string with field (use {field} as placeholders) s.replace(string), s.replace(string,escape=True) where: s is a struct object string is a string with possibly ${variable1} escape is a flag to prevent ${} replaced by {} """ if raiseerror: try: if escape: return s.format(**self.__dict__) else: return s.replace("${","{").format(**self.__dict__) except KeyError as kerr: s_ = s.replace("{","${") print(f"WARNING: the {self._ftype} {kerr} is undefined in '{s_}'") return s_ # instead of s (we put back $) - OV 2023/01/27 except Exception as othererr: s_ = s.replace("{","${") raise RuntimeError from othererr else: if escape: return s.format(**self.__dict__) else: return s.replace("${","{").format(**self.__dict__)
def fromkeys(self, keys)
-
returns a structure from keys
Expand source code
def fromkeys(self,keys): """ returns a structure from keys """ return self+struct(**dict.fromkeys(keys,None))
def generator(self)
-
generate Python code of the equivalent structure
Expand source code
def generator(self): """ generate Python code of the equivalent structure """ nk = len(self) if nk==0: print("X = struct()") else: ik = 0 fmt = "%%%ss=" % max(10,max([len(k) for k in self.keys()])+2) print("\nX = struct(") for k in self.keys(): ik += 1 end = ",\n" if ik<nk else "\n"+(fmt[:-1] % ")")+"\n" v = getattr(self,k) if isinstance(v,(int,float)) or v == None: print(fmt % k,v,end=end) elif isinstance(v,str): print(fmt % k,f'"{v}"',end=end) elif isinstance(v,(list,tuple)): print(fmt % k,v,end=end) else: print(fmt % k,"/* unsupported type */",end=end)
def getattr(self, key)
-
Get attribute override to access both instance attributes and properties if allowed.
Expand source code
def getattr(self,key): """Get attribute override to access both instance attributes and properties if allowed.""" if key in self.__dict__: return self.__dict__[key] elif getattr(self, '_propertyasattribute', False) and \ key not in self._excludedattr and \ key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property): # If _propertyasattribute is True and it's a property, get its value return self.__class__.__dict__[key].fget(self) else: raise AttributeError(f'the {self._ftype} "{key}" does not exist')
def hasattr(self, key)
-
Return true if the field exists, considering properties as regular attributes if allowed.
Expand source code
def hasattr(self, key): """Return true if the field exists, considering properties as regular attributes if allowed.""" return key in self.__dict__ or ( getattr(self, '_propertyasattribute', False) and key not in self._excludedattr and key in self.__class__.__dict__ and isinstance(self.__class__.__dict__[key], property) )
def isdefined(self, ref=None)
-
isdefined(ref) returns true if it is defined in ref
Expand source code
def isdefined(self,ref=None): """ isdefined(ref) returns true if it is defined in ref """ s = param() if isinstance(self,param) else struct() k,v,isexpr = self.keys(), self.values(), self.isexpression.values() nk = len(k) if ref is None: for i in range(nk): if isexpr[i]: s.setattr(k[i],struct.isstrdefined(v[i],self[:i])) else: s.setattr(k[i],True) else: if not isinstance(ref,struct): raise TypeError("ref must be a structure") for i in range(nk): if isexpr[i]: s.setattr(k[i],struct.isstrdefined(v[i],ref)) else: s.setattr(k[i],True) return s
def items(self)
-
return all elements as iterable key, value
Expand source code
def items(self): """ return all elements as iterable key, value """ return self.zip()
def keys(self)
-
return the fields
Expand source code
def keys(self): """ return the fields """ # keys() is used by struct() and its iterator return [key for key in self.__dict__.keys() if key not in self._excludedattr]
def keyssorted(self, reverse=True)
-
sort keys by length()
Expand source code
def keyssorted(self,reverse=True): """ sort keys by length() """ klist = self.keys() l = [len(k) for k in klist] return [k for _,k in sorted(zip(l,klist),reverse=reverse)]
def set(self, **kwargs)
-
initialization
Expand source code
def set(self,**kwargs): """ initialization """ self.__dict__.update(kwargs)
def setattr(self, key, value)
-
set field and value
Expand source code
def setattr(self,key,value): """ set field and value """ if isinstance(value,list) and len(value)==0 and key in self: delattr(self, key) else: self.__dict__[key] = value
def sortdefinitions(self, raiseerror=True, silentmode=False)
-
sortdefintions sorts all definitions so that they can be executed as param(). If any inconsistency is found, an error message is generated.
Flags = default values raiseerror=True show erros of True silentmode=False no warning if True
Expand source code
def sortdefinitions(self,raiseerror=True,silentmode=False): """ sortdefintions sorts all definitions so that they can be executed as param(). If any inconsistency is found, an error message is generated. Flags = default values raiseerror=True show erros of True silentmode=False no warning if True """ find = lambda xlist: [i for i, x in enumerate(xlist) if x] findnot = lambda xlist: [i for i, x in enumerate(xlist) if not x] k,v,isexpr = self.keys(), self.values(), self.isexpression.values() istatic = findnot(isexpr) idynamic = find(isexpr) static = struct.fromkeysvalues( [ k[i] for i in istatic ], [ v[i] for i in istatic ], makeparam = False) dynamic = struct.fromkeysvalues( [ k[i] for i in idynamic ], [ v[i] for i in idynamic ], makeparam=False) current = static # make static the current structure nmissing, anychange, errorfound = len(dynamic), False, False while nmissing: itst, found = 0, False while itst<nmissing and not found: teststruct = current + dynamic[[itst]] # add the test field found = all(list(teststruct.isdefined())) ifound = itst itst += 1 if found: current = teststruct # we accept the new field dynamic[ifound] = [] nmissing -= 1 anychange = True else: if raiseerror: raise KeyError('unable to interpret %d/%d expressions in "%ss"' % \ (nmissing,len(self),self._ftype)) else: if (not errorfound) and (not silentmode): print('WARNING: unable to interpret %d/%d expressions in "%ss"' % \ (nmissing,len(self),self._ftype)) current = teststruct # we accept the new field (even if it cannot be interpreted) dynamic[ifound] = [] nmissing -= 1 errorfound = True if anychange: self.clear() # reset all fields and assign them in the proper order k,v = current.keys(), current.values() for i in range(len(k)): self.setattr(k[i],v[i])
def struct2dict(self)
-
create a dictionary from the current structure
Expand source code
def struct2dict(self): """ create a dictionary from the current structure """ return dict(self.zip())
def struct2param(self, protection=False, evaluation=True)
-
convert an object struct() to param()
Expand source code
def struct2param(self,protection=False,evaluation=True): """ convert an object struct() to param() """ p = param(**self.struct2dict()) for i in range(len(self)): if isinstance(self[i],pstr): p[i] = pstr(p[i]) p._protection = protection p._evaluation = evaluation return p
def update(self, **kwargs)
-
Update multiple fields at once, while protecting certain attributes.
Parameters:
**kwargs : dict The fields to update and their new values.
Protected attributes defined in _excludedattr are not updated.
Usage:
s.update(a=10, b=[1, 2, 3], new_field="new_value")
Expand source code
def update(self, **kwargs): """ Update multiple fields at once, while protecting certain attributes. Parameters: ----------- **kwargs : dict The fields to update and their new values. Protected attributes defined in _excludedattr are not updated. Usage: ------ s.update(a=10, b=[1, 2, 3], new_field="new_value") """ protected_attributes = getattr(self, '_excludedattr', ()) for key, value in kwargs.items(): if key in protected_attributes: print(f"Warning: Cannot update protected attribute '{key}'") else: self.setattr(key, value)
def values(self)
-
return the values
Expand source code
def values(self): """ return the values """ # values() is used by struct() and its iterator return [pstr.eval(value) for key,value in self.__dict__.items() if key not in self._excludedattr]
def write(self, file, overwrite=True, mkdir=False)
-
write the equivalent structure (not recursive for nested struct) write(filename, overwrite=True, mkdir=False)
Parameters: - file: The file path to write to. - overwrite: Whether to overwrite the file if it exists (default: True). - mkdir: Whether to create the directory if it doesn't exist (default: False).
Expand source code
def write(self, file, overwrite=True, mkdir=False): """ write the equivalent structure (not recursive for nested struct) write(filename, overwrite=True, mkdir=False) Parameters: - file: The file path to write to. - overwrite: Whether to overwrite the file if it exists (default: True). - mkdir: Whether to create the directory if it doesn't exist (default: False). """ # Create a Path object for the file to handle cross-platform paths file_path = Path(file).resolve() # Check if the directory exists or if mkdir is set to True, create it if mkdir: file_path.parent.mkdir(parents=True, exist_ok=True) elif not file_path.parent.exists(): raise FileNotFoundError(f"The directory {file_path.parent} does not exist.") # If overwrite is False and the file already exists, raise an exception if not overwrite and file_path.exists(): raise FileExistsError(f"The file {file_path} already exists, and overwrite is set to False.") # Open and write to the file using the resolved path with file_path.open(mode="w", encoding='utf-8') as f: print(f"# {self._fulltype} with {len(self)} {self._ftype}s\n", file=f) for k, v in self.items(): if v is None: print(k, "=None", file=f, sep="") elif isinstance(v, (int, float)): print(k, "=", v, file=f, sep="") elif isinstance(v, str): print(k, '="', v, '"', file=f, sep="") else: print(k, "=", str(v), file=f, sep="")
def zip(self)
-
zip keys and values
Expand source code
def zip(self): """ zip keys and values """ return zip(self.keys(),self.values())
class tlsph
-
SMD:TLSPH forcefield (total Lagrangian)
Expand source code
class tlsph(smd): """ SMD:TLSPH forcefield (total Lagrangian) """ name = smd.name + struct(style="tlsph") description = smd.description + struct(style="SMD:TLSPH - total Lagrangian for solids") # style definition (LAMMPS code between triple """) PAIR_DIAGCOEFF = """ # [comment] Diagonal pair coefficient tlsph pair_coeff %d %d smd/tlsph *COMMON ${rho} ${E} ${nu} ${q1} ${q2} ${Hg} ${Cp} & *STRENGTH_LINEAR_PLASTIC ${sigma_yield} ${hardening} & *EOS_LINEAR & *END """ PAIR_OFFDIAGCOEFF = """ # [comment] Off-diagonal pair coefficient (generic) pair_coeff %d %d smd/hertz ${contact_stiffness} """
Ancestors
- 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