Skip to content

Commit 514684b

Browse files
committed
first version
1 parent 1e66e98 commit 514684b

File tree

9 files changed

+1273
-2
lines changed

9 files changed

+1273
-2
lines changed
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
# While Loop Control Flow Examples
2+
3+
This directory contains examples demonstrating the `while` loop control flow node added to the Python Workflow Definition syntax.
4+
5+
## Overview
6+
7+
The `PythonWorkflowDefinitionWhileNode` enables iterative execution in workflows, supporting two main modes:
8+
9+
1. **Simple mode**: Condition and body as Python functions
10+
2. **Complex mode**: Nested workflow as loop body
11+
12+
## Files
13+
14+
### Core Implementation
15+
16+
- `models.py`: Contains the `PythonWorkflowDefinitionWhileNode` class (in main source tree)
17+
- `expression_eval.py`: Safe expression evaluator for condition expressions
18+
19+
### Examples
20+
21+
1. **simple_counter.json** - Basic while loop with function-based condition
22+
2. **simple_counter_expression.json** - While loop with expression-based condition
23+
3. **nested_optimization.json** - Complex nested workflow example
24+
25+
### Supporting Files
26+
27+
- `workflow.py`: Simple workflow functions for basic examples
28+
- `nested_workflow.py`: Functions for the geometry optimization example
29+
- `test_while_node.py`: Test suite validating the implementation
30+
31+
## WhileNode Schema
32+
33+
```json
34+
{
35+
"id": <int>,
36+
"type": "while",
37+
38+
// Condition (exactly one required)
39+
"conditionFunction": "<module.function>", // OR
40+
"conditionExpression": "<expression>", // Safe Python expression
41+
42+
// Body (exactly one required)
43+
"bodyFunction": "<module.function>", // OR
44+
"bodyWorkflow": { ... }, // Nested workflow definition
45+
46+
// Configuration
47+
"maxIterations": 1000, // Safety limit (default: 1000)
48+
"stateVars": ["var1", "var2"] // Optional: tracked variables
49+
}
50+
```
51+
52+
## Example 1: Simple Counter (Function-based)
53+
54+
**File**: `simple_counter.json`
55+
56+
Counts from `m=0` to `n=10` using function-based condition and body.
57+
58+
**Workflow Functions** (`workflow.py`):
59+
60+
```python
61+
def is_less_than(n, m):
62+
"""Condition: continue while m < n"""
63+
return m < n
64+
65+
def increment_m(n, m):
66+
"""Body: increment m by 1"""
67+
return {"n": n, "m": m + 1}
68+
```
69+
70+
**Workflow Graph**:
71+
72+
```
73+
Input(n=10) ──┐
74+
├──> While(condition=is_less_than, body=increment_m) ──> Output(result)
75+
Input(m=0) ──┘
76+
```
77+
78+
## Example 2: Simple Counter (Expression-based)
79+
80+
**File**: `simple_counter_expression.json`
81+
82+
Same as Example 1, but uses a condition expression instead of a function:
83+
84+
```json
85+
{
86+
"type": "while",
87+
"conditionExpression": "m < n",
88+
"bodyFunction": "workflow.increment_m"
89+
}
90+
```
91+
92+
**Supported Expression Operators**:
93+
- Comparison: `<`, `<=`, `>`, `>=`, `==`, `!=`, `in`, `not in`, `is`, `is not`
94+
- Boolean: `and`, `or`, `not`
95+
- Arithmetic: `+`, `-`, `*`, `/`, `//`, `%`, `**`
96+
- Subscript: `data[0]`, `dict["key"]`
97+
98+
**Safety Features**:
99+
- No function calls allowed
100+
- No imports allowed
101+
- No access to `__builtins__`
102+
- No dunder attribute access (e.g., `__import__`)
103+
104+
## Example 3: Nested Workflow (Geometry Optimization)
105+
106+
**File**: `nested_optimization.json`
107+
108+
Demonstrates a complex iterative algorithm with a nested workflow as the loop body.
109+
110+
**Use Case**: Geometry optimization of a molecular structure
111+
112+
**Outer Loop** (While node):
113+
- **Condition**: `not_converged(threshold, energy_change)`
114+
- **Body**: Nested workflow (see below)
115+
- **Max iterations**: 100
116+
117+
**Inner Workflow** (Loop body):
118+
1. `calculate_energy(structure)` → energy
119+
2. `calculate_forces(structure, energy)` → forces
120+
3. `update_geometry(structure, forces)` → new_structure
121+
4. `check_convergence(old_energy, new_structure)` → energy_change
122+
123+
**State Flow**:
124+
125+
```
126+
Iteration N:
127+
structure_N, energy_N → [nested workflow] → structure_{N+1}, energy_{N+1}, energy_change
128+
129+
Check condition:
130+
not_converged(threshold, energy_change) → continue or stop
131+
```
132+
133+
**Workflow Functions** (`nested_workflow.py`):
134+
135+
```python
136+
def not_converged(threshold, energy_change):
137+
return abs(energy_change) > threshold
138+
139+
def calculate_energy(structure):
140+
# Compute energy from atomic positions
141+
...
142+
143+
def calculate_forces(structure, energy):
144+
# Compute forces on atoms
145+
...
146+
147+
def update_geometry(structure, forces):
148+
# Update positions using steepest descent
149+
...
150+
151+
def check_convergence(old_energy, new_structure):
152+
# Calculate energy change
153+
new_energy = calculate_energy(new_structure)
154+
return {"energy": new_energy, "energy_change": new_energy - old_energy}
155+
```
156+
157+
## State Management
158+
159+
The while loop follows the current workflow edge model for state management:
160+
161+
### Input Ports
162+
- Initial values flow into the while node via edges targeting specific ports
163+
- Example: `{"source": 0, "target": 2, "targetPort": "n"}`
164+
165+
### Output Ports
166+
- Loop body functions return dictionaries with updated state
167+
- Output ports extract specific values from the result
168+
- Example: `{"source": 2, "sourcePort": "m", "target": 3}`
169+
170+
### Iteration State Flow
171+
172+
For each iteration:
173+
1. Input state flows into the condition function
174+
2. If condition returns `True`, state flows into body
175+
3. Body returns updated state (dict)
176+
4. Updated state becomes input for next iteration
177+
5. Loop terminates when condition returns `False` or `maxIterations` reached
178+
179+
## Running the Tests
180+
181+
```bash
182+
cd example_workflows/while_loop
183+
python test_while_node.py
184+
```
185+
186+
**Test Coverage**:
187+
- ✓ Schema validation (valid/invalid node configurations)
188+
- ✓ JSON workflow loading (all three examples)
189+
- ✓ Safe expression evaluation (valid expressions and security tests)
190+
191+
## Design Rationale
192+
193+
### Hybrid Approach (Option 3 + 4)
194+
195+
The implementation combines:
196+
1. **Simple function-based** loops (easy to author, backend-compatible)
197+
2. **Nested workflow** loops (powerful for complex multi-step iterations)
198+
199+
### Condition Evaluation Options
200+
201+
**Option A**: `conditionFunction` only
202+
- ✅ Safe, backend-compatible
203+
- ❌ Requires writing functions for simple checks
204+
205+
**Option B**: `conditionExpression` only
206+
- ✅ Natural syntax for simple conditions
207+
- ❌ Requires safe eval mechanism
208+
209+
**Option C**: **Hybrid (Implemented)**
210+
- ✅ Flexibility: use functions OR expressions
211+
- ✅ Safety: restricted AST validation
212+
- ✅ Convenience: no function needed for `m < n`
213+
214+
### Port-based State Management
215+
216+
Following the existing edge model:
217+
- ✅ Consistent with current architecture
218+
- ✅ Explicit data flow
219+
- ✅ Backend translation is straightforward
220+
- ✅ Visualizable in graph UIs
221+
222+
## Implementation Details
223+
224+
### Files Modified/Created
225+
226+
**Core Schema** (`models.py`):
227+
- Added `PythonWorkflowDefinitionWhileNode` class
228+
- Updated discriminated union to include while nodes
229+
- Added validators for condition/body requirements
230+
231+
**Expression Evaluator** (`expression_eval.py`):
232+
- `evaluate_expression()`: Safe eval with AST validation
233+
- `evaluate_condition()`: Wrapper ensuring boolean results
234+
- `SAFE_NODES`: Whitelist of allowed AST node types
235+
236+
**Examples** (this directory):
237+
- Three JSON workflow examples
238+
- Two Python function modules
239+
- Test suite
240+
241+
### Future Extensions
242+
243+
The while node design can be extended to support:
244+
245+
1. **Other control flow**:
246+
- `if/else` conditional nodes
247+
- `for` loop nodes with iterables
248+
- `break`/`continue` conditions
249+
250+
2. **Advanced features**:
251+
- Loop carry state (accumulator pattern)
252+
- Parallel iteration (map-reduce style)
253+
- Dynamic max iterations based on state
254+
255+
3. **Debugging support**:
256+
- Iteration count tracking
257+
- State snapshots per iteration
258+
- Convergence history logging
259+
260+
## Notes
261+
262+
- **DAG Preservation**: While nodes do not create cycles in the graph. The while node itself is atomic and maintains the DAG structure.
263+
- **Safety**: `maxIterations` prevents infinite loops. Default is 1000.
264+
- **Backend Compatibility**: Backends can choose to:
265+
- Unroll loops (for static execution)
266+
- Use native loop constructs (for dynamic execution)
267+
- Implement custom while loop handling
268+
269+
## Questions?
270+
271+
See the test file (`test_while_node.py`) for detailed usage examples and validation.

0 commit comments

Comments
 (0)