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__ = "1.006"
# INRAE\Olivier Vitrac - rev. 2025-02-18 (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()
# 2025-01-02 improve + and | for script and pipescript
# 2025-01-04 add VariableOccurrences, pipescript.list_values(), pipescript.list_multiple_values(), pipescript.plot_value_distribution()
# 2025-01-06 script.dscript() forces autorefresh=False to prevent automatic assignement of variables not definet yet (see dscript.ScriptTemplate constructor)
# 2025-01-07 add VariableOccurrences.export() in Markdown and HTML, pipescript.generate_report() (version 1.0)
# 2025-01-18 consistent implementation of do() between dscript and script for indexed variables
# 2025-02-18 fix pipescipt | scriptobjectgroup (the script method was not called before)
# %% Dependencies
import os, sys, datetime, socket, getpass, tempfile, types, re, inspect
from copy import copy as duplicate
from copy import deepcopy as deepduplicate
from shutil import copy as copyfile
# To facilitate data review with `VariableOccurrences` class
from collections import defaultdict
import matplotlib.pyplot as plt
# All forcefield parameters are stored à la Matlab in a structure
from pizza.private.mstruct import param,struct
from pizza.forcefield import *
__all__ = ['CallableScript', 'VariableOccurrences', 'boundarysection', 'discretizationsection', 'dumpsection', 'forcefield', 'frame_header', 'geometrysection', 'get_metadata', 'get_tmp_location', 'globalsection', 'initializesection', 'integrationsection', 'interactionsection', 'is_scalar', 'make_hashable', 'none', 'param', 'paramauto', 'parameterforcefield', 'picker', 'pipescript', 'remove_comments', 'rigidwall', 'runsection', 'saltTLSPH', 'script', 'scriptdata', 'scriptobject', 'scriptobjectgroup', 'smd', 'solidfood', 'span', 'statussection', 'struct', 'tlsph', 'tlsphalone', 'ulsph', 'ulsphalone', '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"
def make_hashable(val):
"""
Recursively converts lists and dictionaries to tuples to make them hashable.
"""
if isinstance(val, list):
return tuple(make_hashable(item) for item in val)
elif isinstance(val, dict):
return tuple(sorted((k, make_hashable(v)) for k, v in val.items()))
return val
def is_scalar(val):
"""
Determines if a value is scalar (not a list, dict, or tuple).
"""
return not isinstance(val, (list, dict, tuple))
# 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)
# This class encapsulates the raw data and provides utility methods for analyzing variable occurrences.
class VariableOccurrences:
"""
The `VariableOccurrences` class encapsulates raw data and provides utility methods for analyzing variable occurrences
across various scopes. It is designed to handle both single and multiple variables, supporting flexible data structures
and analysis operations.
Class Methods:
--------------
- `__init__(data, variables=None)`: Initializes the class with raw data and optional variable names.
- `_determine_scopes()`: Determines the unique scopes present across all variables.
- `get_raw_data()`: Returns the raw data as provided during initialization.
- `get_steps_with_value(value, within_list=False)`: Retrieves steps where the variable equals the specified value.
- `get_all_values()`: Retrieves all unique values of the variable(s).
- `get_all_elements_in_lists()`: Retrieves all unique elements within list-type variable values.
- `get_usage_count(value, within_list=False)`: Counts occurrences of a specific value.
- `get_steps_with_value_in_scope(variable, scope, value, within_list=False)`: Retrieves steps where a variable equals a value within a specific scope.
- `summarize()`: Summarizes the occurrences of variables, including counts and unique elements.
- `export(filename=None, scopes='all', variables='all', include_headers=True, return_content=False)`:
Exports variable occurrences as Markdown, plain text, or HTML.
Attributes:
-----------
- `data`: Dictionary containing the raw variable data.
- Single variable: `{'scope1': [...], 'scope2': [...]}`.
- Multiple variables: `{'var1': {...}, 'var2': {...}, ...}`.
- `variables`: List of variable names managed by the instance.
- `scopes`: List of unique scopes across all variables.
Usage:
------
The class is useful for tracking and analyzing variable values across different contexts, such as configuration files,
programming environments, or simulation data. It supports advanced querying, summary generation, and export functionality.
Example:
--------
# Initialize with single variable
data = {'global': [(0, 1), (1, 2)], 'local': [(0, 3), (1, 4)]}
vo = VariableOccurrences(data, variables="var1")
# Initialize with multiple variables
data = {
"var1": {"global": [(0, 1), (1, 2)], "local": [(0, 3), (1, 4)]},
"var2": {"global": [1, 2], "local": [3, 4]},
}
vo = VariableOccurrences(data)
# Query steps where a value is present
steps = vo.get_steps_with_value(2)
# Export data to a Markdown file
vo.export("output.md")
"""
def __init__(self, data, variables=None):
"""
Initializes the VariableOccurrences object.
Parameters:
- data:
- For single variable: dict with scopes as keys and lists of values or a single value.
- For multiple variables: dict mapping variable names to their respective scope data.
- variables (str or list/tuple, optional):
- If single variable: string representing the variable name.
- If multiple variables: list or tuple of variable names.
- If None: assumes data is for multiple variables without specifying names.
"""
if variables is None:
# Assume data is a dict mapping variable names to their scope data
if not isinstance(data, dict):
raise ValueError("For multiple variables, data must be a dict mapping variable names to their scope data.")
self.variables = list(data.keys())
self.data = data # {var1: {scope1: [...], scope2: [...]}, var2: {...}, ...}
elif isinstance(variables, str):
# Single variable case
self.variables = [variables]
if not isinstance(data, dict):
raise ValueError("For single variable, data must be a dict with scopes as keys and lists of values or single values as values.")
self.data = {variables: data} # {var: {scope1: [...], scope2: [...], ...}}
elif isinstance(variables, (list, tuple)):
# Multiple variables specified
if not isinstance(data, dict):
raise ValueError("For multiple variables, data must be a dict mapping variable names to their scope data.")
if set(variables) > set(data.keys()):
missing_vars = set(variables) - set(data.keys())
raise ValueError(f"Data does not contain entries for variables: {', '.join(missing_vars)}")
self.variables = list(variables)
self.data = {var: data[var] for var in variables} # {var1: {...}, var2: {...}, ...}
else:
raise ValueError("Parameter 'variables' must be a string, list, tuple, or None.")
self.scopes = self._determine_scopes()
def _determine_scopes(self):
"""Determines the unique scopes present across all variables."""
scopes = set()
for var in self.variables:
var_scopes = self.data[var].keys()
scopes.update(scope.lower() for scope in var_scopes)
return sorted(list(scopes))
def get_raw_data(self):
"""
Returns the raw data.
Returns:
- The raw data as provided during initialization.
"""
return self.data
def get_steps_with_value(self, value, within_list=False):
"""
Retrieves the steps where the variable equals the specified value.
Parameters:
- value: The value to search for.
- within_list (bool): If True, searches for the value within list-type variable values.
Returns:
- If single variable:
- A dict with scopes as keys and lists of step indices or values as values.
- If multiple variables:
- A dict mapping each variable name to their respective scope-step/value mappings.
"""
result = {}
for var in self.variables:
var_result = {}
for scope, occurrences in self.data[var].items():
if is_scalar(occurrences):
# Scalar value
if within_list:
continue # Cannot search within a scalar
if occurrences == value:
var_result[scope] = occurrences
elif isinstance(occurrences, list):
# List of values or list of tuples
steps = []
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
step, val = item
if within_list:
if isinstance(val, list) and value in val:
steps.append(step)
else:
if val == value:
steps.append(step)
else:
# List of values
if within_list:
if isinstance(item, list) and value in item:
steps.append(item)
else:
if item == value:
steps.append(item)
if steps:
var_result[scope] = steps
else:
# Other types (e.g., dict), can be extended as needed
pass
if var_result:
result[var] = var_result
return result
def get_all_values(self):
"""
Retrieves all unique values of the variable(s).
Returns:
- A dict mapping each variable to its set of unique values per scope.
"""
result = {}
for var in self.variables:
var_unique = {}
for scope, occurrences in self.data[var].items():
unique_vals = set()
if is_scalar(occurrences):
unique_vals.add(occurrences)
elif isinstance(occurrences, list):
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
_, val = item
hashable_val = make_hashable(val)
unique_vals.add(hashable_val)
else:
hashable_val = make_hashable(item)
unique_vals.add(hashable_val)
var_unique[scope] = unique_vals
result[var] = var_unique
return result
def get_all_elements_in_lists(self):
"""
Retrieves all unique elements within list-type variable values.
Returns:
- A dict mapping each variable to its set of unique elements in lists per scope.
"""
result = {}
for var in self.variables:
var_elements = {}
for scope, occurrences in self.data[var].items():
unique_elems = set()
if is_scalar(occurrences):
if isinstance(occurrences, list):
unique_elems.update(occurrences)
elif isinstance(occurrences, list):
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
_, val = item
if isinstance(val, list):
unique_elems.update(val)
elif isinstance(item, list):
unique_elems.update(item)
var_elements[scope] = unique_elems
result[var] = var_elements
return result
def get_usage_count(self, value, within_list=False):
"""
Counts how many times a specific value is used.
Parameters:
- value: The value to count.
- within_list (bool): If True, counts occurrences within list-type variable values.
Returns:
- If single variable:
- A dict with scopes as keys and integer counts or counts of values as values.
- If multiple variables:
- A dict mapping each variable name to their respective scope-count/value mappings.
"""
result = {}
for var in self.variables:
var_count = {}
for scope, occurrences in self.data[var].items():
count = 0
if is_scalar(occurrences):
if within_list:
if isinstance(occurrences, list) and value in occurrences:
count += 1
else:
if occurrences == value:
count += 1
elif isinstance(occurrences, list):
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
_, val = item
if within_list:
if isinstance(val, list) and value in val:
count += 1
else:
if val == value:
count += 1
else:
if within_list:
if isinstance(item, list) and value in item:
count += 1
else:
if item == value:
count += 1
if count > 0:
var_count[scope] = count
if var_count:
result[var] = var_count
return result
def get_steps_with_value_in_scope(self, variable, scope, value, within_list=False):
"""
Retrieves the steps within a specific scope where the variable equals the specified value.
Parameters:
- variable (str): The variable name.
- scope (str): The scope to search within ("static", "global", "local").
- value: The value to search for.
- within_list (bool): If True, searches for the value within list-type variable values.
Returns:
- A list of step indices or values where the variable equals the value within the specified scope.
"""
if variable not in self.variables:
raise ValueError(f"Variable '{variable}' is not managed by this instance.")
scope = scope.lower()
occurrences = self.data[variable].get(scope, [])
steps = []
if is_scalar(occurrences):
if within_list:
if isinstance(occurrences, list) and value in occurrences:
steps.append(occurrences)
else:
if occurrences == value:
steps.append(occurrences)
elif isinstance(occurrences, list):
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
step, val = item
if within_list:
if isinstance(val, list) and value in val:
steps.append(step)
else:
if val == value:
steps.append(step)
else:
if within_list:
if isinstance(item, list) and value in item:
steps.append(item)
else:
if item == value:
steps.append(item)
return steps
def summarize(self):
"""
Provides a summary of the variable occurrences.
Returns:
- A dict mapping each variable to their respective summaries per scope.
"""
summary = {}
for var in self.variables:
var_summary = {}
for scope, occurrences in self.data[var].items():
unique_vals = set()
unique_elements = set()
value_counts = defaultdict(int)
element_counts = defaultdict(int)
if is_scalar(occurrences):
# Scalar value
hashable_val = make_hashable(occurrences)
unique_vals.add(hashable_val)
value_counts[hashable_val] += 1
if isinstance(occurrences, list):
unique_elements.update(occurrences)
for elem in occurrences:
element_counts[elem] += 1
elif isinstance(occurrences, list):
for item in occurrences:
if isinstance(item, tuple) and len(item) == 2:
# Tuple: (step, value)
step, val = item
hashable_val = make_hashable(val)
unique_vals.add(hashable_val)
value_counts[hashable_val] += 1
if isinstance(val, list):
unique_elements.update(val)
for elem in val:
element_counts[elem] += 1
elif isinstance(val, dict):
# Handle nested dictionaries if necessary
for sub_val in val.values():
if isinstance(sub_val, list):
unique_elements.update(sub_val)
for elem in sub_val:
element_counts[elem] += 1
else:
# Direct value
hashable_val = make_hashable(item)
unique_vals.add(hashable_val)
value_counts[hashable_val] += 1
if isinstance(item, list):
unique_elements.update(item)
for elem in item:
element_counts[elem] += 1
elif isinstance(item, dict):
for sub_val in item.values():
if isinstance(sub_val, list):
unique_elements.update(sub_val)
for elem in sub_val:
element_counts[elem] += 1
else:
# Other types can be handled here if needed
pass
var_summary[scope] = {
"total_occurrences": len(occurrences),
"unique_values": unique_vals,
"unique_elements_in_lists": unique_elements,
"value_counts": dict(value_counts),
"element_counts_in_lists": dict(element_counts)
}
summary[var] = var_summary
return summary
def export(self, filename=None, scopes='all', variables='all', include_headers=True, return_content=False):
"""
Exports the variable occurrences to a file or returns the content as a string.
Parameters:
- filename (str, optional): Path to the output file. Must end with .md, .txt, or .html. Required if return_content is False.
- scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'.
- variables (str or list/tuple, optional): 'all', a single variable string, or a list of variable strings. Defaults to 'all'.
- include_headers (bool, optional): If True, includes headers in the exported content. Defaults to True.
- return_content (bool, optional): If True, returns the content as a string instead of writing to a file. Defaults to False.
Returns:
- str: The exported content as a string if return_content is True.
- None: Writes to file if return_content is False.
Raises:
- ValueError: If 'filename' is not provided when return_content is False.
- ValueError: If 'scopes' or 'variables' are of incorrect types.
"""
# Determine file extension if filename is provided
if filename:
_, ext = os.path.splitext(filename)
ext = ext.lower()
if ext in ['.md', '.txt']:
export_format = 'markdown'
elif ext == '.html':
export_format = 'html'
else:
raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.")
elif not return_content:
raise ValueError("Filename must be provided if return_content is False.")
# Determine scopes
if isinstance(scopes, str):
if scopes.lower() == 'all':
selected_scopes = self.scopes
else:
selected_scopes = [scopes.lower()]
elif isinstance(scopes, (list, tuple)):
scopes_lower = [s.lower() for s in scopes]
if 'all' in scopes_lower:
selected_scopes = self.scopes
else:
selected_scopes = scopes_lower
else:
raise ValueError("Parameter 'scopes' must be a string or a list/tuple of strings.")
# Determine variables
if isinstance(variables, str):
if variables.lower() == 'all':
selected_variables = self.variables
else:
if variables not in self.variables:
print(f"Warning: Variable '{variables}' not managed by this instance.")
selected_variables = []
else:
selected_variables = [variables]
elif isinstance(variables, (list, tuple)):
variables_lower = [v for v in variables]
selected_variables = [v for v in variables_lower if v in self.variables]
missing_vars = set(variables_lower) - set(selected_variables)
if missing_vars:
print(f"Warning: Variables '{', '.join(missing_vars)}' not managed by this instance.")
else:
raise ValueError("Parameter 'variables' must be a string or a list/tuple of strings.")
# Generate content
content = ""
if include_headers:
if export_format == 'markdown':
content += f"## Variable: `{self.variables[0]}`\n\n" if len(self.variables) == 1 else ""
elif export_format == 'html':
content += f"<h2>Variable: {self.variables[0]}</h2>\n" if len(self.variables) == 1 else ""
summary = self.summarize()
for var in selected_variables:
if include_headers:
if export_format == 'markdown':
content += f"### Variable: `{var}`\n\n"
elif export_format == 'html':
content += f"<h3>Variable: {var}</h3>\n"
var_summary = summary[var]
for scope, details in var_summary.items():
if scope not in selected_scopes:
continue
if include_headers or True:
if export_format == 'markdown':
content += f"#### Scope: {scope.capitalize()}\n\n"
elif export_format == 'html':
content += f"<h4>Scope: {scope.capitalize()}</h4>\n"
# Add content based on format
if export_format == 'markdown':
content += f"- **Total Occurrences**: {details['total_occurrences']}\n"
unique_vals_formatted = ', '.join(map(str, details['unique_values']))
content += f"- **Unique Values**: {unique_vals_formatted}\n"
if details['unique_elements_in_lists']:
unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists']))
content += f"- **Unique Elements in Lists**: {unique_elems_formatted}\n\n"
# Element Counts Table
content += "**Element Counts in Lists:**\n\n"
content += "| Element | Count |\n"
content += "|---------|-------|\n"
for elem, count in details['element_counts_in_lists'].items():
content += f"| {elem} | {count} |\n"
content += "\n"
elif export_format == 'html':
content += "<ul>"
content += f"<li><strong>Total Occurrences</strong>: {details['total_occurrences']}</li>"
unique_vals_formatted = ', '.join(map(str, details['unique_values']))
content += f"<li><strong>Unique Values</strong>: {unique_vals_formatted}</li>"
if details['unique_elements_in_lists']:
unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists']))
content += f"<li><strong>Unique Elements in Lists</strong>: {unique_elems_formatted}</li>"
content += "</ul>\n"
if details['element_counts_in_lists']:
content += "<h5>Element Counts in Lists:</h5>\n"
content += "<table>\n<tr><th>Element</th><th>Count</th></tr>\n"
for elem, count in details['element_counts_in_lists'].items():
content += f"<tr><td>{elem}</td><td>{count}</td></tr>\n"
content += "</table>\n"
# Add a horizontal line between variables
if include_headers and len(selected_variables) > 1:
if export_format == 'markdown':
content += "\n---\n\n"
elif export_format == 'html':
content += "<hr/>\n"
# Handle format-specific headers
if return_content:
if export_format == 'markdown':
return content
elif export_format == 'html':
return f"<html><head><meta charset='UTF-8'><title>Variable Report</title></head><body>{content}</body></html>"
else:
if not filename:
raise ValueError("Filename must be provided if return_content is False.")
# Determine export format based on file extension
if ext in ['.md', '.txt']:
export_format = 'markdown'
elif ext == '.html':
export_format = 'html'
else:
raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.")
# Prepare full content for HTML
if export_format == 'html' and not include_headers:
full_content = f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Variable Report</title>\n</head>\n<body>\n{content}\n</body>\n</html>"
else:
full_content = content
# Write to file
try:
with open(filename, 'w', encoding='utf-8') as file:
file.write(full_content)
print(f"Report successfully generated at '{filename}'.")
except Exception as e:
raise Exception(f"Failed to write the report to '{filename}': {e}")
# %% 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.
Known Issues for indexed variables
----------------------------------
List and tupples used indexed in templates, such as varlist[1], are not converted into strings so that
each element can be considered as a variable and accessed as ${varlist[1]}. If varlist is also used in
full, such as ${varlist}. Then it is preferable to define varlist as a string:
"![v1,v2,...]" where v1,v2,v3 can be int, float, static str or evaluable str (i.e., subjected to nested
evaluation).
The control character ! in lists, such as in var=![v1,v2,v3] preserves the Pythonic definition by do()
at later stages during the evaluation so that its content can be indexed.
"""
printflag = self.printflag if printflag is None else printflag
verbose = self.verbose if verbose is None else verbose
inputs = self.DEFINITIONS + self.USER
usedvariables = self.detect_variables(with_index=False,only_indexed=False)
variables_used_with_index = self.detect_variables(with_index=False,only_indexed=True)
usedvariables_withoutindex = [ var for var in usedvariables if var not in variables_used_with_index ]
for k in inputs.keys():
if k in usedvariables_withoutindex:
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(f"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, with_index=False, only_indexed=False):
"""
Detects variables in the content of the template using an extended pattern
to include indexed variables (e.g., ${var[i]}) if `with_index` is True.
Parameters:
-----------
with_index : bool, optional
If True, include indexed variables with their indices (e.g., ${var[i]}). Default is False.
only_indexed : bool, optional
If True, target only variables that are used with an index (e.g., ${var[i]}). Default is False.
Returns:
--------
list
A list of unique variable names detected in the content based on the flags.
"""
# Regular expression to match variables with optional indexing
variable_pattern = re.compile(r'\$\{(\w+)(\[\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 = set()
for line in lines:
matches = variable_pattern.findall(line)
for match in matches:
variable_name = match[0] # Base variable name
index = match[1] # Optional index (e.g., '[i]')
if only_indexed and not index:
continue # Skip non-indexed variables if targeting only indexed ones
if with_index and index:
detected_vars.add(f"{variable_name}{index}") # Include the full indexed variable
elif not with_index:
detected_vars.add(variable_name) # Include only the base variable
# 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
header(self, verbose=True,verbosity=None, style=4):
Generate a formatted header for the pipescript file.
list_values(self, varname, what="all"):
List all occurrences and values of a variable across the pipeline scripts.
list_multiple_values(self, varnames, what="all"):
List all occurrences and values of multiple variables across the pipeline scripts.
plot_value_distribution(self, varname, what="all"):
Plot the distribution of values for a given variable across specified scopes.
generate_report(self, filename, varnames=None, scopes="all"):
Generate a comprehensive report for specified variables and writes it to a file.
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 """
from pizza.dscript import dscript
if isinstance(s,(pipescript,script)):
dup = deepduplicate(self)
return dup | s # + or | are synonyms
elif isinstance(s,scriptobject):
return self + s.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity)
elif isinstance(s,dscript):
return self + s.pipescript(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity)
else:
raise TypeError(f"The operand should be a pipescript/script/dscript/scriptobjectgroup and not '{type(s).__name__}'")
def __iadd__(self,s):
""" overload += as pipe without copy """
if isinstance(s,pipescript):
return self | s # + or | are synonyms
else:
raise TypeError(f"The operand should be a pipescript and not '{type(s).__name__}'")
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(f"The operand should be a pipescript and not '{type(s).__name__}'")
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)):
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, 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, clean="fixing", **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.
clean : "fixing" or "removing"
- 'removing': Completely remove the empty step from TEMPLATE.
- 'fixing': Replace the content of the empty step with a comment.
**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],
autorefresh=False # prevent the replacement by default values ${}
)
# 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)
# Clean the entries for empty templates
outd.clean(verbose=verbose,behavior=clean)
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)
def list_values(self, varname=None, what="all"):
"""
Lists all occurrences and values of a specified variable or all variables across the pipeline scripts.
Parameters:
- varname (str, optional): The name of the variable to search for. If None, all variables are listed.
- what (str or list/tuple, optional): Specifies the scopes to search in.
Can be "static", "global", "local", "all",
or a list/tuple of any combination of these.
Returns:
- VariableOccurrences: If varname is specified, returns a VariableOccurrences instance for that variable.
- dict of VariableOccurrences: If varname is None, returns a dictionary mapping each variable name to its VariableOccurrences instance.
"""
# Normalize 'what' to a list for uniform processing
if isinstance(what, str):
if what.lower() == "all":
scopes = ["static", "global", "local"]
else:
scopes = [what.lower()]
elif isinstance(what, (list, tuple)):
scopes_lower = [s.lower() for s in what]
if 'all' in scopes_lower:
scopes = ["static", "global", "local"]
else:
scopes = scopes_lower
else:
raise ValueError("Parameter 'what' must be a string or a list/tuple of strings.")
# Initialize data structures
if varname:
# Single variable case
if len(scopes) == 1:
data = []
else:
data = {}
for scope in scopes:
data[scope] = []
# Iterate over each script in the pipeline
for i, script in enumerate(self.listscript):
# Retrieve variables for each scope
static_vars = self.listUSER[i] if i < len(self.listUSER) else {}
global_vars = getattr(script, 'DEFINITIONS', {})
local_vars = getattr(script, 'USER', {})
scope_vars = {
"static": static_vars,
"global": global_vars,
"local": local_vars
}
# Check each requested scope
for scope in scopes:
vars_dict = scope_vars.get(scope, {})
if varname in vars_dict.keys():
value = getattr(vars_dict,varname)
if len(scopes) == 1:
data.append((i, value))
else:
data[scope].append((i, value))
# Return a VariableOccurrences instance for the specified variable
return VariableOccurrences(data, variables=varname)
else:
# All variables case
all_vars = set()
# First, collect all variable names across specified scopes and scripts
for i, script in enumerate(self.listscript):
# Retrieve variables for each scope
static_vars = self.listUSER[i] if i < len(self.listUSER) else {}
global_vars = getattr(script, 'DEFINITIONS', {})
local_vars = getattr(script, 'USER', {})
scope_vars = {
"static": static_vars,
"global": global_vars,
"local": local_vars
}
for scope in scopes:
vars_dict = scope_vars.get(scope, {})
all_vars.update(vars_dict.keys())
# Initialize a dictionary to hold VariableOccurrences for each variable
variables_data = {}
for var in all_vars:
var_data = {}
for scope in scopes:
var_data[scope] = []
variables_data[var] = var_data
# Iterate again to populate the data for each variable
for i, script in enumerate(self.listscript):
# Retrieve variables for each scope
static_vars = self.listUSER[i] if i < len(self.listUSER) else {}
global_vars = getattr(script, 'DEFINITIONS', {})
local_vars = getattr(script, 'USER', {})
scope_vars = {
"static": static_vars,
"global": global_vars,
"local": local_vars
}
for scope in scopes:
vars_dict = scope_vars.get(scope, {})
for var, value in vars_dict.items():
variables_data[var][scope].append((i, value))
# Convert each variable's data into a VariableOccurrences instance
variables_occurrences = {}
for var, data in variables_data.items():
variables_occurrences[var] = VariableOccurrences(data, variables=var)
return variables_occurrences
def list_multiple_values(self, varnames, what="all"):
"""
Lists all occurrences and values of multiple variables across the pipeline scripts.
Parameters:
- varnames (list): A list of variable names to search for.
- what (str or list/tuple): Specifies the scopes to search in.
Can be "static", "global", "local", "all",
or a list/tuple of any combination of these.
Returns:
- dict: A dictionary mapping each variable name to its VariableOccurrences object.
"""
if not isinstance(varnames, (list, tuple)):
raise ValueError("Parameter 'varnames' must be a list or tuple of strings.")
return self.list_values(varname=varnames, what=what)
def plot_multiple_value_distributions(self, varnames, what="all", separate_plots=True):
"""
Plots the distribution of elements for multiple variables across specified scopes.
Parameters:
- varnames (list): A list of variable names to plot.
- what (str or list/tuple): Specifies the scopes to include in the plot.
Can be "static", "global", "local", "all",
or a list/tuple of any combination of these.
- separate_plots (bool): If True, plots each variable in a separate subplot.
If False, combines all variables in a single plot for comparison.
"""
if not isinstance(varnames, (list, tuple)):
raise ValueError("Parameter 'varnames' must be a list or tuple of strings.")
# Retrieve VariableOccurrences instances
multiple_vars = self.list_multiple_values(varnames, what=what)
if separate_plots:
num_vars = len(multiple_vars)
fig, axes = plt.subplots(num_vars, 1, figsize=(10, 5 * num_vars))
if num_vars == 1:
axes = [axes] # Make it iterable
for ax, (var, vo) in zip(axes, multiple_vars.items()):
summary = vo.summarize()[var]
for scope, details in summary.items():
elements = list(details.get('unique_elements_in_lists', []))
counts = [details['element_counts_in_lists'][elem] for elem in elements]
ax.bar(elements, counts, label=scope)
ax.set_xlabel('Element')
ax.set_ylabel('Count')
ax.set_title(f"Distribution of elements in '{var}'")
ax.legend()
plt.tight_layout()
plt.show()
else:
plt.figure(figsize=(12, 8))
for var, vo in multiple_vars.items():
summary = vo.summarize()[var]
for scope, details in summary.items():
elements = list(details.get('unique_elements_in_lists', []))
counts = [details['element_counts_in_lists'][elem] for elem in elements]
plt.bar([f"{var}_{elem}" for elem in elements], counts, label=f"{var} - {scope}")
plt.xlabel('Element')
plt.ylabel('Count')
plt.title("Distribution of elements in multiple variables")
plt.legend()
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.show()
def generate_report(self, filename, varnames=None, scopes="all"):
"""
Generates a comprehensive report for specified variables and writes it to a file.
Parameters:
- filename (str): Path to the output report file. Must end with .md, .txt, or .html.
- varnames (str or list/tuple, optional): Variable name(s) to include in the report. Defaults to 'all'.
- scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'.
Raises:
- ValueError: If 'filename' has an unsupported extension.
- Exception: For other unforeseen errors.
"""
# Validate filename extension
_, ext = os.path.splitext(filename)
ext = ext.lower()
if ext not in ['.md', '.txt', '.html']:
raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.")
# Determine format based on extension
if ext in ['.md', '.txt']:
export_format = 'markdown'
elif ext == '.html':
export_format = 'html'
# Determine variables to include
if varnames is None or (isinstance(varnames, (list, tuple)) and len(varnames) == 0):
variables = 'all'
else:
variables = varnames # Can be a string or a list/tuple
# Retrieve VariableOccurrences instances
if variables == 'all':
variables_occurrences = self.list_values(varname=None, what=scopes)
else:
# Normalize varnames to a list
if isinstance(variables, str):
variables = [variables]
elif isinstance(variables, (list, tuple)):
variables = list(variables)
else:
raise ValueError("Parameter 'varnames' must be a string or a list/tuple of strings.")
variables_occurrences = {}
for var in variables:
vo = self.list_values(varname=var, what=scopes)
if vo and var in vo.variables:
variables_occurrences[var] = vo
else:
print(f"Warning: Variable '{var}' not found in the specified scopes.")
# Initialize report content
report_content = ""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
caller = "generate_report"
# Add report header
if export_format == 'markdown':
report_content += f"# Comprehensive Variable Report\n\n"
report_content += f"**Generated on {timestamp} by {caller}**\n\n"
elif export_format == 'html':
# Define CSS for HTML
css = """
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
h1, h2, h3, h4, h5 {
color: #333;
}
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 40px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #4CAF50;
color: white;
}
tr:nth-child(even){background-color: #f2f2f2;}
tr:hover {background-color: #ddd;}
</style>
"""
report_content += f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Comprehensive Variable Report</title>\n{css}\n</head>\n<body>\n"
report_content += f"<h1>Comprehensive Variable Report</h1>\n"
report_content += f"<h2>Generated on {timestamp} by {caller}</h2>\n"
# Assemble report content using VariableOccurrences.export()
for var, vo in variables_occurrences.items():
# Export content without headers and get as string
var_content = vo.export(filename=filename, # use to identify the format based on its extension
scopes=scopes,
variables=var,
include_headers=False,
return_content=True)
if export_format == 'markdown':
# Add variable header
report_content += f"## Variable: `{var}`\n\n"
report_content += var_content + "\n\n"
report_content += "---\n\n" # Horizontal line between variables
elif export_format == 'html':
# Add variable header
report_content += f"<h2>Variable: {var}</h2>\n"
report_content += var_content + "\n<hr/>\n" # Horizontal line between variables
# Finalize HTML content
if export_format == 'html':
report_content += "</body>\n</html>"
# Write report to file
try:
with open(filename, 'w', encoding='utf-8') as file:
file.write(report_content)
print(f"Report successfully generated at '{filename}'.")
except Exception as e:
raise Exception(f"Failed to write the report to '{filename}': {e}")
# %% 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 is_scalar(val)
-
Determines if a value is scalar (not a list, dict, or tuple).
Expand source code
def is_scalar(val): """ Determines if a value is scalar (not a list, dict, or tuple). """ return not isinstance(val, (list, dict, tuple))
def make_hashable(val)
-
Recursively converts lists and dictionaries to tuples to make them hashable.
Expand source code
def make_hashable(val): """ Recursively converts lists and dictionaries to tuples to make them hashable. """ if isinstance(val, list): return tuple(make_hashable(item) for item in val) elif isinstance(val, dict): return tuple(sorted((k, make_hashable(v)) for k, v in val.items())) return val
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 VariableOccurrences (data, variables=None)
-
The
VariableOccurrences
class encapsulates raw data and provides utility methods for analyzing variable occurrences across various scopes. It is designed to handle both single and multiple variables, supporting flexible data structures and analysis operations.Class Methods:
__init__(data, variables=None)
: Initializes the class with raw data and optional variable names._determine_scopes()
: Determines the unique scopes present across all variables.get_raw_data()
: Returns the raw data as provided during initialization.get_steps_with_value(value, within_list=False)
: Retrieves steps where the variable equals the specified value.get_all_values()
: Retrieves all unique values of the variable(s).get_all_elements_in_lists()
: Retrieves all unique elements within list-type variable values.get_usage_count(value, within_list=False)
: Counts occurrences of a specific value.get_steps_with_value_in_scope(variable, scope, value, within_list=False)
: Retrieves steps where a variable equals a value within a specific scope.summarize()
: Summarizes the occurrences of variables, including counts and unique elements.export(filename=None, scopes='all', variables='all', include_headers=True, return_content=False)
: Exports variable occurrences as Markdown, plain text, or HTML.
Attributes:
data
: Dictionary containing the raw variable data.- Single variable:
{'scope1': [...], 'scope2': [...]}
. - Multiple variables:
{'var1': {...}, 'var2': {...}, ...}
. variables
: List of variable names managed by the instance.scopes
: List of unique scopes across all variables.
Usage:
The class is useful for tracking and analyzing variable values across different contexts, such as configuration files, programming environments, or simulation data. It supports advanced querying, summary generation, and export functionality.
Example:
Initialize with single variable
data = {'global': [(0, 1), (1, 2)], 'local': [(0, 3), (1, 4)]} vo = VariableOccurrences(data, variables="var1")
Initialize with multiple variables
data = { "var1": {"global": [(0, 1), (1, 2)], "local": [(0, 3), (1, 4)]}, "var2": {"global": [1, 2], "local": [3, 4]}, } vo = VariableOccurrences(data)
Query steps where a value is present
steps = vo.get_steps_with_value(2)
Export data to a Markdown file
vo.export("output.md")
Initializes the VariableOccurrences object.
Parameters: - data: - For single variable: dict with scopes as keys and lists of values or a single value. - For multiple variables: dict mapping variable names to their respective scope data. - variables (str or list/tuple, optional): - If single variable: string representing the variable name. - If multiple variables: list or tuple of variable names. - If None: assumes data is for multiple variables without specifying names.
Expand source code
class VariableOccurrences: """ The `VariableOccurrences` class encapsulates raw data and provides utility methods for analyzing variable occurrences across various scopes. It is designed to handle both single and multiple variables, supporting flexible data structures and analysis operations. Class Methods: -------------- - `__init__(data, variables=None)`: Initializes the class with raw data and optional variable names. - `_determine_scopes()`: Determines the unique scopes present across all variables. - `get_raw_data()`: Returns the raw data as provided during initialization. - `get_steps_with_value(value, within_list=False)`: Retrieves steps where the variable equals the specified value. - `get_all_values()`: Retrieves all unique values of the variable(s). - `get_all_elements_in_lists()`: Retrieves all unique elements within list-type variable values. - `get_usage_count(value, within_list=False)`: Counts occurrences of a specific value. - `get_steps_with_value_in_scope(variable, scope, value, within_list=False)`: Retrieves steps where a variable equals a value within a specific scope. - `summarize()`: Summarizes the occurrences of variables, including counts and unique elements. - `export(filename=None, scopes='all', variables='all', include_headers=True, return_content=False)`: Exports variable occurrences as Markdown, plain text, or HTML. Attributes: ----------- - `data`: Dictionary containing the raw variable data. - Single variable: `{'scope1': [...], 'scope2': [...]}`. - Multiple variables: `{'var1': {...}, 'var2': {...}, ...}`. - `variables`: List of variable names managed by the instance. - `scopes`: List of unique scopes across all variables. Usage: ------ The class is useful for tracking and analyzing variable values across different contexts, such as configuration files, programming environments, or simulation data. It supports advanced querying, summary generation, and export functionality. Example: -------- # Initialize with single variable data = {'global': [(0, 1), (1, 2)], 'local': [(0, 3), (1, 4)]} vo = VariableOccurrences(data, variables="var1") # Initialize with multiple variables data = { "var1": {"global": [(0, 1), (1, 2)], "local": [(0, 3), (1, 4)]}, "var2": {"global": [1, 2], "local": [3, 4]}, } vo = VariableOccurrences(data) # Query steps where a value is present steps = vo.get_steps_with_value(2) # Export data to a Markdown file vo.export("output.md") """ def __init__(self, data, variables=None): """ Initializes the VariableOccurrences object. Parameters: - data: - For single variable: dict with scopes as keys and lists of values or a single value. - For multiple variables: dict mapping variable names to their respective scope data. - variables (str or list/tuple, optional): - If single variable: string representing the variable name. - If multiple variables: list or tuple of variable names. - If None: assumes data is for multiple variables without specifying names. """ if variables is None: # Assume data is a dict mapping variable names to their scope data if not isinstance(data, dict): raise ValueError("For multiple variables, data must be a dict mapping variable names to their scope data.") self.variables = list(data.keys()) self.data = data # {var1: {scope1: [...], scope2: [...]}, var2: {...}, ...} elif isinstance(variables, str): # Single variable case self.variables = [variables] if not isinstance(data, dict): raise ValueError("For single variable, data must be a dict with scopes as keys and lists of values or single values as values.") self.data = {variables: data} # {var: {scope1: [...], scope2: [...], ...}} elif isinstance(variables, (list, tuple)): # Multiple variables specified if not isinstance(data, dict): raise ValueError("For multiple variables, data must be a dict mapping variable names to their scope data.") if set(variables) > set(data.keys()): missing_vars = set(variables) - set(data.keys()) raise ValueError(f"Data does not contain entries for variables: {', '.join(missing_vars)}") self.variables = list(variables) self.data = {var: data[var] for var in variables} # {var1: {...}, var2: {...}, ...} else: raise ValueError("Parameter 'variables' must be a string, list, tuple, or None.") self.scopes = self._determine_scopes() def _determine_scopes(self): """Determines the unique scopes present across all variables.""" scopes = set() for var in self.variables: var_scopes = self.data[var].keys() scopes.update(scope.lower() for scope in var_scopes) return sorted(list(scopes)) def get_raw_data(self): """ Returns the raw data. Returns: - The raw data as provided during initialization. """ return self.data def get_steps_with_value(self, value, within_list=False): """ Retrieves the steps where the variable equals the specified value. Parameters: - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values. Returns: - If single variable: - A dict with scopes as keys and lists of step indices or values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-step/value mappings. """ result = {} for var in self.variables: var_result = {} for scope, occurrences in self.data[var].items(): if is_scalar(occurrences): # Scalar value if within_list: continue # Cannot search within a scalar if occurrences == value: var_result[scope] = occurrences elif isinstance(occurrences, list): # List of values or list of tuples steps = [] for item in occurrences: if isinstance(item, tuple) and len(item) == 2: step, val = item if within_list: if isinstance(val, list) and value in val: steps.append(step) else: if val == value: steps.append(step) else: # List of values if within_list: if isinstance(item, list) and value in item: steps.append(item) else: if item == value: steps.append(item) if steps: var_result[scope] = steps else: # Other types (e.g., dict), can be extended as needed pass if var_result: result[var] = var_result return result def get_all_values(self): """ Retrieves all unique values of the variable(s). Returns: - A dict mapping each variable to its set of unique values per scope. """ result = {} for var in self.variables: var_unique = {} for scope, occurrences in self.data[var].items(): unique_vals = set() if is_scalar(occurrences): unique_vals.add(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item hashable_val = make_hashable(val) unique_vals.add(hashable_val) else: hashable_val = make_hashable(item) unique_vals.add(hashable_val) var_unique[scope] = unique_vals result[var] = var_unique return result def get_all_elements_in_lists(self): """ Retrieves all unique elements within list-type variable values. Returns: - A dict mapping each variable to its set of unique elements in lists per scope. """ result = {} for var in self.variables: var_elements = {} for scope, occurrences in self.data[var].items(): unique_elems = set() if is_scalar(occurrences): if isinstance(occurrences, list): unique_elems.update(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item if isinstance(val, list): unique_elems.update(val) elif isinstance(item, list): unique_elems.update(item) var_elements[scope] = unique_elems result[var] = var_elements return result def get_usage_count(self, value, within_list=False): """ Counts how many times a specific value is used. Parameters: - value: The value to count. - within_list (bool): If True, counts occurrences within list-type variable values. Returns: - If single variable: - A dict with scopes as keys and integer counts or counts of values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-count/value mappings. """ result = {} for var in self.variables: var_count = {} for scope, occurrences in self.data[var].items(): count = 0 if is_scalar(occurrences): if within_list: if isinstance(occurrences, list) and value in occurrences: count += 1 else: if occurrences == value: count += 1 elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item if within_list: if isinstance(val, list) and value in val: count += 1 else: if val == value: count += 1 else: if within_list: if isinstance(item, list) and value in item: count += 1 else: if item == value: count += 1 if count > 0: var_count[scope] = count if var_count: result[var] = var_count return result def get_steps_with_value_in_scope(self, variable, scope, value, within_list=False): """ Retrieves the steps within a specific scope where the variable equals the specified value. Parameters: - variable (str): The variable name. - scope (str): The scope to search within ("static", "global", "local"). - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values. Returns: - A list of step indices or values where the variable equals the value within the specified scope. """ if variable not in self.variables: raise ValueError(f"Variable '{variable}' is not managed by this instance.") scope = scope.lower() occurrences = self.data[variable].get(scope, []) steps = [] if is_scalar(occurrences): if within_list: if isinstance(occurrences, list) and value in occurrences: steps.append(occurrences) else: if occurrences == value: steps.append(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: step, val = item if within_list: if isinstance(val, list) and value in val: steps.append(step) else: if val == value: steps.append(step) else: if within_list: if isinstance(item, list) and value in item: steps.append(item) else: if item == value: steps.append(item) return steps def summarize(self): """ Provides a summary of the variable occurrences. Returns: - A dict mapping each variable to their respective summaries per scope. """ summary = {} for var in self.variables: var_summary = {} for scope, occurrences in self.data[var].items(): unique_vals = set() unique_elements = set() value_counts = defaultdict(int) element_counts = defaultdict(int) if is_scalar(occurrences): # Scalar value hashable_val = make_hashable(occurrences) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(occurrences, list): unique_elements.update(occurrences) for elem in occurrences: element_counts[elem] += 1 elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: # Tuple: (step, value) step, val = item hashable_val = make_hashable(val) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(val, list): unique_elements.update(val) for elem in val: element_counts[elem] += 1 elif isinstance(val, dict): # Handle nested dictionaries if necessary for sub_val in val.values(): if isinstance(sub_val, list): unique_elements.update(sub_val) for elem in sub_val: element_counts[elem] += 1 else: # Direct value hashable_val = make_hashable(item) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(item, list): unique_elements.update(item) for elem in item: element_counts[elem] += 1 elif isinstance(item, dict): for sub_val in item.values(): if isinstance(sub_val, list): unique_elements.update(sub_val) for elem in sub_val: element_counts[elem] += 1 else: # Other types can be handled here if needed pass var_summary[scope] = { "total_occurrences": len(occurrences), "unique_values": unique_vals, "unique_elements_in_lists": unique_elements, "value_counts": dict(value_counts), "element_counts_in_lists": dict(element_counts) } summary[var] = var_summary return summary def export(self, filename=None, scopes='all', variables='all', include_headers=True, return_content=False): """ Exports the variable occurrences to a file or returns the content as a string. Parameters: - filename (str, optional): Path to the output file. Must end with .md, .txt, or .html. Required if return_content is False. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'. - variables (str or list/tuple, optional): 'all', a single variable string, or a list of variable strings. Defaults to 'all'. - include_headers (bool, optional): If True, includes headers in the exported content. Defaults to True. - return_content (bool, optional): If True, returns the content as a string instead of writing to a file. Defaults to False. Returns: - str: The exported content as a string if return_content is True. - None: Writes to file if return_content is False. Raises: - ValueError: If 'filename' is not provided when return_content is False. - ValueError: If 'scopes' or 'variables' are of incorrect types. """ # Determine file extension if filename is provided if filename: _, ext = os.path.splitext(filename) ext = ext.lower() if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' else: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") elif not return_content: raise ValueError("Filename must be provided if return_content is False.") # Determine scopes if isinstance(scopes, str): if scopes.lower() == 'all': selected_scopes = self.scopes else: selected_scopes = [scopes.lower()] elif isinstance(scopes, (list, tuple)): scopes_lower = [s.lower() for s in scopes] if 'all' in scopes_lower: selected_scopes = self.scopes else: selected_scopes = scopes_lower else: raise ValueError("Parameter 'scopes' must be a string or a list/tuple of strings.") # Determine variables if isinstance(variables, str): if variables.lower() == 'all': selected_variables = self.variables else: if variables not in self.variables: print(f"Warning: Variable '{variables}' not managed by this instance.") selected_variables = [] else: selected_variables = [variables] elif isinstance(variables, (list, tuple)): variables_lower = [v for v in variables] selected_variables = [v for v in variables_lower if v in self.variables] missing_vars = set(variables_lower) - set(selected_variables) if missing_vars: print(f"Warning: Variables '{', '.join(missing_vars)}' not managed by this instance.") else: raise ValueError("Parameter 'variables' must be a string or a list/tuple of strings.") # Generate content content = "" if include_headers: if export_format == 'markdown': content += f"## Variable: `{self.variables[0]}`\n\n" if len(self.variables) == 1 else "" elif export_format == 'html': content += f"<h2>Variable: {self.variables[0]}</h2>\n" if len(self.variables) == 1 else "" summary = self.summarize() for var in selected_variables: if include_headers: if export_format == 'markdown': content += f"### Variable: `{var}`\n\n" elif export_format == 'html': content += f"<h3>Variable: {var}</h3>\n" var_summary = summary[var] for scope, details in var_summary.items(): if scope not in selected_scopes: continue if include_headers or True: if export_format == 'markdown': content += f"#### Scope: {scope.capitalize()}\n\n" elif export_format == 'html': content += f"<h4>Scope: {scope.capitalize()}</h4>\n" # Add content based on format if export_format == 'markdown': content += f"- **Total Occurrences**: {details['total_occurrences']}\n" unique_vals_formatted = ', '.join(map(str, details['unique_values'])) content += f"- **Unique Values**: {unique_vals_formatted}\n" if details['unique_elements_in_lists']: unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists'])) content += f"- **Unique Elements in Lists**: {unique_elems_formatted}\n\n" # Element Counts Table content += "**Element Counts in Lists:**\n\n" content += "| Element | Count |\n" content += "|---------|-------|\n" for elem, count in details['element_counts_in_lists'].items(): content += f"| {elem} | {count} |\n" content += "\n" elif export_format == 'html': content += "<ul>" content += f"<li><strong>Total Occurrences</strong>: {details['total_occurrences']}</li>" unique_vals_formatted = ', '.join(map(str, details['unique_values'])) content += f"<li><strong>Unique Values</strong>: {unique_vals_formatted}</li>" if details['unique_elements_in_lists']: unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists'])) content += f"<li><strong>Unique Elements in Lists</strong>: {unique_elems_formatted}</li>" content += "</ul>\n" if details['element_counts_in_lists']: content += "<h5>Element Counts in Lists:</h5>\n" content += "<table>\n<tr><th>Element</th><th>Count</th></tr>\n" for elem, count in details['element_counts_in_lists'].items(): content += f"<tr><td>{elem}</td><td>{count}</td></tr>\n" content += "</table>\n" # Add a horizontal line between variables if include_headers and len(selected_variables) > 1: if export_format == 'markdown': content += "\n---\n\n" elif export_format == 'html': content += "<hr/>\n" # Handle format-specific headers if return_content: if export_format == 'markdown': return content elif export_format == 'html': return f"<html><head><meta charset='UTF-8'><title>Variable Report</title></head><body>{content}</body></html>" else: if not filename: raise ValueError("Filename must be provided if return_content is False.") # Determine export format based on file extension if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' else: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") # Prepare full content for HTML if export_format == 'html' and not include_headers: full_content = f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Variable Report</title>\n</head>\n<body>\n{content}\n</body>\n</html>" else: full_content = content # Write to file try: with open(filename, 'w', encoding='utf-8') as file: file.write(full_content) print(f"Report successfully generated at '{filename}'.") except Exception as e: raise Exception(f"Failed to write the report to '{filename}': {e}")
Methods
def export(self, filename=None, scopes='all', variables='all', include_headers=True, return_content=False)
-
Exports the variable occurrences to a file or returns the content as a string.
Parameters: - filename (str, optional): Path to the output file. Must end with .md, .txt, or .html. Required if return_content is False. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'. - variables (str or list/tuple, optional): 'all', a single variable string, or a list of variable strings. Defaults to 'all'. - include_headers (bool, optional): If True, includes headers in the exported content. Defaults to True. - return_content (bool, optional): If True, returns the content as a string instead of writing to a file. Defaults to False.
Returns: - str: The exported content as a string if return_content is True. - None: Writes to file if return_content is False.
Raises: - ValueError: If 'filename' is not provided when return_content is False. - ValueError: If 'scopes' or 'variables' are of incorrect types.
Expand source code
def export(self, filename=None, scopes='all', variables='all', include_headers=True, return_content=False): """ Exports the variable occurrences to a file or returns the content as a string. Parameters: - filename (str, optional): Path to the output file. Must end with .md, .txt, or .html. Required if return_content is False. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'. - variables (str or list/tuple, optional): 'all', a single variable string, or a list of variable strings. Defaults to 'all'. - include_headers (bool, optional): If True, includes headers in the exported content. Defaults to True. - return_content (bool, optional): If True, returns the content as a string instead of writing to a file. Defaults to False. Returns: - str: The exported content as a string if return_content is True. - None: Writes to file if return_content is False. Raises: - ValueError: If 'filename' is not provided when return_content is False. - ValueError: If 'scopes' or 'variables' are of incorrect types. """ # Determine file extension if filename is provided if filename: _, ext = os.path.splitext(filename) ext = ext.lower() if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' else: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") elif not return_content: raise ValueError("Filename must be provided if return_content is False.") # Determine scopes if isinstance(scopes, str): if scopes.lower() == 'all': selected_scopes = self.scopes else: selected_scopes = [scopes.lower()] elif isinstance(scopes, (list, tuple)): scopes_lower = [s.lower() for s in scopes] if 'all' in scopes_lower: selected_scopes = self.scopes else: selected_scopes = scopes_lower else: raise ValueError("Parameter 'scopes' must be a string or a list/tuple of strings.") # Determine variables if isinstance(variables, str): if variables.lower() == 'all': selected_variables = self.variables else: if variables not in self.variables: print(f"Warning: Variable '{variables}' not managed by this instance.") selected_variables = [] else: selected_variables = [variables] elif isinstance(variables, (list, tuple)): variables_lower = [v for v in variables] selected_variables = [v for v in variables_lower if v in self.variables] missing_vars = set(variables_lower) - set(selected_variables) if missing_vars: print(f"Warning: Variables '{', '.join(missing_vars)}' not managed by this instance.") else: raise ValueError("Parameter 'variables' must be a string or a list/tuple of strings.") # Generate content content = "" if include_headers: if export_format == 'markdown': content += f"## Variable: `{self.variables[0]}`\n\n" if len(self.variables) == 1 else "" elif export_format == 'html': content += f"<h2>Variable: {self.variables[0]}</h2>\n" if len(self.variables) == 1 else "" summary = self.summarize() for var in selected_variables: if include_headers: if export_format == 'markdown': content += f"### Variable: `{var}`\n\n" elif export_format == 'html': content += f"<h3>Variable: {var}</h3>\n" var_summary = summary[var] for scope, details in var_summary.items(): if scope not in selected_scopes: continue if include_headers or True: if export_format == 'markdown': content += f"#### Scope: {scope.capitalize()}\n\n" elif export_format == 'html': content += f"<h4>Scope: {scope.capitalize()}</h4>\n" # Add content based on format if export_format == 'markdown': content += f"- **Total Occurrences**: {details['total_occurrences']}\n" unique_vals_formatted = ', '.join(map(str, details['unique_values'])) content += f"- **Unique Values**: {unique_vals_formatted}\n" if details['unique_elements_in_lists']: unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists'])) content += f"- **Unique Elements in Lists**: {unique_elems_formatted}\n\n" # Element Counts Table content += "**Element Counts in Lists:**\n\n" content += "| Element | Count |\n" content += "|---------|-------|\n" for elem, count in details['element_counts_in_lists'].items(): content += f"| {elem} | {count} |\n" content += "\n" elif export_format == 'html': content += "<ul>" content += f"<li><strong>Total Occurrences</strong>: {details['total_occurrences']}</li>" unique_vals_formatted = ', '.join(map(str, details['unique_values'])) content += f"<li><strong>Unique Values</strong>: {unique_vals_formatted}</li>" if details['unique_elements_in_lists']: unique_elems_formatted = ', '.join(map(str, details['unique_elements_in_lists'])) content += f"<li><strong>Unique Elements in Lists</strong>: {unique_elems_formatted}</li>" content += "</ul>\n" if details['element_counts_in_lists']: content += "<h5>Element Counts in Lists:</h5>\n" content += "<table>\n<tr><th>Element</th><th>Count</th></tr>\n" for elem, count in details['element_counts_in_lists'].items(): content += f"<tr><td>{elem}</td><td>{count}</td></tr>\n" content += "</table>\n" # Add a horizontal line between variables if include_headers and len(selected_variables) > 1: if export_format == 'markdown': content += "\n---\n\n" elif export_format == 'html': content += "<hr/>\n" # Handle format-specific headers if return_content: if export_format == 'markdown': return content elif export_format == 'html': return f"<html><head><meta charset='UTF-8'><title>Variable Report</title></head><body>{content}</body></html>" else: if not filename: raise ValueError("Filename must be provided if return_content is False.") # Determine export format based on file extension if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' else: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") # Prepare full content for HTML if export_format == 'html' and not include_headers: full_content = f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Variable Report</title>\n</head>\n<body>\n{content}\n</body>\n</html>" else: full_content = content # Write to file try: with open(filename, 'w', encoding='utf-8') as file: file.write(full_content) print(f"Report successfully generated at '{filename}'.") except Exception as e: raise Exception(f"Failed to write the report to '{filename}': {e}")
def get_all_elements_in_lists(self)
-
Retrieves all unique elements within list-type variable values.
Returns: - A dict mapping each variable to its set of unique elements in lists per scope.
Expand source code
def get_all_elements_in_lists(self): """ Retrieves all unique elements within list-type variable values. Returns: - A dict mapping each variable to its set of unique elements in lists per scope. """ result = {} for var in self.variables: var_elements = {} for scope, occurrences in self.data[var].items(): unique_elems = set() if is_scalar(occurrences): if isinstance(occurrences, list): unique_elems.update(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item if isinstance(val, list): unique_elems.update(val) elif isinstance(item, list): unique_elems.update(item) var_elements[scope] = unique_elems result[var] = var_elements return result
def get_all_values(self)
-
Retrieves all unique values of the variable(s).
Returns: - A dict mapping each variable to its set of unique values per scope.
Expand source code
def get_all_values(self): """ Retrieves all unique values of the variable(s). Returns: - A dict mapping each variable to its set of unique values per scope. """ result = {} for var in self.variables: var_unique = {} for scope, occurrences in self.data[var].items(): unique_vals = set() if is_scalar(occurrences): unique_vals.add(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item hashable_val = make_hashable(val) unique_vals.add(hashable_val) else: hashable_val = make_hashable(item) unique_vals.add(hashable_val) var_unique[scope] = unique_vals result[var] = var_unique return result
def get_raw_data(self)
-
Returns the raw data.
Returns: - The raw data as provided during initialization.
Expand source code
def get_raw_data(self): """ Returns the raw data. Returns: - The raw data as provided during initialization. """ return self.data
def get_steps_with_value(self, value, within_list=False)
-
Retrieves the steps where the variable equals the specified value.
Parameters: - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values.
Returns: - If single variable: - A dict with scopes as keys and lists of step indices or values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-step/value mappings.
Expand source code
def get_steps_with_value(self, value, within_list=False): """ Retrieves the steps where the variable equals the specified value. Parameters: - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values. Returns: - If single variable: - A dict with scopes as keys and lists of step indices or values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-step/value mappings. """ result = {} for var in self.variables: var_result = {} for scope, occurrences in self.data[var].items(): if is_scalar(occurrences): # Scalar value if within_list: continue # Cannot search within a scalar if occurrences == value: var_result[scope] = occurrences elif isinstance(occurrences, list): # List of values or list of tuples steps = [] for item in occurrences: if isinstance(item, tuple) and len(item) == 2: step, val = item if within_list: if isinstance(val, list) and value in val: steps.append(step) else: if val == value: steps.append(step) else: # List of values if within_list: if isinstance(item, list) and value in item: steps.append(item) else: if item == value: steps.append(item) if steps: var_result[scope] = steps else: # Other types (e.g., dict), can be extended as needed pass if var_result: result[var] = var_result return result
def get_steps_with_value_in_scope(self, variable, scope, value, within_list=False)
-
Retrieves the steps within a specific scope where the variable equals the specified value.
Parameters: - variable (str): The variable name. - scope (str): The scope to search within ("static", "global", "local"). - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values.
Returns: - A list of step indices or values where the variable equals the value within the specified scope.
Expand source code
def get_steps_with_value_in_scope(self, variable, scope, value, within_list=False): """ Retrieves the steps within a specific scope where the variable equals the specified value. Parameters: - variable (str): The variable name. - scope (str): The scope to search within ("static", "global", "local"). - value: The value to search for. - within_list (bool): If True, searches for the value within list-type variable values. Returns: - A list of step indices or values where the variable equals the value within the specified scope. """ if variable not in self.variables: raise ValueError(f"Variable '{variable}' is not managed by this instance.") scope = scope.lower() occurrences = self.data[variable].get(scope, []) steps = [] if is_scalar(occurrences): if within_list: if isinstance(occurrences, list) and value in occurrences: steps.append(occurrences) else: if occurrences == value: steps.append(occurrences) elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: step, val = item if within_list: if isinstance(val, list) and value in val: steps.append(step) else: if val == value: steps.append(step) else: if within_list: if isinstance(item, list) and value in item: steps.append(item) else: if item == value: steps.append(item) return steps
def get_usage_count(self, value, within_list=False)
-
Counts how many times a specific value is used.
Parameters: - value: The value to count. - within_list (bool): If True, counts occurrences within list-type variable values.
Returns: - If single variable: - A dict with scopes as keys and integer counts or counts of values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-count/value mappings.
Expand source code
def get_usage_count(self, value, within_list=False): """ Counts how many times a specific value is used. Parameters: - value: The value to count. - within_list (bool): If True, counts occurrences within list-type variable values. Returns: - If single variable: - A dict with scopes as keys and integer counts or counts of values as values. - If multiple variables: - A dict mapping each variable name to their respective scope-count/value mappings. """ result = {} for var in self.variables: var_count = {} for scope, occurrences in self.data[var].items(): count = 0 if is_scalar(occurrences): if within_list: if isinstance(occurrences, list) and value in occurrences: count += 1 else: if occurrences == value: count += 1 elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: _, val = item if within_list: if isinstance(val, list) and value in val: count += 1 else: if val == value: count += 1 else: if within_list: if isinstance(item, list) and value in item: count += 1 else: if item == value: count += 1 if count > 0: var_count[scope] = count if var_count: result[var] = var_count return result
def summarize(self)
-
Provides a summary of the variable occurrences.
Returns: - A dict mapping each variable to their respective summaries per scope.
Expand source code
def summarize(self): """ Provides a summary of the variable occurrences. Returns: - A dict mapping each variable to their respective summaries per scope. """ summary = {} for var in self.variables: var_summary = {} for scope, occurrences in self.data[var].items(): unique_vals = set() unique_elements = set() value_counts = defaultdict(int) element_counts = defaultdict(int) if is_scalar(occurrences): # Scalar value hashable_val = make_hashable(occurrences) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(occurrences, list): unique_elements.update(occurrences) for elem in occurrences: element_counts[elem] += 1 elif isinstance(occurrences, list): for item in occurrences: if isinstance(item, tuple) and len(item) == 2: # Tuple: (step, value) step, val = item hashable_val = make_hashable(val) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(val, list): unique_elements.update(val) for elem in val: element_counts[elem] += 1 elif isinstance(val, dict): # Handle nested dictionaries if necessary for sub_val in val.values(): if isinstance(sub_val, list): unique_elements.update(sub_val) for elem in sub_val: element_counts[elem] += 1 else: # Direct value hashable_val = make_hashable(item) unique_vals.add(hashable_val) value_counts[hashable_val] += 1 if isinstance(item, list): unique_elements.update(item) for elem in item: element_counts[elem] += 1 elif isinstance(item, dict): for sub_val in item.values(): if isinstance(sub_val, list): unique_elements.update(sub_val) for elem in sub_val: element_counts[elem] += 1 else: # Other types can be handled here if needed pass var_summary[scope] = { "total_occurrences": len(occurrences), "unique_values": unique_vals, "unique_elements_in_lists": unique_elements, "value_counts": dict(value_counts), "element_counts_in_lists": dict(element_counts) } summary[var] = var_summary return summary
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
- pizza.forcefield.tlsphalone
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, debug=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.
Shorthands for
p=param(...)
s = p.eval()
returns the full evaluated structurep.getval("field")
returns the evaluation for the field "field"s = p()
returns the full evaluated structure asp.eval()
s = p("field1","field2"...)
returns the evaluated substructure for fields "field1", "field2"
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) # --------
Internal Evaluation and Recursion with !
p=param() p.a = [0,1,2] p.b = '![1,2,"test","${a[1]}"]' p # Output: # -------------:---------------------------------------- # a: [0, 1, 2] # b: ![1,2,"test","${a[1]}"] # = [1, 2, 'test', '1'] # -------------:---------------------------------------- # Out: parameter list (param object) with 2 definitions
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. safe_fstring(string)
evaluate safely complex mathemical expressions.
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. ### Shorthands for `p=param(...)` - `s = p.eval()` returns the full evaluated structure - `p.getval("field")` returns the evaluation for the field "field" - `s = p()` returns the full evaluated structure as `p.eval()` - `s = p("field1","field2"...)` returns the evaluated substructure for fields "field1", "field2" --- ### 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) # -------- ``` #### Internal Evaluation and Recursion with ! ```python p=param() p.a = [0,1,2] p.b = '![1,2,"test","${a[1]}"]' p # Output: # -------------:---------------------------------------- # a: [0, 1, 2] # b: ![1,2,"test","${a[1]}"] # = [1, 2, 'test', '1'] # -------------:---------------------------------------- # Out: parameter list (param object) with 2 definitions ``` #### 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. | | `safe_fstring(string)` | evaluate safely complex mathemical expressions. | --- ### 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,debug=False,**kwargs): """ constructor """ super().__init__(debug=debug,**kwargs) self._protection = _protection self._evaluation = _evaluation self._needs_sorting = False # defers sorting 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(r"\${",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(r"\$","££") # && is a placeholder escape = t!=s for k in self.keyssorted(): t = t.replace("$"+k,"${"+k+"}") if escape: t = t.replace("££",r"\$") 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 """ # handle deferred sorting if self._needs_sorting: self.sortdefinitions(raiseerror=False, silentmode=True) # the argument s is only used by formateval() for error management tmp = struct(debug=self._debug) # evaluator without context evaluator_nocontext = SafeEvaluator() # for global evaluation without context # main string evaluator def evalstr(value,key=""): # 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 valuesafe=="${"+key+"}": # circular reference (it cannot be evaluated) return valuesafe if protection or self._protection: valuesafe, escape0 = self.protect(valuesafe) else: escape0 = False # replace ${var} by {var} once basic substitutions have been applied valuesafe_priorescape = tmp.numrepl(valuesafe) # minimal substitution valuesafe, escape = param.escape(valuesafe_priorescape) 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() # Matrix shorthand replacement # $[[1,2,${a}]]+$[[10,20,30]] --> np.array([[1,2,${a}]])+np.array([[10,20,30]]) valuesafe = param.replace_matrix_shorthand(valuesafe) # Literal string starts with $ (no interpretation), ! (evaluation) if not self._evaluation: return pstr.eval(tmp.format(valuesafe,escape),ispstr=ispstr) elif valuesafe.startswith("!"): # <---------- FORECED LITERAL EVALUATION (error messages are returned) try: #vtmp = ast.literal_eval(valuesafe[1:]) evaluator = SafeEvaluator(tmp) vtmp = evaluate_with_placeholders(valuesafe[1:],evaluator,evaluator_nocontext) if isinstance(vtmp,list): for i,item in enumerate(vtmp): if isinstance(item,str) and not is_literal_string(item): try: vtmp[i] = tmp.format(item, raiseerror=False) # in case substitions/interpolations are needed try: vtmp[i] = evaluator_nocontext.evaluate(vtmp[i]) # full evaluation without context except Exception as othererr: if self._debug: print(f"DEBUG {key}: Error evaluating: {vtmp[i]}\n< {othererr} >") except Exception as ve: vtmp[i] = f"Error in <{item}>: {ve.__class__.__name__} - {str(ve)}" return vtmp except (SyntaxError, ValueError) as e: return f"Error: {e.__class__.__name__} - {str(e)}" elif valuesafe.startswith("$") and not escape: return tmp.format(valuesafe[1:].lstrip()) # discard $ elif valuesafe.startswith("%"): return tmp.format(valuesafe[1:].lstrip()) # discard % else: # string empty or which can be evaluated if valuesafe=="": return valuesafe # empty content else: if isinstance(value,pstr): # keep path return pstr.topath(tmp.format(valuesafe,escape=escape)) elif escape: # partial evaluation return tmp.format(valuesafe,escape=True) else: # full evaluation (if it fails the last string content is returned) <---------- FULL EVALUTION will be tried try: resstr = tmp.format(valuesafe,raiseerror=False) except (KeyError,NameError) as nameerr: try: # nested indexing (guess) resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr, evaluator,evaluator_nocontext,raiseerror=True) except Exception as othererr: if self._returnerror: # added on 2024-09-06 strnameerr = str(nameerr).replace("'","") if self._debug: print(f'Key Error for "{key}" < {othererr} >') return '< undef %s "${%s}" >' % (self._ftype,strnameerr) else: return value #we keep the original value else: return reseval except SyntaxError as commonerr: return "Syntax Error < %s >" % commonerr except TypeError as commonerr: try: # nested indexing (guess) resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr, evaluator,evaluator_nocontext,raiseerror=True) except Exception as othererr: if self._debug: print(f'Type Error for "{key}" < {othererr} >') return "Type Error < %s >" % commonerr else: return reseval except (IndexError,AttributeError): try: resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) except Exception as fstrerr: return "Index Error < %s >" % fstrerr else: try: # reseval = eval(resstr) # reseval = ast.literal_eval(resstr) # Use SafeEvaluator to evaluate the final expression evaluator = SafeEvaluator(tmp) reseval = evaluator.evaluate(resstr) except Exception as othererr: #tmp.setattr(key,"Mathematical Error around/in ${}: < %s >" % othererr) if self._debug: print(f"DEBUG {key}: Error evaluating: {resstr}\n< {othererr} >") return resstr else: return reseval except ValueError as valerr: # forced evaluation within ${} try: evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(valuesafe_priorescape,evaluator,evaluator_nocontext,raiseerror=True) except SyntaxError as synerror: if self._debug: print(f"DEBUG {key}: Error evaluating: {valuesafe_priorescape}\n< {synerror} >") return evaluate_with_placeholders(valuesafe_priorescape,evaluator,evaluator_nocontext,raiseerror=False) except Exception as othererr: if self._debug: print(f"DEBUG {key}: Error evaluating: {valuesafe_priorescape}\n< {othererr} >") return "Error in ${}: < %s >" % valerr else: return reseval except Exception as othererr: return "Error in ${}: < %s >" % othererr else: try: # reseval = eval(resstr) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr,evaluator,evaluator_nocontext) except Exception as othererr: #tmp.setattr(key,"Eval Error < %s >" % othererr) if self._debug: print(f"DEBUG {key}: Error evaluating: {resstr}\n< {othererr} >") return resstr.replace("\n",",") # \n replaced by , else: return reseval # evalstr() refactored for error management def safe_evalstr(x,key=""): xeval = evalstr(x,key) if isinstance(xeval,str): try: evaluator = SafeEvaluator(tmp) return evaluate_with_placeholders(xeval,evaluator,evaluator_nocontext) except Exception as e: if self._debug: print(f"DEBUG {key}: Error evaluating '{x}': {e}") return xeval # default fallback value else: return xeval # Evaluate all DEFINITIONS for key,value in self.items(): # strings are assumed to be expressions on one single line if isinstance(value,str): tmp.setattr(key,evalstr(value,key)) elif isinstance(value,_numeric_types): # already a number if isinstance(value,list): valuelist = [safe_evalstr(x,key) if isinstance(x,str) else x for x in value] tmp.setattr(key,valuelist) else: tmp.setattr(key, value) # store the value with the key elif isinstance(value, dict): # For dictionaries, evaluate each entry using its own key (sub_key) new_dict = {} for sub_key, sub_value in value.items(): if isinstance(sub_value, str): new_dict[sub_key] = safe_evalstr(sub_value,key) elif isinstance(sub_value, list): # If an entry is a list, apply safe_evalstr to each string element within it new_dict[sub_key] = [safe_evalstr(x, sub_key) if isinstance(x, str) else x for x in sub_value] else: new_dict[sub_key] = sub_value tmp.setattr(key, new_dict) 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,fullevaluation=True): """ 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) evaluator = SafeEvaluator(tmp) # used when fullevaluation=True evaluator_nocontext = SafeEvaluator() # for global evaluation without context # 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") slines_priorescape = s.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: if fullevaluation: try: resstr =tmp.format(slines[i],escape=escape) except: resstr = param.safe_fstring(slines[i],tmp,varprefix="") try: #reseval = evaluator.evaluate(resstr) reseval = evaluate_with_placeholders(slines_priorescape[i],evaluator,evaluator_nocontext,raiseerror=True) slines[i] = str(reseval)+" "+comment if comment else str(reseval) except: slines[i] = resstr + comment 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) # return the value instead of formula def getval(self,key): """ returns the evaluated value """ s = self.eval() return getattr(s,key) # override () for subindexing structure with key names def __call__(self, *keys): """ Extract an evaluated 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 An evaluated instance of class struct, containing only the specified keys with evaluated values. Usage: ------ sub_struct = p('key1', 'key2', ...) """ s = self.eval() if keys: return s(*keys) else: return s # 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 paramauto instance def toparamauto(self): """ convert a param instance into a paramauto instance toparamauto() """ return paramauto(**self) # 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) # Matlab vector/list conversion @staticmethod def expand_ranges(text,numfmt=".4g"): """ Expands MATLAB-style ranges in a string. Args: text: The input string containing ranges. numfmt: numeric format to be used for the string conversion (default=".4g") Returns: The string with ranges expanded, or the original string if no valid ranges are found or if expansion leads to more than 100 elements. Returns an error message if the input format is invalid. """ def expand_range(match): try: parts = match.group(1).split(':') if len(parts) == 2: start, stop = map(float, parts) step = 1.0 elif len(parts) == 3: start, step, stop = map(float, parts) else: return match.group(0) # Return original if format is invalid if step == 0: return "Error: <Step cannot be zero.>" if (stop - start) / step > 1e6: return "Error: <Range is too large.>" if step > 0: num_elements = int(np.floor((stop - start)/step)+1) else: num_elements = int(np.floor((start - stop)/-step)+1) if num_elements > 100: return match.group(0) # Return original if too many elements expanded_range = np.arange(start, stop + np.sign(step)*1e-9, step) #adding a small number to include the stop in case of integer steps return '[' + ','.join(f'{x:{numfmt}}' for x in expanded_range) + ']' except ValueError: return "Error: <Invalid range format.>" pattern = r'(\b(?:-?\d+(?:\.\d*)?|-?\.\d+)(?::(?:-?\d+(?:\.\d*)?|-?\.\d+)){1,2})\b' expanded_text = re.sub(pattern, expand_range, text) #check for errors generated by the function if "Error:" in expanded_text: return expanded_text return expanded_text # Matlab syntax conversion @staticmethod def convert_matlab_like_arrays(text): """ Converts Matlab-like array syntax (including hybrid notations) into a NumPy-esque list syntax in multiple passes. Steps: 1) Convert 2D Matlab arrays (containing semicolons) into Python-like nested lists. 2) Convert bracketed row vectors (no semicolons or nested brackets) into double-bracket format. 3) Replace spaces with commas under specific conditions and remove duplicates. Args: text (str): Input string that may contain Matlab-like arrays. Returns: str: Transformed text with arrays converted to a Python/NumPy-like syntax. Examples: examples = [ "[1, 2 ${var1} ; 4, 5 ${var2}]", "[1,2,3]", "[1 2 , 3]", "[1;2; 3]", "[[-0.5, 0.5;-0.5, 0.5],[ -0.5, 0.5; -0.5, 0.5]]", "[[1,2;3,4],[5,6; 7,8]]", "[1, 2, 3; 4, 5, 6]", # Hybrid "[[Already, in, Python]]", # Already Python-like? "Not an array" ] for ex in examples: converted = param.convert_matlab_like_arrays(ex) print(f"Matlab: {ex}\nNumPy : {converted}\n") """ # -------------------------------------------------------------------------- # Step 1: Detect innermost [ ... ; ... ] blocks and convert them # -------------------------------------------------------------------------- def convert_matrices_with_semicolons(txt): """ Repeatedly find the innermost bracket pair that contains a semicolon and convert it to a Python-style nested list, row by row. """ # Pattern to find innermost [ ... ; ... ] without nested brackets pattern = r'\[[^\[\]]*?;[^\[\]]*?\]' while True: match = re.search(pattern, txt) if not match: break # No more [ ... ; ... ] blocks to convert inner_block = match.group(0) # Remove the outer brackets inner_content = inner_block[1:-1].strip() # Split into rows by semicolon rows = [row.strip() for row in inner_content.split(';')] converted_rows = [] for row in rows: # Replace multiple spaces with a single space row_clean = re.sub(r'\s+', ' ', row) # Split row by commas or spaces row_elems = re.split(r'[,\s]+', row_clean) row_elems = [elem for elem in row_elems if elem] # Remove empty strings # Join elements with commas and encapsulate in brackets converted_rows.append("[" + ",".join(row_elems) + "]") # Join the row lists and encapsulate them in brackets replacement = "[" + ",".join(converted_rows) + "]" # Replace the original Matlab matrix with the Python list txt = txt[:match.start()] + replacement + txt[match.end():] return txt # -------------------------------------------------------------------------- # Step 2: Convert row vectors without semicolons or nested brackets # into double-bracket format, e.g. [1,2,3] -> [[1,2,3]] # -------------------------------------------------------------------------- def convert_row_vectors(txt): """ Convert [1,2,3] or [1 2 3] into [[1,2,3]] if the bracket does not contain semicolons, nor nested brackets. We do this iteratively, skipping any bracket blocks that don't qualify, rather than stopping. """ # We only want bracket blocks that are NOT preceded by '[' or ',' # do not contain semicolons or nested brackets # and are not followed by ']' or ',' pattern = r"(?<!\[)(?<!,)\([^();]*\)(?!\s*\])(?!\s*\,)" startpos = 0 while True: match = re.search(pattern, txt[startpos:]) if not match: break # No more bracket blocks to check # Compute absolute positions in txt mstart = startpos + match.start() mend = startpos + match.end() block = txt[mstart:mend] # we need to be sure that [ ] are present around the block even if separated by spaces if mstart == 0 or mend == len(txt) - 1: break if not (re.match(r"\[\s*$", txt[:mstart]) and re.match(r"^\s*\]", txt[mend+1:])): break # Double-check that this bracket does not contain semicolons or nested brackets # If it does, we skip it (just advance the search) to avoid messing up matrices. # That is, we do not transform it into double brackets. if ';' in block or '[' in block[1:-1] or ']' in block[1:-1]: # Move beyond this match and keep searching startpos = mend continue # It's a pure row vector (no semicolons, no nested brackets) new_block = "[" + block + "]" # e.g. [1,2,3] -> [[1,2,3]] txt = txt[:mstart] + new_block + txt[mend:] # Update search position to avoid re-matching inside the newly inserted text startpos = mstart + len(new_block) return txt # -------------------------------------------------------------------------- # Step 3: Replace spaces with commas under specific conditions and clean up # -------------------------------------------------------------------------- def replace_spaces_safely(txt): """ Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas. Then remove multiple consecutive commas and trailing commas before closing brackets. """ # 1) Replace spaces with commas if not preceded by [,\s # and followed by digit or $ txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # 2) Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) # 3) Remove trailing commas before closing brackets txt = re.sub(r',+\]', ']', txt) return txt def replace_spaces_with_commas(txt): """ Replaces spaces with commas only when they're within array shorthands and not preceded by a comma, opening bracket, or whitespace, and are followed by a digit or '$'. Also collapses multiple commas into one and strips leading/trailing commas. Parameters: ---------- txt : str The text to process. Returns: ------- str The processed text with appropriate commas. """ # Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) # Strip leading and trailing commas txt = txt.strip(',') # Replace residual multiple consecutive spaces with a single space return re.sub(r'\s+', ' ', txt) # -------------------------------------------------------------------------- # Apply Step 1: Convert matrices with semicolons # -------------------------------------------------------------------------- text_cv = convert_matrices_with_semicolons(text) # -------------------------------------------------------------------------- # Apply Step 2: Convert row vectors (no semicolons/nested brackets) # -------------------------------------------------------------------------- text_cv = convert_row_vectors(text_cv) # -------------------------------------------------------------------------- # Apply Step 3: Replace spaces with commas and clean up # -------------------------------------------------------------------------- if text_cv != text: return replace_spaces_with_commas(text_cv) # old method: replace_spaces_safely(text_cv) else: return text @classmethod def replace_matrix_shorthand(cls,valuesafe): """ Transforms custom shorthand notations for NumPy arrays within a string into valid NumPy array constructors. Supports up to 4-dimensional arrays and handles variable references. **Shorthand Patterns:** - **1D**: `$[1 2 3]` → `np.atleast_2d(np.array([1,2,3]))` - **2D**: `$[[1 2],[3 4]]` → `np.array([[1,2],[3,4]])` - **3D**: `$[[[1 2],[3 4]],[[5 6],[7 8]]]` → `np.array([[[1,2],[3,4]],[[5,6],[7,8]]])` - **4D**: `$[[[[1 2]]]]` → `np.array([[[[1,2]]]])` - **Variable References**: `@{var}` → `np.atleast_2d(np.array(${var}))` **Parameters:** ---------- valuesafe : str The input string containing shorthand notations for NumPy arrays and variable references. **Returns:** ------- str The transformed string with shorthands replaced by valid NumPy array constructors. **Raises:** ------- ValueError If there are unmatched brackets in any shorthand. **Examples:** -------- >>> # 1D shorthand >>> s = "$[1 2 3]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array([1,2,3]))' >>> # 2D shorthand with mixed spacing >>> s = "$[[1, 2], [3 4]]" >>> param.replace_matrix_shorthand(s) 'np.array([[1,2],[3,4]])' >>> # 3D array with partial spacing >>> s = "$[[[1 2], [3 4]], [[5 6], [7 8]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[1,2],[3,4]],[[5,6],[7,8]]])' >>> # 4D array >>> s = "$[[[[1 2]]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[[1,2]]]])' >>> # Combined with variable references >>> s = "@{a} + $[[${b}, 2],[ 3 4]]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array(${a})) + np.array([[${b},2],[3,4]])' >>> # Complex ND array with scaling >>> s = '$[[[-0.5, -0.5],[-0.5, -0.5]],[[ 0.5, 0.5],[ 0.5, 0.5]]]*0.001' >>> param.replace_matrix_shorthand(s) 'np.array([[[-0.5,-0.5],[-0.5,-0.5]],[[0.5,0.5],[0.5,0.5]]])*0.001' """ numfmt = f".{cls._precision}g" def replace_spaces_with_commas(txt): """ Replaces spaces with commas only when they're not preceded by a comma, opening bracket, or whitespace, and are followed by a digit or '$'. Also collapses multiple commas into one and strips leading/trailing commas. Parameters: ---------- txt : str The text to process. Returns: ------- str The processed text with appropriate commas. """ # Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) return txt.strip(',') def build_pass_list(string): """ Determines which dimensions (1D..4D) appear in the string by searching for: - 4D: $[[[[ - 3D: $[[[ - 2D: $[[ - 1D: $[ Returns a sorted list in descending order, e.g., [4, 3, 2, 1]. Parameters: ---------- string : str The input string to scan. Returns: ------- list A list of integers representing the dimensions found, sorted descending. """ dims_found = set() if re.search(r'\$\[\[\[\[', string): dims_found.add(4) if re.search(r'\$\[\[\[', string): dims_found.add(3) if re.search(r'\$\[\[', string): dims_found.add(2) if re.search(r'\$\[', string): dims_found.add(1) return sorted(dims_found, reverse=True) # Step 0: convert eventual Matlab syntax for row and column vectors into NumPy syntax valuesafe = param.expand_ranges(valuesafe,numfmt) # expands start:stop and start:step:stop syntax valuesafe = param.convert_matlab_like_arrays(valuesafe) # vectors and matrices conversion # Step 1: Handle @{var} -> np.atleast_2d(np.array(${var})) valuesafe = re.sub(r'@\{([^\{\}]+)\}', r'np.atleast_2d(np.array(${\1}))', valuesafe) # Step 2: Build pass list from largest dimension to smallest pass_list = build_pass_list(valuesafe) # Step 3: Define patterns and replacements for each dimension dimension_patterns = { 4: (r'\$\[\[\[\[(.*?)\]\]\]\]', 'np.array([[[[{content}]]]])'), # 4D 3: (r'\$\[\[\[(.*?)\]\]\]', 'np.array([[[{content}]]])'), # 3D 2: (r'\$\[\[(.*?)\]\]', 'np.array([[{content}]])'), # 2D 1: (r'\$\[(.*?)\]', 'np.atleast_2d(np.array([{content}]))') # 1D } # Step 4: Iterate over each dimension and perform replacements for dim in pass_list: pattern, replacement_fmt = dimension_patterns[dim] # Find all non-overlapping matches for the current dimension matches = list(re.finditer(pattern, valuesafe)) for match in matches: full_match = match.group(0) # Entire matched shorthand inner_content = match.group(1) # Content inside the brackets # Replace spaces with commas as per rules processed_content = replace_spaces_with_commas(inner_content.strip()) # Create the replacement string replacement = replacement_fmt.format(content=processed_content) # Replace the shorthand in the string valuesafe = valuesafe.replace(full_match, replacement) # Step 5: Verify that all shorthands have been replaced by checking for remaining '$[' if re.search(r'\$\[', valuesafe): raise ValueError("Unmatched or improperly formatted brackets detected in the input string.") return valuesafe # Safe fstring @staticmethod def safe_fstring(template, context,varprefix="$"): """Safely evaluate expressions in ${} using SafeEvaluator.""" evaluator = SafeEvaluator(context) # Process template string in combination with safe_fstring() # it is required to have an output compatible with eval() def process_template(valuesafe): """ Processes the input string by: 1. Stripping leading and trailing whitespace. 2. Removing comments (any text after '#' unless '#' is the first character). 3. Replacing '^' with '**'. 4. Replacing '{' with '${' if '{' is not preceded by '$'. <-- not applied anymore (brings confusion) Args: valuesafe (str): The input string to process. Returns: str: The processed string. """ # Step 1: Strip leading and trailing whitespace valuesafe = valuesafe.strip() # Step 2: Remove comments # This regex removes '#' and everything after it if '#' is not the first character # (?<!^) is a negative lookbehind that ensures '#' is not at the start of the string valuesafe = re.sub(r'(?<!^)\#.*', '', valuesafe) # Step 3: Replace '^' with '**' valuesafe = re.sub(r'\^', '**', valuesafe) # Step 4: Replace '{' with '${' if '{' is not preceded by '$' # (?<!\$)\{ matches '{' not preceded by '$' # valuesafe = re.sub(r'(?<!\$)\{', '${', valuesafe) # Optional: Strip again to remove any trailing whitespace left after removing comments valuesafe = valuesafe.strip() return valuesafe # Adjusted display for NumPy arrays def serialize_result(result): """ Serialize the result into a string that can be evaluated in Python. Handles NumPy arrays by converting them to lists with commas. Handles other iterable types appropriately. """ if isinstance(result, np.ndarray): return str(result.tolist()) elif isinstance(result, (list, tuple, dict)): return str(result) else: return str(result) # Regular expression to find ${expr} patterns escaped_varprefix = re.escape(varprefix) pattern = re.compile(escaped_varprefix+r'\{([^{}]+)\}') def replacer(match): expr = match.group(1) try: result = evaluator.evaluate(expr) serialized = serialize_result(result) return serialized except Exception as e: return f"<Error: {e}>" return pattern.sub(replacer, process_template(template))
Ancestors
- pizza.private.mstruct.struct
Subclasses
- pizza.private.mstruct.paramauto
- pizza.script.scriptdata
- scriptdata
Static methods
def convert_matlab_like_arrays(text)
-
Converts Matlab-like array syntax (including hybrid notations) into a NumPy-esque list syntax in multiple passes.
Steps: 1) Convert 2D Matlab arrays (containing semicolons) into Python-like nested lists. 2) Convert bracketed row vectors (no semicolons or nested brackets) into double-bracket format. 3) Replace spaces with commas under specific conditions and remove duplicates. Args: text (str): Input string that may contain Matlab-like arrays. Returns: str: Transformed text with arrays converted to a Python/NumPy-like syntax. Examples: examples = [ "[1, 2 ${var1} ; 4, 5 ${var2}]", "[1,2,3]", "[1 2 , 3]", "[1;2; 3]", "[[-0.5, 0.5;-0.5, 0.5],[ -0.5, 0.5; -0.5, 0.5]]", "[[1,2;3,4],[5,6; 7,8]]", "[1, 2, 3; 4, 5, 6]", # Hybrid "[[Already, in, Python]]", # Already Python-like? "Not an array" ] for ex in examples: converted = param.convert_matlab_like_arrays(ex) print(f"Matlab: {ex}
NumPy : {converted} ")
Expand source code
@staticmethod def convert_matlab_like_arrays(text): """ Converts Matlab-like array syntax (including hybrid notations) into a NumPy-esque list syntax in multiple passes. Steps: 1) Convert 2D Matlab arrays (containing semicolons) into Python-like nested lists. 2) Convert bracketed row vectors (no semicolons or nested brackets) into double-bracket format. 3) Replace spaces with commas under specific conditions and remove duplicates. Args: text (str): Input string that may contain Matlab-like arrays. Returns: str: Transformed text with arrays converted to a Python/NumPy-like syntax. Examples: examples = [ "[1, 2 ${var1} ; 4, 5 ${var2}]", "[1,2,3]", "[1 2 , 3]", "[1;2; 3]", "[[-0.5, 0.5;-0.5, 0.5],[ -0.5, 0.5; -0.5, 0.5]]", "[[1,2;3,4],[5,6; 7,8]]", "[1, 2, 3; 4, 5, 6]", # Hybrid "[[Already, in, Python]]", # Already Python-like? "Not an array" ] for ex in examples: converted = param.convert_matlab_like_arrays(ex) print(f"Matlab: {ex}\nNumPy : {converted}\n") """ # -------------------------------------------------------------------------- # Step 1: Detect innermost [ ... ; ... ] blocks and convert them # -------------------------------------------------------------------------- def convert_matrices_with_semicolons(txt): """ Repeatedly find the innermost bracket pair that contains a semicolon and convert it to a Python-style nested list, row by row. """ # Pattern to find innermost [ ... ; ... ] without nested brackets pattern = r'\[[^\[\]]*?;[^\[\]]*?\]' while True: match = re.search(pattern, txt) if not match: break # No more [ ... ; ... ] blocks to convert inner_block = match.group(0) # Remove the outer brackets inner_content = inner_block[1:-1].strip() # Split into rows by semicolon rows = [row.strip() for row in inner_content.split(';')] converted_rows = [] for row in rows: # Replace multiple spaces with a single space row_clean = re.sub(r'\s+', ' ', row) # Split row by commas or spaces row_elems = re.split(r'[,\s]+', row_clean) row_elems = [elem for elem in row_elems if elem] # Remove empty strings # Join elements with commas and encapsulate in brackets converted_rows.append("[" + ",".join(row_elems) + "]") # Join the row lists and encapsulate them in brackets replacement = "[" + ",".join(converted_rows) + "]" # Replace the original Matlab matrix with the Python list txt = txt[:match.start()] + replacement + txt[match.end():] return txt # -------------------------------------------------------------------------- # Step 2: Convert row vectors without semicolons or nested brackets # into double-bracket format, e.g. [1,2,3] -> [[1,2,3]] # -------------------------------------------------------------------------- def convert_row_vectors(txt): """ Convert [1,2,3] or [1 2 3] into [[1,2,3]] if the bracket does not contain semicolons, nor nested brackets. We do this iteratively, skipping any bracket blocks that don't qualify, rather than stopping. """ # We only want bracket blocks that are NOT preceded by '[' or ',' # do not contain semicolons or nested brackets # and are not followed by ']' or ',' pattern = r"(?<!\[)(?<!,)\([^();]*\)(?!\s*\])(?!\s*\,)" startpos = 0 while True: match = re.search(pattern, txt[startpos:]) if not match: break # No more bracket blocks to check # Compute absolute positions in txt mstart = startpos + match.start() mend = startpos + match.end() block = txt[mstart:mend] # we need to be sure that [ ] are present around the block even if separated by spaces if mstart == 0 or mend == len(txt) - 1: break if not (re.match(r"\[\s*$", txt[:mstart]) and re.match(r"^\s*\]", txt[mend+1:])): break # Double-check that this bracket does not contain semicolons or nested brackets # If it does, we skip it (just advance the search) to avoid messing up matrices. # That is, we do not transform it into double brackets. if ';' in block or '[' in block[1:-1] or ']' in block[1:-1]: # Move beyond this match and keep searching startpos = mend continue # It's a pure row vector (no semicolons, no nested brackets) new_block = "[" + block + "]" # e.g. [1,2,3] -> [[1,2,3]] txt = txt[:mstart] + new_block + txt[mend:] # Update search position to avoid re-matching inside the newly inserted text startpos = mstart + len(new_block) return txt # -------------------------------------------------------------------------- # Step 3: Replace spaces with commas under specific conditions and clean up # -------------------------------------------------------------------------- def replace_spaces_safely(txt): """ Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas. Then remove multiple consecutive commas and trailing commas before closing brackets. """ # 1) Replace spaces with commas if not preceded by [,\s # and followed by digit or $ txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # 2) Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) # 3) Remove trailing commas before closing brackets txt = re.sub(r',+\]', ']', txt) return txt def replace_spaces_with_commas(txt): """ Replaces spaces with commas only when they're within array shorthands and not preceded by a comma, opening bracket, or whitespace, and are followed by a digit or '$'. Also collapses multiple commas into one and strips leading/trailing commas. Parameters: ---------- txt : str The text to process. Returns: ------- str The processed text with appropriate commas. """ # Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) # Strip leading and trailing commas txt = txt.strip(',') # Replace residual multiple consecutive spaces with a single space return re.sub(r'\s+', ' ', txt) # -------------------------------------------------------------------------- # Apply Step 1: Convert matrices with semicolons # -------------------------------------------------------------------------- text_cv = convert_matrices_with_semicolons(text) # -------------------------------------------------------------------------- # Apply Step 2: Convert row vectors (no semicolons/nested brackets) # -------------------------------------------------------------------------- text_cv = convert_row_vectors(text_cv) # -------------------------------------------------------------------------- # Apply Step 3: Replace spaces with commas and clean up # -------------------------------------------------------------------------- if text_cv != text: return replace_spaces_with_commas(text_cv) # old method: replace_spaces_safely(text_cv) else: return text
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(r"\${",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
def expand_ranges(text, numfmt='.4g')
-
Expands MATLAB-style ranges in a string.
Args
text
- The input string containing ranges.
numfmt
- numeric format to be used for the string conversion (default=".4g")
Returns
The string with ranges expanded, or the original string if no valid ranges are found or if expansion leads to more than 100 elements. Returns an error message if the input format is invalid.
Expand source code
@staticmethod def expand_ranges(text,numfmt=".4g"): """ Expands MATLAB-style ranges in a string. Args: text: The input string containing ranges. numfmt: numeric format to be used for the string conversion (default=".4g") Returns: The string with ranges expanded, or the original string if no valid ranges are found or if expansion leads to more than 100 elements. Returns an error message if the input format is invalid. """ def expand_range(match): try: parts = match.group(1).split(':') if len(parts) == 2: start, stop = map(float, parts) step = 1.0 elif len(parts) == 3: start, step, stop = map(float, parts) else: return match.group(0) # Return original if format is invalid if step == 0: return "Error: <Step cannot be zero.>" if (stop - start) / step > 1e6: return "Error: <Range is too large.>" if step > 0: num_elements = int(np.floor((stop - start)/step)+1) else: num_elements = int(np.floor((start - stop)/-step)+1) if num_elements > 100: return match.group(0) # Return original if too many elements expanded_range = np.arange(start, stop + np.sign(step)*1e-9, step) #adding a small number to include the stop in case of integer steps return '[' + ','.join(f'{x:{numfmt}}' for x in expanded_range) + ']' except ValueError: return "Error: <Invalid range format.>" pattern = r'(\b(?:-?\d+(?:\.\d*)?|-?\.\d+)(?::(?:-?\d+(?:\.\d*)?|-?\.\d+)){1,2})\b' expanded_text = re.sub(pattern, expand_range, text) #check for errors generated by the function if "Error:" in expanded_text: return expanded_text return expanded_text
def replace_matrix_shorthand(valuesafe)
-
Transforms custom shorthand notations for NumPy arrays within a string into valid NumPy array constructors. Supports up to 4-dimensional arrays and handles variable references.
Shorthand Patterns: - 1D:
$[1 2 3]
→np.atleast_2d(np.array([1,2,3]))
- 2D:$[[1 2],[3 4]]
→np.array([[1,2],[3,4]])
- 3D:$[[[1 2],[3 4]],[[5 6],[7 8]]]
→np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
- 4D:$[[[[1 2]]]]
→np.array([[[[1,2]]]])
- Variable References:@{var}
→np.atleast_2d(np.array(${var}))
Parameters:
valuesafe : str The input string containing shorthand notations for NumPy arrays and variable references.
Returns:
str The transformed string with shorthands replaced by valid NumPy array constructors.
Raises:
ValueError If there are unmatched brackets in any shorthand.
Examples:
>>> # 1D shorthand >>> s = "$[1 2 3]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array([1,2,3]))'
>>> # 2D shorthand with mixed spacing >>> s = "$[[1, 2], [3 4]]" >>> param.replace_matrix_shorthand(s) 'np.array([[1,2],[3,4]])'
>>> # 3D array with partial spacing >>> s = "$[[[1 2], [3 4]], [[5 6], [7 8]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[1,2],[3,4]],[[5,6],[7,8]]])'
>>> # 4D array >>> s = "$[[[[1 2]]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[[1,2]]]])'
>>> # Combined with variable references >>> s = "@{a} + $[[${b}, 2],[ 3 4]]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array(${a})) + np.array([[${b},2],[3,4]])'
>>> # Complex ND array with scaling >>> s = '$[[[-0.5, -0.5],[-0.5, -0.5]],[[ 0.5, 0.5],[ 0.5, 0.5]]]*0.001' >>> param.replace_matrix_shorthand(s) 'np.array([[[-0.5,-0.5],[-0.5,-0.5]],[[0.5,0.5],[0.5,0.5]]])*0.001'
Expand source code
@classmethod def replace_matrix_shorthand(cls,valuesafe): """ Transforms custom shorthand notations for NumPy arrays within a string into valid NumPy array constructors. Supports up to 4-dimensional arrays and handles variable references. **Shorthand Patterns:** - **1D**: `$[1 2 3]` → `np.atleast_2d(np.array([1,2,3]))` - **2D**: `$[[1 2],[3 4]]` → `np.array([[1,2],[3,4]])` - **3D**: `$[[[1 2],[3 4]],[[5 6],[7 8]]]` → `np.array([[[1,2],[3,4]],[[5,6],[7,8]]])` - **4D**: `$[[[[1 2]]]]` → `np.array([[[[1,2]]]])` - **Variable References**: `@{var}` → `np.atleast_2d(np.array(${var}))` **Parameters:** ---------- valuesafe : str The input string containing shorthand notations for NumPy arrays and variable references. **Returns:** ------- str The transformed string with shorthands replaced by valid NumPy array constructors. **Raises:** ------- ValueError If there are unmatched brackets in any shorthand. **Examples:** -------- >>> # 1D shorthand >>> s = "$[1 2 3]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array([1,2,3]))' >>> # 2D shorthand with mixed spacing >>> s = "$[[1, 2], [3 4]]" >>> param.replace_matrix_shorthand(s) 'np.array([[1,2],[3,4]])' >>> # 3D array with partial spacing >>> s = "$[[[1 2], [3 4]], [[5 6], [7 8]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[1,2],[3,4]],[[5,6],[7,8]]])' >>> # 4D array >>> s = "$[[[[1 2]]]]" >>> param.replace_matrix_shorthand(s) 'np.array([[[[1,2]]]])' >>> # Combined with variable references >>> s = "@{a} + $[[${b}, 2],[ 3 4]]" >>> param.replace_matrix_shorthand(s) 'np.atleast_2d(np.array(${a})) + np.array([[${b},2],[3,4]])' >>> # Complex ND array with scaling >>> s = '$[[[-0.5, -0.5],[-0.5, -0.5]],[[ 0.5, 0.5],[ 0.5, 0.5]]]*0.001' >>> param.replace_matrix_shorthand(s) 'np.array([[[-0.5,-0.5],[-0.5,-0.5]],[[0.5,0.5],[0.5,0.5]]])*0.001' """ numfmt = f".{cls._precision}g" def replace_spaces_with_commas(txt): """ Replaces spaces with commas only when they're not preceded by a comma, opening bracket, or whitespace, and are followed by a digit or '$'. Also collapses multiple commas into one and strips leading/trailing commas. Parameters: ---------- txt : str The text to process. Returns: ------- str The processed text with appropriate commas. """ # Replace spaces not preceded by ',', '[', or whitespace and followed by digit or '$' with commas txt = re.sub(r'(?<![,\[\s])\s+(?=[\d\$])', ',', txt) # Remove multiple consecutive commas txt = re.sub(r',+', ',', txt) return txt.strip(',') def build_pass_list(string): """ Determines which dimensions (1D..4D) appear in the string by searching for: - 4D: $[[[[ - 3D: $[[[ - 2D: $[[ - 1D: $[ Returns a sorted list in descending order, e.g., [4, 3, 2, 1]. Parameters: ---------- string : str The input string to scan. Returns: ------- list A list of integers representing the dimensions found, sorted descending. """ dims_found = set() if re.search(r'\$\[\[\[\[', string): dims_found.add(4) if re.search(r'\$\[\[\[', string): dims_found.add(3) if re.search(r'\$\[\[', string): dims_found.add(2) if re.search(r'\$\[', string): dims_found.add(1) return sorted(dims_found, reverse=True) # Step 0: convert eventual Matlab syntax for row and column vectors into NumPy syntax valuesafe = param.expand_ranges(valuesafe,numfmt) # expands start:stop and start:step:stop syntax valuesafe = param.convert_matlab_like_arrays(valuesafe) # vectors and matrices conversion # Step 1: Handle @{var} -> np.atleast_2d(np.array(${var})) valuesafe = re.sub(r'@\{([^\{\}]+)\}', r'np.atleast_2d(np.array(${\1}))', valuesafe) # Step 2: Build pass list from largest dimension to smallest pass_list = build_pass_list(valuesafe) # Step 3: Define patterns and replacements for each dimension dimension_patterns = { 4: (r'\$\[\[\[\[(.*?)\]\]\]\]', 'np.array([[[[{content}]]]])'), # 4D 3: (r'\$\[\[\[(.*?)\]\]\]', 'np.array([[[{content}]]])'), # 3D 2: (r'\$\[\[(.*?)\]\]', 'np.array([[{content}]])'), # 2D 1: (r'\$\[(.*?)\]', 'np.atleast_2d(np.array([{content}]))') # 1D } # Step 4: Iterate over each dimension and perform replacements for dim in pass_list: pattern, replacement_fmt = dimension_patterns[dim] # Find all non-overlapping matches for the current dimension matches = list(re.finditer(pattern, valuesafe)) for match in matches: full_match = match.group(0) # Entire matched shorthand inner_content = match.group(1) # Content inside the brackets # Replace spaces with commas as per rules processed_content = replace_spaces_with_commas(inner_content.strip()) # Create the replacement string replacement = replacement_fmt.format(content=processed_content) # Replace the shorthand in the string valuesafe = valuesafe.replace(full_match, replacement) # Step 5: Verify that all shorthands have been replaced by checking for remaining '$[' if re.search(r'\$\[', valuesafe): raise ValueError("Unmatched or improperly formatted brackets detected in the input string.") return valuesafe
def safe_fstring(template, context, varprefix='$')
-
Safely evaluate expressions in ${} using SafeEvaluator.
Expand source code
@staticmethod def safe_fstring(template, context,varprefix="$"): """Safely evaluate expressions in ${} using SafeEvaluator.""" evaluator = SafeEvaluator(context) # Process template string in combination with safe_fstring() # it is required to have an output compatible with eval() def process_template(valuesafe): """ Processes the input string by: 1. Stripping leading and trailing whitespace. 2. Removing comments (any text after '#' unless '#' is the first character). 3. Replacing '^' with '**'. 4. Replacing '{' with '${' if '{' is not preceded by '$'. <-- not applied anymore (brings confusion) Args: valuesafe (str): The input string to process. Returns: str: The processed string. """ # Step 1: Strip leading and trailing whitespace valuesafe = valuesafe.strip() # Step 2: Remove comments # This regex removes '#' and everything after it if '#' is not the first character # (?<!^) is a negative lookbehind that ensures '#' is not at the start of the string valuesafe = re.sub(r'(?<!^)\#.*', '', valuesafe) # Step 3: Replace '^' with '**' valuesafe = re.sub(r'\^', '**', valuesafe) # Step 4: Replace '{' with '${' if '{' is not preceded by '$' # (?<!\$)\{ matches '{' not preceded by '$' # valuesafe = re.sub(r'(?<!\$)\{', '${', valuesafe) # Optional: Strip again to remove any trailing whitespace left after removing comments valuesafe = valuesafe.strip() return valuesafe # Adjusted display for NumPy arrays def serialize_result(result): """ Serialize the result into a string that can be evaluated in Python. Handles NumPy arrays by converting them to lists with commas. Handles other iterable types appropriately. """ if isinstance(result, np.ndarray): return str(result.tolist()) elif isinstance(result, (list, tuple, dict)): return str(result) else: return str(result) # Regular expression to find ${expr} patterns escaped_varprefix = re.escape(varprefix) pattern = re.compile(escaped_varprefix+r'\{([^{}]+)\}') def replacer(match): expr = match.group(1) try: result = evaluator.evaluate(expr) serialized = serialize_result(result) return serialized except Exception as e: return f"<Error: {e}>" return pattern.sub(replacer, process_template(template))
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 """ # handle deferred sorting if self._needs_sorting: self.sortdefinitions(raiseerror=False, silentmode=True) # the argument s is only used by formateval() for error management tmp = struct(debug=self._debug) # evaluator without context evaluator_nocontext = SafeEvaluator() # for global evaluation without context # main string evaluator def evalstr(value,key=""): # 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 valuesafe=="${"+key+"}": # circular reference (it cannot be evaluated) return valuesafe if protection or self._protection: valuesafe, escape0 = self.protect(valuesafe) else: escape0 = False # replace ${var} by {var} once basic substitutions have been applied valuesafe_priorescape = tmp.numrepl(valuesafe) # minimal substitution valuesafe, escape = param.escape(valuesafe_priorescape) 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() # Matrix shorthand replacement # $[[1,2,${a}]]+$[[10,20,30]] --> np.array([[1,2,${a}]])+np.array([[10,20,30]]) valuesafe = param.replace_matrix_shorthand(valuesafe) # Literal string starts with $ (no interpretation), ! (evaluation) if not self._evaluation: return pstr.eval(tmp.format(valuesafe,escape),ispstr=ispstr) elif valuesafe.startswith("!"): # <---------- FORECED LITERAL EVALUATION (error messages are returned) try: #vtmp = ast.literal_eval(valuesafe[1:]) evaluator = SafeEvaluator(tmp) vtmp = evaluate_with_placeholders(valuesafe[1:],evaluator,evaluator_nocontext) if isinstance(vtmp,list): for i,item in enumerate(vtmp): if isinstance(item,str) and not is_literal_string(item): try: vtmp[i] = tmp.format(item, raiseerror=False) # in case substitions/interpolations are needed try: vtmp[i] = evaluator_nocontext.evaluate(vtmp[i]) # full evaluation without context except Exception as othererr: if self._debug: print(f"DEBUG {key}: Error evaluating: {vtmp[i]}\n< {othererr} >") except Exception as ve: vtmp[i] = f"Error in <{item}>: {ve.__class__.__name__} - {str(ve)}" return vtmp except (SyntaxError, ValueError) as e: return f"Error: {e.__class__.__name__} - {str(e)}" elif valuesafe.startswith("$") and not escape: return tmp.format(valuesafe[1:].lstrip()) # discard $ elif valuesafe.startswith("%"): return tmp.format(valuesafe[1:].lstrip()) # discard % else: # string empty or which can be evaluated if valuesafe=="": return valuesafe # empty content else: if isinstance(value,pstr): # keep path return pstr.topath(tmp.format(valuesafe,escape=escape)) elif escape: # partial evaluation return tmp.format(valuesafe,escape=True) else: # full evaluation (if it fails the last string content is returned) <---------- FULL EVALUTION will be tried try: resstr = tmp.format(valuesafe,raiseerror=False) except (KeyError,NameError) as nameerr: try: # nested indexing (guess) resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr, evaluator,evaluator_nocontext,raiseerror=True) except Exception as othererr: if self._returnerror: # added on 2024-09-06 strnameerr = str(nameerr).replace("'","") if self._debug: print(f'Key Error for "{key}" < {othererr} >') return '< undef %s "${%s}" >' % (self._ftype,strnameerr) else: return value #we keep the original value else: return reseval except SyntaxError as commonerr: return "Syntax Error < %s >" % commonerr except TypeError as commonerr: try: # nested indexing (guess) resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr, evaluator,evaluator_nocontext,raiseerror=True) except Exception as othererr: if self._debug: print(f'Type Error for "{key}" < {othererr} >') return "Type Error < %s >" % commonerr else: return reseval except (IndexError,AttributeError): try: resstr = param.safe_fstring( param.replace_matrix_shorthand(valuesafe_priorescape),tmp) except Exception as fstrerr: return "Index Error < %s >" % fstrerr else: try: # reseval = eval(resstr) # reseval = ast.literal_eval(resstr) # Use SafeEvaluator to evaluate the final expression evaluator = SafeEvaluator(tmp) reseval = evaluator.evaluate(resstr) except Exception as othererr: #tmp.setattr(key,"Mathematical Error around/in ${}: < %s >" % othererr) if self._debug: print(f"DEBUG {key}: Error evaluating: {resstr}\n< {othererr} >") return resstr else: return reseval except ValueError as valerr: # forced evaluation within ${} try: evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(valuesafe_priorescape,evaluator,evaluator_nocontext,raiseerror=True) except SyntaxError as synerror: if self._debug: print(f"DEBUG {key}: Error evaluating: {valuesafe_priorescape}\n< {synerror} >") return evaluate_with_placeholders(valuesafe_priorescape,evaluator,evaluator_nocontext,raiseerror=False) except Exception as othererr: if self._debug: print(f"DEBUG {key}: Error evaluating: {valuesafe_priorescape}\n< {othererr} >") return "Error in ${}: < %s >" % valerr else: return reseval except Exception as othererr: return "Error in ${}: < %s >" % othererr else: try: # reseval = eval(resstr) evaluator = SafeEvaluator(tmp) reseval = evaluate_with_placeholders(resstr,evaluator,evaluator_nocontext) except Exception as othererr: #tmp.setattr(key,"Eval Error < %s >" % othererr) if self._debug: print(f"DEBUG {key}: Error evaluating: {resstr}\n< {othererr} >") return resstr.replace("\n",",") # \n replaced by , else: return reseval # evalstr() refactored for error management def safe_evalstr(x,key=""): xeval = evalstr(x,key) if isinstance(xeval,str): try: evaluator = SafeEvaluator(tmp) return evaluate_with_placeholders(xeval,evaluator,evaluator_nocontext) except Exception as e: if self._debug: print(f"DEBUG {key}: Error evaluating '{x}': {e}") return xeval # default fallback value else: return xeval # Evaluate all DEFINITIONS for key,value in self.items(): # strings are assumed to be expressions on one single line if isinstance(value,str): tmp.setattr(key,evalstr(value,key)) elif isinstance(value,_numeric_types): # already a number if isinstance(value,list): valuelist = [safe_evalstr(x,key) if isinstance(x,str) else x for x in value] tmp.setattr(key,valuelist) else: tmp.setattr(key, value) # store the value with the key elif isinstance(value, dict): # For dictionaries, evaluate each entry using its own key (sub_key) new_dict = {} for sub_key, sub_value in value.items(): if isinstance(sub_value, str): new_dict[sub_key] = safe_evalstr(sub_value,key) elif isinstance(sub_value, list): # If an entry is a list, apply safe_evalstr to each string element within it new_dict[sub_key] = [safe_evalstr(x, sub_key) if isinstance(x, str) else x for x in sub_value] else: new_dict[sub_key] = sub_value tmp.setattr(key, new_dict) 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, fullevaluation=True)
-
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,fullevaluation=True): """ 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) evaluator = SafeEvaluator(tmp) # used when fullevaluation=True evaluator_nocontext = SafeEvaluator() # for global evaluation without context # 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") slines_priorescape = s.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: if fullevaluation: try: resstr =tmp.format(slines[i],escape=escape) except: resstr = param.safe_fstring(slines[i],tmp,varprefix="") try: #reseval = evaluator.evaluate(resstr) reseval = evaluate_with_placeholders(slines_priorescape[i],evaluator,evaluator_nocontext,raiseerror=True) slines[i] = str(reseval)+" "+comment if comment else str(reseval) except: slines[i] = resstr + comment 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 getval(self, key)
-
returns the evaluated value
Expand source code
def getval(self,key): """ returns the evaluated value """ s = self.eval() return getattr(s,key)
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(r"\$","££") # && is a placeholder escape = t!=s for k in self.keyssorted(): t = t.replace("$"+k,"${"+k+"}") if escape: t = t.replace("££",r"\$") if isinstance(s,pstr): t = pstr(t) return t, escape raise TypeError(f'the argument must be string not {type(s)}')
def toparamauto(self)
-
convert a param instance into a paramauto instance toparamauto()
Expand source code
def toparamauto(self): """ convert a param instance into a paramauto instance toparamauto() """ return paramauto(**self)
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, debug=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().__add__(p,sortdefinitions=True,raiseerror=False) self._needs_sorting = True def __iadd__(self,p): return super().__iadd__(p,sortdefinitions=True,raiseerror=False) self._needs_sorting = True def __repr__(self): self.sortdefinitions(raiseerror=False) #super(param,self).__repr__() super().__repr__() return str(self) 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 self.__dict__["_needs_sorting"] = True def sortdefinitions(self, raiseerror=True, silentmode=True): if self._needs_sorting: super().sortdefinitions(raiseerror=raiseerror, silentmode=silentmode) self._needs_sorting = False
Ancestors
- pizza.private.mstruct.param
- pizza.private.mstruct.struct
Subclasses
- pizza.dscript.lambdaScriptdata
- pizza.forcefield.parameterforcefield
- pizza.region.regiondata
Methods
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 self.__dict__["_needs_sorting"] = True
def sortdefinitions(self, raiseerror=True, silentmode=True)
-
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=True): if self._needs_sorting: super().sortdefinitions(raiseerror=raiseerror, silentmode=silentmode) self._needs_sorting = False
- 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
header(self, verbose=True,verbosity=None, style=4): Generate a formatted header for the pipescript file.
list_values(self, varname, what="all"): List all occurrences and values of a variable across the pipeline scripts.
list_multiple_values(self, varnames, what="all"): List all occurrences and values of multiple variables across the pipeline scripts.
plot_value_distribution(self, varname, what="all"): Plot the distribution of values for a given variable across specified scopes.
generate_report(self, filename, varnames=None, scopes="all"): Generate a comprehensive report for specified variables and writes it to a file.
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 header(self, verbose=True,verbosity=None, style=4): Generate a formatted header for the pipescript file. list_values(self, varname, what="all"): List all occurrences and values of a variable across the pipeline scripts. list_multiple_values(self, varnames, what="all"): List all occurrences and values of multiple variables across the pipeline scripts. plot_value_distribution(self, varname, what="all"): Plot the distribution of values for a given variable across specified scopes. generate_report(self, filename, varnames=None, scopes="all"): Generate a comprehensive report for specified variables and writes it to a file. 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 """ from pizza.dscript import dscript if isinstance(s,(pipescript,script)): dup = deepduplicate(self) return dup | s # + or | are synonyms elif isinstance(s,scriptobject): return self + s.script(printflag=self.printflag,verbose=self.verbose,verbosity=self.verbosity) elif isinstance(s,dscript): return self + s.pipescript(printflag=s.printflag,verbose=s.verbose,verbosity=s.verbosity) else: raise TypeError(f"The operand should be a pipescript/script/dscript/scriptobjectgroup and not '{type(s).__name__}'") def __iadd__(self,s): """ overload += as pipe without copy """ if isinstance(s,pipescript): return self | s # + or | are synonyms else: raise TypeError(f"The operand should be a pipescript and not '{type(s).__name__}'") 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(f"The operand should be a pipescript and not '{type(s).__name__}'") 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)): 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, 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, clean="fixing", **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. clean : "fixing" or "removing" - 'removing': Completely remove the empty step from TEMPLATE. - 'fixing': Replace the content of the empty step with a comment. **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], autorefresh=False # prevent the replacement by default values ${} ) # 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) # Clean the entries for empty templates outd.clean(verbose=verbose,behavior=clean) 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) def list_values(self, varname=None, what="all"): """ Lists all occurrences and values of a specified variable or all variables across the pipeline scripts. Parameters: - varname (str, optional): The name of the variable to search for. If None, all variables are listed. - what (str or list/tuple, optional): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. Returns: - VariableOccurrences: If varname is specified, returns a VariableOccurrences instance for that variable. - dict of VariableOccurrences: If varname is None, returns a dictionary mapping each variable name to its VariableOccurrences instance. """ # Normalize 'what' to a list for uniform processing if isinstance(what, str): if what.lower() == "all": scopes = ["static", "global", "local"] else: scopes = [what.lower()] elif isinstance(what, (list, tuple)): scopes_lower = [s.lower() for s in what] if 'all' in scopes_lower: scopes = ["static", "global", "local"] else: scopes = scopes_lower else: raise ValueError("Parameter 'what' must be a string or a list/tuple of strings.") # Initialize data structures if varname: # Single variable case if len(scopes) == 1: data = [] else: data = {} for scope in scopes: data[scope] = [] # Iterate over each script in the pipeline for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } # Check each requested scope for scope in scopes: vars_dict = scope_vars.get(scope, {}) if varname in vars_dict.keys(): value = getattr(vars_dict,varname) if len(scopes) == 1: data.append((i, value)) else: data[scope].append((i, value)) # Return a VariableOccurrences instance for the specified variable return VariableOccurrences(data, variables=varname) else: # All variables case all_vars = set() # First, collect all variable names across specified scopes and scripts for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } for scope in scopes: vars_dict = scope_vars.get(scope, {}) all_vars.update(vars_dict.keys()) # Initialize a dictionary to hold VariableOccurrences for each variable variables_data = {} for var in all_vars: var_data = {} for scope in scopes: var_data[scope] = [] variables_data[var] = var_data # Iterate again to populate the data for each variable for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } for scope in scopes: vars_dict = scope_vars.get(scope, {}) for var, value in vars_dict.items(): variables_data[var][scope].append((i, value)) # Convert each variable's data into a VariableOccurrences instance variables_occurrences = {} for var, data in variables_data.items(): variables_occurrences[var] = VariableOccurrences(data, variables=var) return variables_occurrences def list_multiple_values(self, varnames, what="all"): """ Lists all occurrences and values of multiple variables across the pipeline scripts. Parameters: - varnames (list): A list of variable names to search for. - what (str or list/tuple): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. Returns: - dict: A dictionary mapping each variable name to its VariableOccurrences object. """ if not isinstance(varnames, (list, tuple)): raise ValueError("Parameter 'varnames' must be a list or tuple of strings.") return self.list_values(varname=varnames, what=what) def plot_multiple_value_distributions(self, varnames, what="all", separate_plots=True): """ Plots the distribution of elements for multiple variables across specified scopes. Parameters: - varnames (list): A list of variable names to plot. - what (str or list/tuple): Specifies the scopes to include in the plot. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. - separate_plots (bool): If True, plots each variable in a separate subplot. If False, combines all variables in a single plot for comparison. """ if not isinstance(varnames, (list, tuple)): raise ValueError("Parameter 'varnames' must be a list or tuple of strings.") # Retrieve VariableOccurrences instances multiple_vars = self.list_multiple_values(varnames, what=what) if separate_plots: num_vars = len(multiple_vars) fig, axes = plt.subplots(num_vars, 1, figsize=(10, 5 * num_vars)) if num_vars == 1: axes = [axes] # Make it iterable for ax, (var, vo) in zip(axes, multiple_vars.items()): summary = vo.summarize()[var] for scope, details in summary.items(): elements = list(details.get('unique_elements_in_lists', [])) counts = [details['element_counts_in_lists'][elem] for elem in elements] ax.bar(elements, counts, label=scope) ax.set_xlabel('Element') ax.set_ylabel('Count') ax.set_title(f"Distribution of elements in '{var}'") ax.legend() plt.tight_layout() plt.show() else: plt.figure(figsize=(12, 8)) for var, vo in multiple_vars.items(): summary = vo.summarize()[var] for scope, details in summary.items(): elements = list(details.get('unique_elements_in_lists', [])) counts = [details['element_counts_in_lists'][elem] for elem in elements] plt.bar([f"{var}_{elem}" for elem in elements], counts, label=f"{var} - {scope}") plt.xlabel('Element') plt.ylabel('Count') plt.title("Distribution of elements in multiple variables") plt.legend() plt.xticks(rotation=45, ha='right') plt.tight_layout() plt.show() def generate_report(self, filename, varnames=None, scopes="all"): """ Generates a comprehensive report for specified variables and writes it to a file. Parameters: - filename (str): Path to the output report file. Must end with .md, .txt, or .html. - varnames (str or list/tuple, optional): Variable name(s) to include in the report. Defaults to 'all'. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'. Raises: - ValueError: If 'filename' has an unsupported extension. - Exception: For other unforeseen errors. """ # Validate filename extension _, ext = os.path.splitext(filename) ext = ext.lower() if ext not in ['.md', '.txt', '.html']: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") # Determine format based on extension if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' # Determine variables to include if varnames is None or (isinstance(varnames, (list, tuple)) and len(varnames) == 0): variables = 'all' else: variables = varnames # Can be a string or a list/tuple # Retrieve VariableOccurrences instances if variables == 'all': variables_occurrences = self.list_values(varname=None, what=scopes) else: # Normalize varnames to a list if isinstance(variables, str): variables = [variables] elif isinstance(variables, (list, tuple)): variables = list(variables) else: raise ValueError("Parameter 'varnames' must be a string or a list/tuple of strings.") variables_occurrences = {} for var in variables: vo = self.list_values(varname=var, what=scopes) if vo and var in vo.variables: variables_occurrences[var] = vo else: print(f"Warning: Variable '{var}' not found in the specified scopes.") # Initialize report content report_content = "" timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") caller = "generate_report" # Add report header if export_format == 'markdown': report_content += f"# Comprehensive Variable Report\n\n" report_content += f"**Generated on {timestamp} by {caller}**\n\n" elif export_format == 'html': # Define CSS for HTML css = """ <style> body { font-family: Arial, sans-serif; margin: 20px; } h1, h2, h3, h4, h5 { color: #333; } table { border-collapse: collapse; width: 100%; margin-bottom: 40px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #4CAF50; color: white; } tr:nth-child(even){background-color: #f2f2f2;} tr:hover {background-color: #ddd;} </style> """ report_content += f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Comprehensive Variable Report</title>\n{css}\n</head>\n<body>\n" report_content += f"<h1>Comprehensive Variable Report</h1>\n" report_content += f"<h2>Generated on {timestamp} by {caller}</h2>\n" # Assemble report content using VariableOccurrences.export() for var, vo in variables_occurrences.items(): # Export content without headers and get as string var_content = vo.export(filename=filename, # use to identify the format based on its extension scopes=scopes, variables=var, include_headers=False, return_content=True) if export_format == 'markdown': # Add variable header report_content += f"## Variable: `{var}`\n\n" report_content += var_content + "\n\n" report_content += "---\n\n" # Horizontal line between variables elif export_format == 'html': # Add variable header report_content += f"<h2>Variable: {var}</h2>\n" report_content += var_content + "\n<hr/>\n" # Horizontal line between variables # Finalize HTML content if export_format == 'html': report_content += "</body>\n</html>" # Write report to file try: with open(filename, 'w', encoding='utf-8') as file: file.write(report_content) print(f"Report successfully generated at '{filename}'.") except Exception as e: raise Exception(f"Failed to write the report to '{filename}': {e}")
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, clean='fixing', **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.
clean : "fixing" or "removing" - 'removing': Completely remove the empty step from TEMPLATE. - 'fixing': Replace the content of the empty step with a comment.
**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, clean="fixing", **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. clean : "fixing" or "removing" - 'removing': Completely remove the empty step from TEMPLATE. - 'fixing': Replace the content of the empty step with a comment. **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], autorefresh=False # prevent the replacement by default values ${} ) # 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) # Clean the entries for empty templates outd.clean(verbose=verbose,behavior=clean) return outd
def generate_report(self, filename, varnames=None, scopes='all')
-
Generates a comprehensive report for specified variables and writes it to a file.
Parameters: - filename (str): Path to the output report file. Must end with .md, .txt, or .html. - varnames (str or list/tuple, optional): Variable name(s) to include in the report. Defaults to 'all'. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'.
Raises: - ValueError: If 'filename' has an unsupported extension. - Exception: For other unforeseen errors.
Expand source code
def generate_report(self, filename, varnames=None, scopes="all"): """ Generates a comprehensive report for specified variables and writes it to a file. Parameters: - filename (str): Path to the output report file. Must end with .md, .txt, or .html. - varnames (str or list/tuple, optional): Variable name(s) to include in the report. Defaults to 'all'. - scopes (str or list/tuple, optional): 'all', a single scope string, or a list of scope strings. Defaults to 'all'. Raises: - ValueError: If 'filename' has an unsupported extension. - Exception: For other unforeseen errors. """ # Validate filename extension _, ext = os.path.splitext(filename) ext = ext.lower() if ext not in ['.md', '.txt', '.html']: raise ValueError("Unsupported file extension. Supported extensions are .md, .txt, and .html.") # Determine format based on extension if ext in ['.md', '.txt']: export_format = 'markdown' elif ext == '.html': export_format = 'html' # Determine variables to include if varnames is None or (isinstance(varnames, (list, tuple)) and len(varnames) == 0): variables = 'all' else: variables = varnames # Can be a string or a list/tuple # Retrieve VariableOccurrences instances if variables == 'all': variables_occurrences = self.list_values(varname=None, what=scopes) else: # Normalize varnames to a list if isinstance(variables, str): variables = [variables] elif isinstance(variables, (list, tuple)): variables = list(variables) else: raise ValueError("Parameter 'varnames' must be a string or a list/tuple of strings.") variables_occurrences = {} for var in variables: vo = self.list_values(varname=var, what=scopes) if vo and var in vo.variables: variables_occurrences[var] = vo else: print(f"Warning: Variable '{var}' not found in the specified scopes.") # Initialize report content report_content = "" timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") caller = "generate_report" # Add report header if export_format == 'markdown': report_content += f"# Comprehensive Variable Report\n\n" report_content += f"**Generated on {timestamp} by {caller}**\n\n" elif export_format == 'html': # Define CSS for HTML css = """ <style> body { font-family: Arial, sans-serif; margin: 20px; } h1, h2, h3, h4, h5 { color: #333; } table { border-collapse: collapse; width: 100%; margin-bottom: 40px; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #4CAF50; color: white; } tr:nth-child(even){background-color: #f2f2f2;} tr:hover {background-color: #ddd;} </style> """ report_content += f"<!DOCTYPE html>\n<html>\n<head>\n<meta charset='UTF-8'>\n<title>Comprehensive Variable Report</title>\n{css}\n</head>\n<body>\n" report_content += f"<h1>Comprehensive Variable Report</h1>\n" report_content += f"<h2>Generated on {timestamp} by {caller}</h2>\n" # Assemble report content using VariableOccurrences.export() for var, vo in variables_occurrences.items(): # Export content without headers and get as string var_content = vo.export(filename=filename, # use to identify the format based on its extension scopes=scopes, variables=var, include_headers=False, return_content=True) if export_format == 'markdown': # Add variable header report_content += f"## Variable: `{var}`\n\n" report_content += var_content + "\n\n" report_content += "---\n\n" # Horizontal line between variables elif export_format == 'html': # Add variable header report_content += f"<h2>Variable: {var}</h2>\n" report_content += var_content + "\n<hr/>\n" # Horizontal line between variables # Finalize HTML content if export_format == 'html': report_content += "</body>\n</html>" # Write report to file try: with open(filename, 'w', encoding='utf-8') as file: file.write(report_content) print(f"Report successfully generated at '{filename}'.") except Exception as e: raise Exception(f"Failed to write the report to '{filename}': {e}")
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 list_multiple_values(self, varnames, what='all')
-
Lists all occurrences and values of multiple variables across the pipeline scripts.
Parameters: - varnames (list): A list of variable names to search for. - what (str or list/tuple): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these.
Returns: - dict: A dictionary mapping each variable name to its VariableOccurrences object.
Expand source code
def list_multiple_values(self, varnames, what="all"): """ Lists all occurrences and values of multiple variables across the pipeline scripts. Parameters: - varnames (list): A list of variable names to search for. - what (str or list/tuple): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. Returns: - dict: A dictionary mapping each variable name to its VariableOccurrences object. """ if not isinstance(varnames, (list, tuple)): raise ValueError("Parameter 'varnames' must be a list or tuple of strings.") return self.list_values(varname=varnames, what=what)
def list_values(self, varname=None, what='all')
-
Lists all occurrences and values of a specified variable or all variables across the pipeline scripts.
Parameters: - varname (str, optional): The name of the variable to search for. If None, all variables are listed. - what (str or list/tuple, optional): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these.
Returns: - VariableOccurrences: If varname is specified, returns a VariableOccurrences instance for that variable. - dict of VariableOccurrences: If varname is None, returns a dictionary mapping each variable name to its VariableOccurrences instance.
Expand source code
def list_values(self, varname=None, what="all"): """ Lists all occurrences and values of a specified variable or all variables across the pipeline scripts. Parameters: - varname (str, optional): The name of the variable to search for. If None, all variables are listed. - what (str or list/tuple, optional): Specifies the scopes to search in. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. Returns: - VariableOccurrences: If varname is specified, returns a VariableOccurrences instance for that variable. - dict of VariableOccurrences: If varname is None, returns a dictionary mapping each variable name to its VariableOccurrences instance. """ # Normalize 'what' to a list for uniform processing if isinstance(what, str): if what.lower() == "all": scopes = ["static", "global", "local"] else: scopes = [what.lower()] elif isinstance(what, (list, tuple)): scopes_lower = [s.lower() for s in what] if 'all' in scopes_lower: scopes = ["static", "global", "local"] else: scopes = scopes_lower else: raise ValueError("Parameter 'what' must be a string or a list/tuple of strings.") # Initialize data structures if varname: # Single variable case if len(scopes) == 1: data = [] else: data = {} for scope in scopes: data[scope] = [] # Iterate over each script in the pipeline for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } # Check each requested scope for scope in scopes: vars_dict = scope_vars.get(scope, {}) if varname in vars_dict.keys(): value = getattr(vars_dict,varname) if len(scopes) == 1: data.append((i, value)) else: data[scope].append((i, value)) # Return a VariableOccurrences instance for the specified variable return VariableOccurrences(data, variables=varname) else: # All variables case all_vars = set() # First, collect all variable names across specified scopes and scripts for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } for scope in scopes: vars_dict = scope_vars.get(scope, {}) all_vars.update(vars_dict.keys()) # Initialize a dictionary to hold VariableOccurrences for each variable variables_data = {} for var in all_vars: var_data = {} for scope in scopes: var_data[scope] = [] variables_data[var] = var_data # Iterate again to populate the data for each variable for i, script in enumerate(self.listscript): # Retrieve variables for each scope static_vars = self.listUSER[i] if i < len(self.listUSER) else {} global_vars = getattr(script, 'DEFINITIONS', {}) local_vars = getattr(script, 'USER', {}) scope_vars = { "static": static_vars, "global": global_vars, "local": local_vars } for scope in scopes: vars_dict = scope_vars.get(scope, {}) for var, value in vars_dict.items(): variables_data[var][scope].append((i, value)) # Convert each variable's data into a VariableOccurrences instance variables_occurrences = {} for var, data in variables_data.items(): variables_occurrences[var] = VariableOccurrences(data, variables=var) return variables_occurrences
def plot_multiple_value_distributions(self, varnames, what='all', separate_plots=True)
-
Plots the distribution of elements for multiple variables across specified scopes.
Parameters: - varnames (list): A list of variable names to plot. - what (str or list/tuple): Specifies the scopes to include in the plot. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. - separate_plots (bool): If True, plots each variable in a separate subplot. If False, combines all variables in a single plot for comparison.
Expand source code
def plot_multiple_value_distributions(self, varnames, what="all", separate_plots=True): """ Plots the distribution of elements for multiple variables across specified scopes. Parameters: - varnames (list): A list of variable names to plot. - what (str or list/tuple): Specifies the scopes to include in the plot. Can be "static", "global", "local", "all", or a list/tuple of any combination of these. - separate_plots (bool): If True, plots each variable in a separate subplot. If False, combines all variables in a single plot for comparison. """ if not isinstance(varnames, (list, tuple)): raise ValueError("Parameter 'varnames' must be a list or tuple of strings.") # Retrieve VariableOccurrences instances multiple_vars = self.list_multiple_values(varnames, what=what) if separate_plots: num_vars = len(multiple_vars) fig, axes = plt.subplots(num_vars, 1, figsize=(10, 5 * num_vars)) if num_vars == 1: axes = [axes] # Make it iterable for ax, (var, vo) in zip(axes, multiple_vars.items()): summary = vo.summarize()[var] for scope, details in summary.items(): elements = list(details.get('unique_elements_in_lists', [])) counts = [details['element_counts_in_lists'][elem] for elem in elements] ax.bar(elements, counts, label=scope) ax.set_xlabel('Element') ax.set_ylabel('Count') ax.set_title(f"Distribution of elements in '{var}'") ax.legend() plt.tight_layout() plt.show() else: plt.figure(figsize=(12, 8)) for var, vo in multiple_vars.items(): summary = vo.summarize()[var] for scope, details in summary.items(): elements = list(details.get('unique_elements_in_lists', [])) counts = [details['element_counts_in_lists'][elem] for elem in elements] plt.bar([f"{var}_{elem}" for elem in elements], counts, label=f"{var} - {scope}") plt.xlabel('Element') plt.ylabel('Count') plt.title("Distribution of elements in multiple variables") plt.legend() plt.xticks(rotation=45, ha='right') plt.tight_layout() plt.show()
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. Known Issues for indexed variables ---------------------------------- List and tupples used indexed in templates, such as varlist[1], are not converted into strings so that each element can be considered as a variable and accessed as ${varlist[1]}. If varlist is also used in full, such as ${varlist}. Then it is preferable to define varlist as a string: "![v1,v2,...]" where v1,v2,v3 can be int, float, static str or evaluable str (i.e., subjected to nested evaluation). The control character ! in lists, such as in var=![v1,v2,v3] preserves the Pythonic definition by do() at later stages during the evaluation so that its content can be indexed. """ printflag = self.printflag if printflag is None else printflag verbose = self.verbose if verbose is None else verbose inputs = self.DEFINITIONS + self.USER usedvariables = self.detect_variables(with_index=False,only_indexed=False) variables_used_with_index = self.detect_variables(with_index=False,only_indexed=True) usedvariables_withoutindex = [ var for var in usedvariables if var not in variables_used_with_index ] for k in inputs.keys(): if k in usedvariables_withoutindex: 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(f"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, with_index=False, only_indexed=False): """ Detects variables in the content of the template using an extended pattern to include indexed variables (e.g., ${var[i]}) if `with_index` is True. Parameters: ----------- with_index : bool, optional If True, include indexed variables with their indices (e.g., ${var[i]}). Default is False. only_indexed : bool, optional If True, target only variables that are used with an index (e.g., ${var[i]}). Default is False. Returns: -------- list A list of unique variable names detected in the content based on the flags. """ # Regular expression to match variables with optional indexing variable_pattern = re.compile(r'\$\{(\w+)(\[\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 = set() for line in lines: matches = variable_pattern.findall(line) for match in matches: variable_name = match[0] # Base variable name index = match[1] # Optional index (e.g., '[i]') if only_indexed and not index: continue # Skip non-indexed variables if targeting only indexed ones if with_index and index: detected_vars.add(f"{variable_name}{index}") # Include the full indexed variable elif not with_index: detected_vars.add(variable_name) # Include only the base variable # 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, with_index=False, only_indexed=False)
-
Detects variables in the content of the template using an extended pattern to include indexed variables (e.g., ${var[i]}) if
with_index
is True.Parameters:
with_index : bool, optional If True, include indexed variables with their indices (e.g., ${var[i]}). Default is False. only_indexed : bool, optional If True, target only variables that are used with an index (e.g., ${var[i]}). Default is False.
Returns:
list A list of unique variable names detected in the content based on the flags.
Expand source code
def detect_variables(self, with_index=False, only_indexed=False): """ Detects variables in the content of the template using an extended pattern to include indexed variables (e.g., ${var[i]}) if `with_index` is True. Parameters: ----------- with_index : bool, optional If True, include indexed variables with their indices (e.g., ${var[i]}). Default is False. only_indexed : bool, optional If True, target only variables that are used with an index (e.g., ${var[i]}). Default is False. Returns: -------- list A list of unique variable names detected in the content based on the flags. """ # Regular expression to match variables with optional indexing variable_pattern = re.compile(r'\$\{(\w+)(\[\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 = set() for line in lines: matches = variable_pattern.findall(line) for match in matches: variable_name = match[0] # Base variable name index = match[1] # Optional index (e.g., '[i]') if only_indexed and not index: continue # Skip non-indexed variables if targeting only indexed ones if with_index and index: detected_vars.add(f"{variable_name}{index}") # Include the full indexed variable elif not with_index: detected_vars.add(variable_name) # Include only the base variable # 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. Known Issues for indexed variables ---------------------------------- List and tupples used indexed in templates, such as varlist[1], are not converted into strings so that each element can be considered as a variable and accessed as ${varlist[1]}. If varlist is also used in full, such as ${varlist}. Then it is preferable to define varlist as a string: "![v1,v2,...]" where v1,v2,v3 can be int, float, static str or evaluable str (i.e., subjected to nested evaluation). The control character ! in lists, such as in var=![v1,v2,v3] preserves the Pythonic definition by do() at later stages during the evaluation so that its content can be indexed.
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. Known Issues for indexed variables ---------------------------------- List and tupples used indexed in templates, such as varlist[1], are not converted into strings so that each element can be considered as a variable and accessed as ${varlist[1]}. If varlist is also used in full, such as ${varlist}. Then it is preferable to define varlist as a string: "![v1,v2,...]" where v1,v2,v3 can be int, float, static str or evaluable str (i.e., subjected to nested evaluation). The control character ! in lists, such as in var=![v1,v2,v3] preserves the Pythonic definition by do() at later stages during the evaluation so that its content can be indexed. """ printflag = self.printflag if printflag is None else printflag verbose = self.verbose if verbose is None else verbose inputs = self.DEFINITIONS + self.USER usedvariables = self.detect_variables(with_index=False,only_indexed=False) variables_used_with_index = self.detect_variables(with_index=False,only_indexed=True) usedvariables_withoutindex = [ var for var in usedvariables if var not in variables_used_with_index ] for k in inputs.keys(): if k in usedvariables_withoutindex: 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, debug=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, use debug=True to report eval errors
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
- pizza.forcefield.ulsphalone
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 (debug=False, **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__
).<<
: Import values from another structure (__lshift__
)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. importfrom()
Import undefined values from another struct or dict. 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. validkeys()
Return valid keys 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, use debug=True to report eval errors
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__`). - `<<`: Import values from another structure (`__lshift__`) - `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. | | `importfrom()` | Import undefined values from another struct or dict. | | `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. | | `validkeys()` | Return valid keys | | `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 _precision = 4 _needs_sorting = 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','_debug','_precision','_needs_sorting'} # used by keys() and len() # Methods def __init__(self,debug=False,**kwargs): """ constructor, use debug=True to report eval errors""" # Optionally extend _excludedattr here self._excludedattr = self._excludedattr | {'_excludedattr', '_type', '_fulltype','_ftype'} # addition 2024-10-11 self._debug = debug 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_types): keys = [keys] if not isinstance(values,_list_types): 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): out = struct.fromkeysvalues(self.keys()[idx], self.values()[idx]) if isinstance(self,paramauto): return paramauto(**out) elif isinstance(self,param): return param(**out) else: return out 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): if -nk <= i < nk: # Allow standard Python negative indexing i = i % nk # Convert negative index to positive equivalent s.setattr(k[i],v[i]) else: raise IndexError(f"idx must contain integers in range [-{nk}, {nk-1}], not {i}") elif isinstance(i,str): if i in self: s.setattr(i, self.getattr(i)) else: raise KeyError((f'idx "{idx}" is not a valid key')) else: TypeError("idx must contain only integers or strings") 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 slice (%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 two structure objects, with precedence as follows: paramauto > param > struct In c = a + b, if b has a higher precedence than a then c will be of b's class, otherwise it will be of a's class. The new instance is created by copying the fields from the left-hand operand (a) and then updating with the fields from the right-hand operand (b). If self or s is of class paramauto, the current state of _needs_sorting is propagated but not forced to be true. """ if not isinstance(s, struct): raise TypeError(f"the second operand must be {self._type}") # Define a helper to assign a precedence value. def get_precedence(obj): if isinstance(obj, paramauto): return 2 elif isinstance(obj, param): return 1 elif isinstance(obj, struct): return 0 else: return 0 # fallback for unknown derivations # current classes leftprecedence = get_precedence(self) rightprecedence = get_precedence(s) # Determine which class to use for the duplicate. # If s (b) has a higher precedence than self (a), use s's class; otherwise, use self's. hi_class = self.__class__ if leftprecedence >= rightprecedence else s.__class__ # Create a new instance of the chosen class by copying self's fields. dup = hi_class(**self) # Update with the fields from s. dup.update(**s) if sortdefinitions: # defer sorting by preserving the state of _needs_sorting if leftprecedence < rightprecedence == 2: # left is promoted dup._needs_sorting = s._needs_sorting elif rightprecedence < leftprecedence == 2: # right is promoted dup._needs_sorting = self._needs_sorting elif leftprecedence == rightprecedence == 2: # left and right are equivalent dup._needs_sorting = self._needs_sorting or s._needs_sorting # 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.update(**s) if sortdefinitions: self._needs_sorting = True # 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: numfmt = f".{self._precision}g" 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,_numeric_types): # old code (removed on 2025-01-18) # 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)) if isinstance(value,np.ndarray): print(fmt % key, struct.format_array(value,numfmt=numfmt)) else: print(fmt % key,self.dispmax(value)) elif isinstance(value,struct): print(fmt % key,self.dispmax(value.__str__())) elif isinstance(value,(type,dict)): 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: calcvalue =tmp.getattr(key) if isinstance(calcvalue, str) and "error" in calcvalue.lower(): print(fmteval % "",calcvalue) else: if isinstance(calcvalue,np.ndarray): print(fmteval % "", struct.format_array(calcvalue,numfmt=numfmt)) else: print(fmteval % "",self.dispmax(calcvalue)) elif isinstance(value,list): calcvalue =tmp.getattr(key) print(fmteval % "",self.dispmax(str(calcvalue))) 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 fields using {field} as placeholders. Handles expressions like ${variable1}. Args: s (str): The input string to format. escape (bool): If True, prevents replacing '${' with '{'. raiseerror (bool): If True, raises errors for missing fields. Note: NumPy vectors and matrices are converted into their text representation (default behavior) If expressions such ${var[1,2]} are used an error is expected, the original content will be used instead Returns: str: The formatted string. """ tmp = self.np2str() if raiseerror: try: if escape: try: # we try to evaluate with all np objects converted in to strings (default) return s.format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.format_map(AttrErrorDict(self.__dict__)) else: try: # we try to evaluate with all np objects converted in to strings (default) return s.replace("${", "{").format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.replace("${", "{").format_map(AttrErrorDict(self.__dict__)) except AttributeError as attr_err: # Handle AttributeError for expressions with operators s_ = s.replace("{", "${") if self._debug: print(f"WARNING: the {self._ftype} {attr_err} is undefined in '{s_}'") return s_ # Revert to using '${' for unresolved expressions except IndexError as idx_err: s_ = s.replace("{", "${") if self._debug: print(f"Index Error {idx_err} in '{s_}'") raise IndexError from idx_err except Exception as other_err: s_ = s.replace("{", "${") raise RuntimeError from other_err else: if escape: try: # we try to evaluate with all np objects converted in to strings (default) return s.format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.format_map(AttrErrorDict(self.__dict__)) else: try: # we try to evaluate with all np objects converted in to strings (default) return s.replace("${", "{").format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.replace("${", "{").format_map(AttrErrorDict(self.__dict__)) def format_legacy(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, printout=False): """ Generate Python code of the equivalent structure. This method converts the current structure (an instance of `param`, `paramauto`, or `struct`) into Python code that, when executed, recreates an equivalent structure. The generated code is formatted with one field per line. By default (when `printout` is False), the generated code is returned as a raw string that starts directly with, for example, `param(` (or `paramauto(` or `struct(`), with no "X = " prefix or leading newline. When `printout` is True, the generated code is printed to standard output and includes a prefix "X = " to indicate the variable name. Parameters: printout (bool): If True, the generated code is printed to standard output with the "X = " prefix. If False (default), the code is returned as a raw string starting with, e.g., `param(`. Returns: str: The generated Python code representing the structure (regardless of whether it was printed). """ nk = len(self) tmp = self.np2str() # Compute the field format based on the maximum key length (with a minimum width of 10) fmt = "%%%ss =" % max(10, max([len(k) for k in self.keys()]) + 2) # Determine the appropriate class string for the current instance. if isinstance(self, param): classstr = "param" elif 'paramauto' in globals() and isinstance(self, paramauto): classstr = "paramauto" else: classstr = "struct" lines = [] if nk == 0: # For an empty structure. if printout: lines.append(f"X = {classstr}()") else: lines.append(f"{classstr}()") else: # Header: include "X = " only if printing. if printout: header = f"X = {classstr}(" else: header = f"{classstr}(" lines.append(header) # Iterate over keys to generate each field line. for i, k in enumerate(self.keys()): v = getattr(self, k) if isinstance(v, np.ndarray): vtmp = getattr(tmp, k) field = fmt % k + " " + vtmp elif isinstance(v, (int, float)) or v is None: field = fmt % k + " " + str(v) elif isinstance(v, str): field = fmt % k + " " + f'"{v}"' elif isinstance(v, (list, tuple, dict)): field = fmt % k + " " + str(v) else: field = fmt % k + " " + "/* unsupported type */" # Append a comma after each field except the last one. if i < nk - 1: field += "," lines.append(field) # Create a closing line that aligns the closing parenthesis. closing_line = fmt[:-1] % ")" lines.append(closing_line) result = "\n".join(lines) if printout: print(result) return None return result # 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.") # Convert to static if needed if isinstance(p,(param,paramauto)): tmp = self.tostatic() else: tmp = self # 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 tmp.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}'") # A la Matlab display method of vectors, matrices and ND-arrays @staticmethod def format_array(value,numfmt=".4g"): """ Format NumPy array for display with distinctions for scalars, row/column vectors, and ND arrays. Recursively formats multi-dimensional arrays without introducing unwanted commas. Args: value (np.ndarray): The NumPy array to format. numfmt: numeric format to be used for the string conversion (default=".4g") Returns: str: A formatted string representation of the array. """ dtype_str = { np.float64: "double", np.float32: "single", np.int32: "int32", np.int64: "int64", np.complex64: "complex single", np.complex128: "complex double", }.get(value.dtype.type, str(value.dtype)) # Default to dtype name if not in the map max_display = 10 # Maximum number of elements to display def format_recursive(arr): """ Recursively formats the array based on its dimensions. Args: arr (np.ndarray): The array or sub-array to format. Returns: str: Formatted string of the array. """ if arr.ndim == 0: return f"{arr.item()}" if arr.ndim == 1: if len(arr) <= max_display: return "[" + " ".join(f"{v:{numfmt}}" for v in arr) + "]" else: return f"[{len(arr)} elements]" if arr.ndim == 2: if arr.shape[1] == 1: # Column vector if arr.shape[0] <= max_display: return "[" + " ".join(f"{v[0]:{numfmt}}" for v in arr) + "]T" else: return f"[{arr.shape[0]}×1 vector]" elif arr.shape[0] == 1: # Row vector if arr.shape[1] <= max_display: return "[" + " ".join(f"{v:{numfmt}}" for v in arr[0]) + "]" else: return f"[1×{arr.shape[1]} vector]" else: # General matrix return f"[{arr.shape[0]}×{arr.shape[1]} matrix]" # For higher dimensions shape_str = "×".join(map(str, arr.shape)) if arr.size <= max_display: # Show full content if arr.ndim > 2: # Represent multi-dimensional arrays with nested brackets return "[" + " ".join(format_recursive(subarr) for subarr in arr) + f"] ({shape_str} {dtype_str})" return f"[{shape_str} array ({dtype_str})]" if value.size == 0: return "[]" if value.ndim == 0 or value.size == 1: return f"{value.item()} ({dtype_str})" if value.ndim == 1 or value.ndim == 2: # Use existing logic for vectors and matrices if value.ndim == 1: if len(value) <= max_display: formatted = "[" + " ".join(f"{v:{numfmt}}" for v in value) + f"] ({dtype_str})" else: formatted = f"[{len(value)}×1 {dtype_str}]" elif value.ndim == 2: rows, cols = value.shape if cols == 1: # Column vector if rows <= max_display: formatted = "[" + " ".join(f"{v[0]:{numfmt}}" for v in value) + f"]T ({dtype_str})" else: formatted = f"[{rows}×1 {dtype_str}]" elif rows == 1: # Row vector if cols <= max_display: formatted = "[" + " ".join(f"{v:{numfmt}}" for v in value[0]) + f"] ({dtype_str})" else: formatted = f"[1×{cols} {dtype_str}]" else: # General matrix formatted = f"[{rows}×{cols} {dtype_str}]" return formatted # For higher-dimensional arrays if value.size <= max_display: formatted = format_recursive(value) else: shape_str = "×".join(map(str, value.shape)) formatted = f"[{shape_str} array ({dtype_str})]" return formatted # convert all NumPy entries to "nestable" expressions def np2str(self): """ Convert all NumPy entries of s into their string representations, handling both lists and dictionaries. """ out = struct() def format_numpy_result(value): """ Converts a NumPy array or scalar into a string representation: - Scalars and single-element arrays (any number of dimensions) are returned as scalars without brackets. - Arrays with more than one element are formatted with proper nesting and commas to make them valid `np.array()` inputs. - If the value is a list or dict, the conversion is applied recursively. - Non-ndarray inputs that are not list/dict are returned without modification. Args: value (np.ndarray, scalar, list, dict, or other): The value to format. Returns: str, list, dict, or original type: A properly formatted string for NumPy arrays/scalars, a recursively converted list/dict, or the original value. """ if isinstance(value, dict): # Recursively process each key in the dictionary. new_dict = {} for k, v in value.items(): new_dict[k] = format_numpy_result(v) return new_dict elif isinstance(value, list): # Recursively process each element in the list. return [format_numpy_result(x) for x in value] elif isinstance(value, tuple): return tuple(format_numpy_result(x) for x in value) elif isinstance(value, struct): return value.npstr() elif np.isscalar(value): # For scalars: if numeric, use str() to avoid extra quotes. if isinstance(value, (int, float, complex, str)) or value is None: return value else: return repr(value) elif isinstance(value, np.ndarray): # Check if the array has exactly one element. if value.size == 1: # Extract the scalar value. return repr(value.item()) # Convert the array to a nested list. nested_list = value.tolist() # Recursively format the nested list into a valid string. def list_to_string(lst): if isinstance(lst, list): return "[" + ",".join(list_to_string(item) for item in lst) + "]" else: return repr(lst) return list_to_string(nested_list) else: # Return the input unmodified if not a NumPy array, list, dict, or scalar. return str(value) # str() preferred over repr() for concision # Process all entries in self. for key, value in self.items(): out.setattr(key, format_numpy_result(value)) return out # minimal replacement of placeholders by numbers or their string representations def numrepl(self, text): r""" Replace all placeholders of the form ${key} in the given text by the corresponding numeric value from the instance fields, under the following conditions: 1. 'key' must be a valid field in self (i.e., if key in self). 2. The value corresponding to 'key' is either: - an int, - a float, or - a string that represents a valid number (e.g., "1" or "1.0"). Only when these conditions are met, the placeholder is substituted. The conversion preserves the original type: if the stored value is int, then the substitution will be done as an integer (e.g., 1 and not 1.0). Otherwise, if it is a float then it will be substituted as a float. Any placeholder for which the above conditions are not met remains unchanged. Placeholders are recognized by the pattern "${<key>}" where <key> is captured as all text until the next "}" (optionally allowing whitespace inside the braces). """ # Pattern: match "${", then optional whitespace, capture all characters until "}", # then optional whitespace, then "}". placeholder_pattern = re.compile(r"\$\{\s*([^}]+?)\s*\}") def replace_match(match): key = match.group(1) # Check if the key exists in self. if key in self: value = self[key] # If the value is already numeric, substitute directly. if isinstance(value, (int, float)): return str(value) # If the value is a string, try to interpret it as a numeric value. elif isinstance(value, str): s = value.strip() # Check if s is a valid integer representation. if re.fullmatch(r"[+-]?\d+", s): try: num = int(s) return str(num) except ValueError: # Should not occur because the regex already matched. return match.group(0) # Check if s is a valid float representation (including scientific notation). elif re.fullmatch(r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?", s): try: num = float(s) return str(num) except ValueError: return match.group(0) # If key not in self or value is not numeric (or numeric string), leave placeholder intact. return match.group(0) # Replace all placeholders in the text using the replacer function. return placeholder_pattern.sub(replace_match, text) # import method def importfrom(self, s, nonempty=True, replacedefaultvar=True): """ Import values from 's' into self according to the following rules: - Only fields that already exist in self are considered. - If s is a dictionary, it is converted to a struct via struct(**s). - If the current value of a field in self is empty (None, "", [] or ()), then that field is updated from s. - If nonempty is True (default), then only non-empty values from s are imported. - If replacedefaultvar is True (default), then if a field in self exactly equals "${key}" (with key being the field name), it is replaced by the corresponding value from s if it is empty. - Raises a TypeError if s is not a dict or struct (i.e. if it doesn’t support keys()). """ # If s is a dictionary, convert it to a struct instance. if isinstance(s, dict): s = struct(**s) elif not hasattr(s, "keys"): raise TypeError(f"s must be a struct or a dictionary not a type {type(s).__name__}") for key in self.keys(): if key in s: s_value = getattr(s, key) current_value = getattr(self, key) if is_empty(current_value) or (replacedefaultvar and current_value == "${" + key + "}"): if nonempty: if not is_empty(s_value): setattr(self, key, s_value) else: setattr(self, key, s_value) # importfrom with copy def __lshift__(self, other): """ Allows the syntax: s = s1 << s2 where a new instance is created as a copy of s1 (preserving its type, whether struct, param, or paramauto) and then updated with the values from s2 using importfrom. """ # Create a new instance preserving the type of self. new_instance = type(self)(**{k: getattr(self, k) for k in self.keys()}) # Import values from other (s2) into the new instance. new_instance.importfrom(other) return new_instance # returns only valid keys def validkeys(self, list_of_keys): """ Validate and return the subset of keys from the provided list that are valid in the instance. Parameters: ----------- list_of_keys : list A list of keys (as strings) to check against the instance’s attributes. Returns: -------- list A list of keys from list_of_keys that are valid (i.e., exist as attributes in the instance). Raises: ------- TypeError If list_of_keys is not a list or if any element in list_of_keys is not a string. Example: -------- >>> s = struct() >>> s.foo = 42 >>> s.bar = "hello" >>> valid = s.validkeys(["foo", "bar", "baz"]) >>> print(valid) # Output: ['foo', 'bar'] assuming 'baz' is not defined in s """ # Check that list_of_keys is a list if not isinstance(list_of_keys, list): raise TypeError("list_of_keys must be a list") # Check that every entry in the list is a string for key in list_of_keys: if not isinstance(key, str): raise TypeError("Each key in list_of_keys must be a string") # Assuming valid keys are those present in the instance's __dict__ return [key for key in list_of_keys if key in self]
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 format_array(value, numfmt='.4g')
-
Format NumPy array for display with distinctions for scalars, row/column vectors, and ND arrays. Recursively formats multi-dimensional arrays without introducing unwanted commas.
Args
value
:np.ndarray
- The NumPy array to format.
numfmt
- numeric format to be used for the string conversion (default=".4g")
Returns
str
- A formatted string representation of the array.
Expand source code
@staticmethod def format_array(value,numfmt=".4g"): """ Format NumPy array for display with distinctions for scalars, row/column vectors, and ND arrays. Recursively formats multi-dimensional arrays without introducing unwanted commas. Args: value (np.ndarray): The NumPy array to format. numfmt: numeric format to be used for the string conversion (default=".4g") Returns: str: A formatted string representation of the array. """ dtype_str = { np.float64: "double", np.float32: "single", np.int32: "int32", np.int64: "int64", np.complex64: "complex single", np.complex128: "complex double", }.get(value.dtype.type, str(value.dtype)) # Default to dtype name if not in the map max_display = 10 # Maximum number of elements to display def format_recursive(arr): """ Recursively formats the array based on its dimensions. Args: arr (np.ndarray): The array or sub-array to format. Returns: str: Formatted string of the array. """ if arr.ndim == 0: return f"{arr.item()}" if arr.ndim == 1: if len(arr) <= max_display: return "[" + " ".join(f"{v:{numfmt}}" for v in arr) + "]" else: return f"[{len(arr)} elements]" if arr.ndim == 2: if arr.shape[1] == 1: # Column vector if arr.shape[0] <= max_display: return "[" + " ".join(f"{v[0]:{numfmt}}" for v in arr) + "]T" else: return f"[{arr.shape[0]}×1 vector]" elif arr.shape[0] == 1: # Row vector if arr.shape[1] <= max_display: return "[" + " ".join(f"{v:{numfmt}}" for v in arr[0]) + "]" else: return f"[1×{arr.shape[1]} vector]" else: # General matrix return f"[{arr.shape[0]}×{arr.shape[1]} matrix]" # For higher dimensions shape_str = "×".join(map(str, arr.shape)) if arr.size <= max_display: # Show full content if arr.ndim > 2: # Represent multi-dimensional arrays with nested brackets return "[" + " ".join(format_recursive(subarr) for subarr in arr) + f"] ({shape_str} {dtype_str})" return f"[{shape_str} array ({dtype_str})]" if value.size == 0: return "[]" if value.ndim == 0 or value.size == 1: return f"{value.item()} ({dtype_str})" if value.ndim == 1 or value.ndim == 2: # Use existing logic for vectors and matrices if value.ndim == 1: if len(value) <= max_display: formatted = "[" + " ".join(f"{v:{numfmt}}" for v in value) + f"] ({dtype_str})" else: formatted = f"[{len(value)}×1 {dtype_str}]" elif value.ndim == 2: rows, cols = value.shape if cols == 1: # Column vector if rows <= max_display: formatted = "[" + " ".join(f"{v[0]:{numfmt}}" for v in value) + f"]T ({dtype_str})" else: formatted = f"[{rows}×1 {dtype_str}]" elif rows == 1: # Row vector if cols <= max_display: formatted = "[" + " ".join(f"{v:{numfmt}}" for v in value[0]) + f"] ({dtype_str})" else: formatted = f"[1×{cols} {dtype_str}]" else: # General matrix formatted = f"[{rows}×{cols} {dtype_str}]" return formatted # For higher-dimensional arrays if value.size <= max_display: formatted = format_recursive(value) else: shape_str = "×".join(map(str, value.shape)) formatted = f"[{shape_str} array ({dtype_str})]" return formatted
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_types): keys = [keys] if not isinstance(values,_list_types): 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 fields using {field} as placeholders. Handles expressions like ${variable1}.
Args
s
:str
- The input string to format.
escape
:bool
- If True, prevents replacing '${' with '{'.
raiseerror
:bool
- If True, raises errors for missing fields.
Note
NumPy vectors and matrices are converted into their text representation (default behavior) If expressions such ${var[1,2]} are used an error is expected, the original content will be used instead
Returns
str
- The formatted string.
Expand source code
def format(self, s, escape=False, raiseerror=True): """ Format a string with fields using {field} as placeholders. Handles expressions like ${variable1}. Args: s (str): The input string to format. escape (bool): If True, prevents replacing '${' with '{'. raiseerror (bool): If True, raises errors for missing fields. Note: NumPy vectors and matrices are converted into their text representation (default behavior) If expressions such ${var[1,2]} are used an error is expected, the original content will be used instead Returns: str: The formatted string. """ tmp = self.np2str() if raiseerror: try: if escape: try: # we try to evaluate with all np objects converted in to strings (default) return s.format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.format_map(AttrErrorDict(self.__dict__)) else: try: # we try to evaluate with all np objects converted in to strings (default) return s.replace("${", "{").format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.replace("${", "{").format_map(AttrErrorDict(self.__dict__)) except AttributeError as attr_err: # Handle AttributeError for expressions with operators s_ = s.replace("{", "${") if self._debug: print(f"WARNING: the {self._ftype} {attr_err} is undefined in '{s_}'") return s_ # Revert to using '${' for unresolved expressions except IndexError as idx_err: s_ = s.replace("{", "${") if self._debug: print(f"Index Error {idx_err} in '{s_}'") raise IndexError from idx_err except Exception as other_err: s_ = s.replace("{", "${") raise RuntimeError from other_err else: if escape: try: # we try to evaluate with all np objects converted in to strings (default) return s.format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.format_map(AttrErrorDict(self.__dict__)) else: try: # we try to evaluate with all np objects converted in to strings (default) return s.replace("${", "{").format_map(AttrErrorDict(tmp.__dict__)) except: # if an error occurs, we use the orginal content return s.replace("${", "{").format_map(AttrErrorDict(self.__dict__))
def format_legacy(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_legacy(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, printout=False)
-
Generate Python code of the equivalent structure.
This method converts the current structure (an instance of
param
,paramauto
, orstruct
) into Python code that, when executed, recreates an equivalent structure. The generated code is formatted with one field per line.By default (when
printout
is False), the generated code is returned as a raw string that starts directly with, for example,param(
(orparamauto(
orstruct(
), with no "X = " prefix or leading newline. Whenprintout
is True, the generated code is printed to standard output and includes a prefix "X = " to indicate the variable name.Parameters
printout (bool): If True, the generated code is printed to standard output with the "X = " prefix. If False (default), the code is returned as a raw string starting with, e.g.,
param(
.Returns
str
- The generated Python code representing the structure (regardless of whether it was printed).
Expand source code
def generator(self, printout=False): """ Generate Python code of the equivalent structure. This method converts the current structure (an instance of `param`, `paramauto`, or `struct`) into Python code that, when executed, recreates an equivalent structure. The generated code is formatted with one field per line. By default (when `printout` is False), the generated code is returned as a raw string that starts directly with, for example, `param(` (or `paramauto(` or `struct(`), with no "X = " prefix or leading newline. When `printout` is True, the generated code is printed to standard output and includes a prefix "X = " to indicate the variable name. Parameters: printout (bool): If True, the generated code is printed to standard output with the "X = " prefix. If False (default), the code is returned as a raw string starting with, e.g., `param(`. Returns: str: The generated Python code representing the structure (regardless of whether it was printed). """ nk = len(self) tmp = self.np2str() # Compute the field format based on the maximum key length (with a minimum width of 10) fmt = "%%%ss =" % max(10, max([len(k) for k in self.keys()]) + 2) # Determine the appropriate class string for the current instance. if isinstance(self, param): classstr = "param" elif 'paramauto' in globals() and isinstance(self, paramauto): classstr = "paramauto" else: classstr = "struct" lines = [] if nk == 0: # For an empty structure. if printout: lines.append(f"X = {classstr}()") else: lines.append(f"{classstr}()") else: # Header: include "X = " only if printing. if printout: header = f"X = {classstr}(" else: header = f"{classstr}(" lines.append(header) # Iterate over keys to generate each field line. for i, k in enumerate(self.keys()): v = getattr(self, k) if isinstance(v, np.ndarray): vtmp = getattr(tmp, k) field = fmt % k + " " + vtmp elif isinstance(v, (int, float)) or v is None: field = fmt % k + " " + str(v) elif isinstance(v, str): field = fmt % k + " " + f'"{v}"' elif isinstance(v, (list, tuple, dict)): field = fmt % k + " " + str(v) else: field = fmt % k + " " + "/* unsupported type */" # Append a comma after each field except the last one. if i < nk - 1: field += "," lines.append(field) # Create a closing line that aligns the closing parenthesis. closing_line = fmt[:-1] % ")" lines.append(closing_line) result = "\n".join(lines) if printout: print(result) return None return result
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 importfrom(self, s, nonempty=True, replacedefaultvar=True)
-
Import values from 's' into self according to the following rules:
- Only fields that already exist in self are considered.
- If s is a dictionary, it is converted to a struct via struct(**s).
- If the current value of a field in self is empty (None, "", [] or ()), then that field is updated from s.
- If nonempty is True (default), then only non-empty values from s are imported.
- If replacedefaultvar is True (default), then if a field in self exactly equals "${key}" (with key being the field name), it is replaced by the corresponding value from s if it is empty.
- Raises a TypeError if s is not a dict or struct (i.e. if it doesn’t support keys()).
Expand source code
def importfrom(self, s, nonempty=True, replacedefaultvar=True): """ Import values from 's' into self according to the following rules: - Only fields that already exist in self are considered. - If s is a dictionary, it is converted to a struct via struct(**s). - If the current value of a field in self is empty (None, "", [] or ()), then that field is updated from s. - If nonempty is True (default), then only non-empty values from s are imported. - If replacedefaultvar is True (default), then if a field in self exactly equals "${key}" (with key being the field name), it is replaced by the corresponding value from s if it is empty. - Raises a TypeError if s is not a dict or struct (i.e. if it doesn’t support keys()). """ # If s is a dictionary, convert it to a struct instance. if isinstance(s, dict): s = struct(**s) elif not hasattr(s, "keys"): raise TypeError(f"s must be a struct or a dictionary not a type {type(s).__name__}") for key in self.keys(): if key in s: s_value = getattr(s, key) current_value = getattr(self, key) if is_empty(current_value) or (replacedefaultvar and current_value == "${" + key + "}"): if nonempty: if not is_empty(s_value): setattr(self, key, s_value) else: setattr(self, key, s_value)
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 np2str(self)
-
Convert all NumPy entries of s into their string representations, handling both lists and dictionaries.
Expand source code
def np2str(self): """ Convert all NumPy entries of s into their string representations, handling both lists and dictionaries. """ out = struct() def format_numpy_result(value): """ Converts a NumPy array or scalar into a string representation: - Scalars and single-element arrays (any number of dimensions) are returned as scalars without brackets. - Arrays with more than one element are formatted with proper nesting and commas to make them valid `np.array()` inputs. - If the value is a list or dict, the conversion is applied recursively. - Non-ndarray inputs that are not list/dict are returned without modification. Args: value (np.ndarray, scalar, list, dict, or other): The value to format. Returns: str, list, dict, or original type: A properly formatted string for NumPy arrays/scalars, a recursively converted list/dict, or the original value. """ if isinstance(value, dict): # Recursively process each key in the dictionary. new_dict = {} for k, v in value.items(): new_dict[k] = format_numpy_result(v) return new_dict elif isinstance(value, list): # Recursively process each element in the list. return [format_numpy_result(x) for x in value] elif isinstance(value, tuple): return tuple(format_numpy_result(x) for x in value) elif isinstance(value, struct): return value.npstr() elif np.isscalar(value): # For scalars: if numeric, use str() to avoid extra quotes. if isinstance(value, (int, float, complex, str)) or value is None: return value else: return repr(value) elif isinstance(value, np.ndarray): # Check if the array has exactly one element. if value.size == 1: # Extract the scalar value. return repr(value.item()) # Convert the array to a nested list. nested_list = value.tolist() # Recursively format the nested list into a valid string. def list_to_string(lst): if isinstance(lst, list): return "[" + ",".join(list_to_string(item) for item in lst) + "]" else: return repr(lst) return list_to_string(nested_list) else: # Return the input unmodified if not a NumPy array, list, dict, or scalar. return str(value) # str() preferred over repr() for concision # Process all entries in self. for key, value in self.items(): out.setattr(key, format_numpy_result(value)) return out
def numrepl(self, text)
-
Replace all placeholders of the form ${key} in the given text by the corresponding numeric value from the instance fields, under the following conditions:
- 'key' must be a valid field in self (i.e., if key in self).
- The value corresponding to 'key' is either:
- an int,
- a float, or
- a string that represents a valid number (e.g., "1" or "1.0").
Only when these conditions are met, the placeholder is substituted. The conversion preserves the original type: if the stored value is int, then the substitution will be done as an integer (e.g., 1 and not 1.0). Otherwise, if it is a float then it will be substituted as a float.
Any placeholder for which the above conditions are not met remains unchanged.
Placeholders are recognized by the pattern "${
}" where is captured as all text until the next "}" (optionally allowing whitespace inside the braces). Expand source code
def numrepl(self, text): r""" Replace all placeholders of the form ${key} in the given text by the corresponding numeric value from the instance fields, under the following conditions: 1. 'key' must be a valid field in self (i.e., if key in self). 2. The value corresponding to 'key' is either: - an int, - a float, or - a string that represents a valid number (e.g., "1" or "1.0"). Only when these conditions are met, the placeholder is substituted. The conversion preserves the original type: if the stored value is int, then the substitution will be done as an integer (e.g., 1 and not 1.0). Otherwise, if it is a float then it will be substituted as a float. Any placeholder for which the above conditions are not met remains unchanged. Placeholders are recognized by the pattern "${<key>}" where <key> is captured as all text until the next "}" (optionally allowing whitespace inside the braces). """ # Pattern: match "${", then optional whitespace, capture all characters until "}", # then optional whitespace, then "}". placeholder_pattern = re.compile(r"\$\{\s*([^}]+?)\s*\}") def replace_match(match): key = match.group(1) # Check if the key exists in self. if key in self: value = self[key] # If the value is already numeric, substitute directly. if isinstance(value, (int, float)): return str(value) # If the value is a string, try to interpret it as a numeric value. elif isinstance(value, str): s = value.strip() # Check if s is a valid integer representation. if re.fullmatch(r"[+-]?\d+", s): try: num = int(s) return str(num) except ValueError: # Should not occur because the regex already matched. return match.group(0) # Check if s is a valid float representation (including scientific notation). elif re.fullmatch(r"[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?", s): try: num = float(s) return str(num) except ValueError: return match.group(0) # If key not in self or value is not numeric (or numeric string), leave placeholder intact. return match.group(0) # Replace all placeholders in the text using the replacer function. return placeholder_pattern.sub(replace_match, text)
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 validkeys(self, list_of_keys)
-
Validate and return the subset of keys from the provided list that are valid in the instance.
Parameters:
list_of_keys : list A list of keys (as strings) to check against the instance’s attributes.
Returns:
list A list of keys from list_of_keys that are valid (i.e., exist as attributes in the instance).
Raises:
TypeError If list_of_keys is not a list or if any element in list_of_keys is not a string.
Example:
>>> s = struct() >>> s.foo = 42 >>> s.bar = "hello" >>> valid = s.validkeys(["foo", "bar", "baz"]) >>> print(valid) # Output: ['foo', 'bar'] assuming 'baz' is not defined in s
Expand source code
def validkeys(self, list_of_keys): """ Validate and return the subset of keys from the provided list that are valid in the instance. Parameters: ----------- list_of_keys : list A list of keys (as strings) to check against the instance’s attributes. Returns: -------- list A list of keys from list_of_keys that are valid (i.e., exist as attributes in the instance). Raises: ------- TypeError If list_of_keys is not a list or if any element in list_of_keys is not a string. Example: -------- >>> s = struct() >>> s.foo = 42 >>> s.bar = "hello" >>> valid = s.validkeys(["foo", "bar", "baz"]) >>> print(valid) # Output: ['foo', 'bar'] assuming 'baz' is not defined in s """ # Check that list_of_keys is a list if not isinstance(list_of_keys, list): raise TypeError("list_of_keys must be a list") # Check that every entry in the list is a string for key in list_of_keys: if not isinstance(key, str): raise TypeError("Each key in list_of_keys must be a string") # Assuming valid keys are those present in the instance's __dict__ return [key for key in list_of_keys if key in self]
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.") # Convert to static if needed if isinstance(p,(param,paramauto)): tmp = self.tostatic() else: tmp = self # 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 tmp.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 tlsphalone
-
SMD:TLSPH forcefield (total Lagrangian)
Expand source code
class tlsphalone(forcefield): """ SMD:TLSPH forcefield (total Lagrangian) """ name = smd.name + struct(style="tlsph") description = smd.description + struct(style="SMD:TLSPH - total Lagrangian for solids") # forcefield definition (LAMMPS code between triple """) PAIR_STYLE = """ # [comment] PAIR STYLE SMD pair_style hybrid/overlay smd/tlsph smd/hertz ${contact_scale} """ # 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.forcefield
Class variables
var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var PAIR_STYLE
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 ulsphalone
-
SMD:ULSPH forcefield (updated Lagrangian)
Expand source code
class ulsphalone(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") # forcefield definition (LAMMPS code between triple """) PAIR_STYLE = """ # [comment] PAIR STYLE SMD pair_style smd/ulsph *DENSITY_CONTINUITY *VELOCITY_GRADIENT *NO_GRADIENT_CORRECTION """ # 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
Class variables
var PAIR_DIAGCOEFF
var PAIR_OFFDIAGCOEFF
var PAIR_STYLE
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