Skip to content

Commit 555e153

Browse files
committed
feat: add support for lammps problem size validator
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
1 parent af772c0 commit 555e153

1 file changed

Lines changed: 123 additions & 1 deletion

File tree

  • resource_secretary/apps/molecular_dynamics

resource_secretary/apps/molecular_dynamics/lammps.py

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import shlex
2+
from typing import Annotated, Any, Dict
3+
14
from resource_secretary.apps.app import BaseApplication
25
from resource_secretary.apps.prompts import AppPromptGenerator
3-
from resource_secretary.providers import get_providers
6+
from resource_secretary.providers.provider import dispatch_tool
47

58
LAMMPS_WORKLOADS = {
69
"reaxff": {
@@ -51,6 +54,125 @@ def __init__(self, **kwargs):
5154
self.modifiers = LAMMPS_MODIFIERS
5255
self.default_workload = "reaxff"
5356

57+
@dispatch_tool
58+
def validate_lmp_problem_size(
59+
command: Annotated[str, "The full LAMMPS command string generated by the agent."],
60+
expected_x: Annotated[
61+
int, "The required replication size or grid count in the X dimension."
62+
],
63+
expected_y: Annotated[
64+
int, "The required replication size or grid count in the Y dimension."
65+
],
66+
expected_z: Annotated[
67+
int, "The required replication size or grid count in the Z dimension."
68+
],
69+
) -> Annotated[
70+
Dict[str, Any], "A dictionary containing validation results and diagnostic messages."
71+
]:
72+
"""
73+
Parses a generated LAMMPS command string to verify that the required scientific
74+
problem size variables (X, Y, Z) are present and match the expected values.
75+
76+
Use cases for the Agent:
77+
1. Pre-Submission Check: After generating a complex command, call this tool to ensure no vital variables were dropped.
78+
2. Failure Recovery: If a previous command ran too quickly or failed, use this to double-check that you are actually passing the right grid scale.
79+
3. Syntax Safeguard: Ensures that the agent translates a descriptive prompt (e.g., '32x32x16 grid') into actual machine-readable command line arguments.
80+
81+
Returns:
82+
A dictionary with:
83+
- is_valid (bool): True if all variables are present and match exactly.
84+
- message (str): A human-readable description of what was found or missing.
85+
- parsed_dimensions (Dict[str, float]): The values of x, y, and z parsed from the command.
86+
- missing_dimensions (List[str]): List of missing required variables.
87+
- mismatched_dimensions (List[str]): List of variables that were present but didn't match the expectation.
88+
"""
89+
if not command:
90+
return {
91+
"is_valid": False,
92+
"message": "Command string is empty.",
93+
"parsed_dimensions": {},
94+
"missing_dimensions": ["x", "y", "z"],
95+
"mismatched_dimensions": [],
96+
}
97+
98+
try:
99+
tokens = shlex.split(command)
100+
except Exception as e:
101+
return {
102+
"is_valid": False,
103+
"message": f"Failed to parse command string tokens: {str(e)}",
104+
"parsed_dimensions": {},
105+
"missing_dimensions": [],
106+
"mismatched_dimensions": [],
107+
}
108+
109+
parsed_vars = {}
110+
i = 0
111+
112+
# Scan through tokens to find LAMMPS variable setting patterns
113+
while i < len(tokens):
114+
token = tokens[i]
115+
116+
# Standard LAMMPS: -v x X OR -var x Y OR --variable x 32
117+
if token in ("-v", "-var", "--variable") and i + 2 < len(tokens):
118+
var_name = tokens[i + 1]
119+
var_val = tokens[i + 2]
120+
if var_name in ("x", "y", "z"):
121+
try:
122+
parsed_vars[var_name] = float(var_val)
123+
except ValueError:
124+
pass # Handle non-numeric edge cases gracefully
125+
i += 3
126+
127+
# Support direct flags if asked for: -x 32, -y 32, -z 32
128+
elif token in ("-x", "-y", "-z") and i + 1 < len(tokens):
129+
var_name = token[1] # Extracts 'x', 'y', or 'z'
130+
var_val = tokens[i + 1]
131+
try:
132+
parsed_vars[var_name] = float(var_val)
133+
except ValueError:
134+
pass
135+
i += 2
136+
137+
else:
138+
i += 1
139+
140+
# Compare parsed against expected
141+
expected = {"x": float(expected_x), "y": float(expected_y), "z": float(expected_z)}
142+
missing = []
143+
mismatched = []
144+
145+
for dim, expected_val in expected.items():
146+
if dim not in parsed_vars:
147+
missing.append(dim)
148+
elif parsed_vars[dim] != expected_val:
149+
mismatched.append(dim)
150+
151+
# Compile the resulting status
152+
if not missing and not mismatched:
153+
return {
154+
"is_valid": True,
155+
"message": f"Perfect match! Command correctly contains x={expected_x}, y={expected_y}, z={expected_z}.",
156+
"parsed_dimensions": parsed_vars,
157+
"missing_dimensions": [],
158+
"mismatched_dimensions": [],
159+
}
160+
161+
# Build failure messaging
162+
error_parts = []
163+
if missing:
164+
error_parts.append(f"Missing variable(s): {', '.join(missing)}")
165+
if mismatched:
166+
error_parts.append(f"Mismatched variable(s): {', '.join(mismatched)}")
167+
168+
return {
169+
"is_valid": False,
170+
"message": f"Validation failed. " + " | ".join(error_parts),
171+
"parsed_dimensions": parsed_vars,
172+
"missing_dimensions": missing,
173+
"mismatched_dimensions": mismatched,
174+
}
175+
54176
def get_prompt_matrix(
55177
self, workload="reaxff", manager="flux", flatten=False, filters=None, count=None, **params
56178
):

0 commit comments

Comments
 (0)