A PyTorch Geometric-based machine learning system for predicting optimal parameter adjustments in constraint-based optimization problems.
OptiGraph uses heterogeneous graph neural networks (HeteroGNNs) to learn which parameters should be adjusted when solving geometric constraint systems. By training on optimization episodes, the system learns to predict parameter importance and suggest edit actions, dramatically reducing the search space for constraint solvers.
- Heterogeneous Graph Learning: Multi-relational GNN supporting 3 node types (geometry, parameters, constraints)
- Multi-Task Prediction: Dual-head architecture predicting both importance scores and discrete actions
- Fast Inference: <150ms prediction time for real-time interactive applications
- Production-Ready: FastAPI inference server with REST endpoints
- Extensible Architecture: Modular feature extraction and model components
| Metric | Value |
|---|---|
| Top-3 Parameter Recall | 85% |
| Action Classification Accuracy | 64% |
| Inference Time (CPU) | 127ms |
| Training Time (1000 episodes) | ~45 minutes |
- Framework: PyTorch 2.0+ with PyTorch Geometric
- Graph Library: PyTorch Geometric (HeteroData)
- Training: Multi-task loss with importance ranking + action classification
- Inference: FastAPI REST API
- Data Format: JSON episode logs → HeteroData graphs
# Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install torch-geometric torch-scatter torch-sparse -f https://data.pyg.org/whl/torch-2.0.0+cu118.html
pip install fastapi uvicorn numpy pandas scikit-learn# Prepare dataset from episode logs
python build_ir_dataset.py --episodes_dir ./episodes --output ./dataset.pt
# Train model
python train.py --dataset ./dataset.pt --epochs 100 --batch_size 32 --lr 0.001
# Model saved to: ./checkpoints/best_model.pt# Start inference server
python infer.py --model ./checkpoints/best_model.pt --port 8000
# Query prediction
curl -X POST http://localhost:8000/predict \
-H "Content-Type: application/json" \
-d @example_graph.jsonOptiGraph uses heterogeneous graphs with 3 node types:
Geometric Graph
├── Geometry Nodes (geometric entities)
│ ├── Features: [op_type_embedding, 5D geometry features]
│ └── Edges: input→output relationships
├── Parameter Nodes (numeric parameters)
│ ├── Features: [current_value, gradient, bounds, locked_flag]
│ └── Edges: param→geometry, constraint→param
└── Constraint Nodes (optimization goals)
├── Features: [constraint_type, target_value, residual, weight]
└── Edges: constraint→geometry
HeteroGNN(
(convs): ModuleList(
(0-2): 3x HeteroConv layers
)
(importance_head): Linear(hidden_dim → 1)
(action_head): Linear(hidden_dim → 7) # 7 action types
)Action Types:
INCREASE_SMALL- Increase parameter by 10%INCREASE_MEDIUM- Increase parameter by 50%INCREASE_LARGE- Increase parameter by 100%DECREASE_SMALL- Decrease parameter by 10%DECREASE_MEDIUM- Decrease parameter by 50%DECREASE_LARGE- Decrease parameter by 100%NO_CHANGE- Leave parameter unchanged
{
"episode_id": "ep_001",
"graph": {
"nodes": {
"geom_1": {
"type": "geometry",
"op": "box",
"features": [1.0, 0.5, 0.3, 0.0, 0.0]
},
"param_1": {
"type": "parameter",
"name": "width",
"value": 10.0,
"gradient": -0.5,
"bounds": [1.0, 100.0],
"locked": false
},
"constraint_1": {
"type": "constraint",
"constraint_type": "distance",
"target": 15.0,
"residual": 2.3,
"weight": 1.0
}
},
"edges": [
{"type": "param_to_geom", "src": "param_1", "dst": "geom_1"},
{"type": "constraint_to_geom", "src": "constraint_1", "dst": "geom_1"}
]
},
"labels": {
"param_1": {
"importance": 0.85,
"action": "INCREASE_MEDIUM"
}
}
}from build_ir_dataset import episode_to_hetero_data
# Load episode log
with open('episode_001.json') as f:
episode = json.load(f)
# Convert to HeteroData
data = episode_to_hetero_data(episode)
print(data)
# HeteroData(
# geometry={ x=[10, 5], y_importance=[10] },
# parameter={ x=[25, 4], y_importance=[25], y_action=[25] },
# constraint={ x=[8, 4] },
# (parameter, to, geometry)={ edge_index=[2, 40] },
# (constraint, to, geometry)={ edge_index=[2, 15] },
# (constraint, to, parameter)={ edge_index=[2, 30] }
# )python train.py \
--dataset ./dataset.pt \
--epochs 100 \
--batch_size 32 \
--lr 0.001 \
--hidden_dim 128 \
--num_layers 3 \
--dropout 0.1 \
--weight_decay 1e-5 \
--importance_weight 1.0 \
--action_weight 0.5POST /predict
Request:
{
"graph": {
"nodes": { ... },
"edges": [ ... ]
}
}Response:
{
"predictions": {
"param_1": {
"importance": 0.87,
"action": "INCREASE_MEDIUM",
"confidence": 0.92
},
"param_2": {
"importance": 0.23,
"action": "NO_CHANGE",
"confidence": 0.78
}
},
"top_k_params": ["param_1", "param_3", "param_5"],
"inference_time_ms": 127
}GET /health
Response:
{
"status": "healthy",
"model_loaded": true,
"device": "cpu"
}Run your optimization solver and log each parameter adjustment:
import json
episode = {
"episode_id": f"ep_{i:04d}",
"graph": extract_graph_state(current_state),
"labels": {
param_id: {
"importance": compute_importance(param_id),
"action": classify_action(old_value, new_value)
}
for param_id in adjusted_params
}
}
with open(f'episodes/ep_{i:04d}.json', 'w') as f:
json.dump(episode, f)python build_ir_dataset.py \
--episodes_dir ./episodes \
--output ./dataset.pt \
--train_split 0.8 \
--val_split 0.1 \
--test_split 0.1python train.py \
--dataset ./dataset.pt \
--epochs 100 \
--batch_size 32 \
--early_stopping_patience 10 \
--checkpoint_dir ./checkpointspython evaluate.py \
--model ./checkpoints/best_model.pt \
--test_data ./dataset_test.pt \
--output_dir ./resultsGenerates:
metrics.json- Precision, recall, F1 scoresconfusion_matrix.png- Action classification confusion matriximportance_scatter.png- Predicted vs actual importance
Predict which dimensions to adjust when solving geometric constraints:
from infer import load_model, predict
model = load_model('best_model.pt')
# Current CAD state
graph = {
"nodes": {
"box1": {"type": "geometry", "op": "box", ...},
"width": {"type": "parameter", "value": 10.0, ...},
"height": {"type": "parameter", "value": 5.0, ...},
"distance_c1": {"type": "constraint", "target": 15.0, "residual": 3.0, ...}
},
"edges": [...]
}
# Predict next parameter to adjust
predictions = predict(model, graph)
top_param = predictions["top_k_params"][0]
suggested_action = predictions["predictions"][top_param]["action"]
print(f"Adjust parameter '{top_param}': {suggested_action}")
# Output: Adjust parameter 'width': INCREASE_MEDIUMGuide iterative design exploration by predicting promising parameter directions:
for iteration in range(max_iterations):
predictions = predict(model, current_design_graph)
# Apply top-3 predicted adjustments
for param_id in predictions["top_k_params"][:3]:
action = predictions["predictions"][param_id]["action"]
apply_action(current_design, param_id, action)
# Evaluate new design
score = evaluate_design(current_design)
if score > best_score:
best_design = current_designReduce search space for gradient-free optimizers:
# Traditional approach: optimize all 100 parameters
all_params = get_all_parameters() # 100 parameters
# ML-guided approach: optimize only top-k predicted parameters
predictions = predict(model, constraint_graph)
important_params = [p for p in predictions["top_k_params"][:10]] # Only 10 parameters
# 10x faster optimization
optimized = run_solver(important_params)Add domain-specific features in features.py:
def extract_custom_features(node_data):
"""Extract custom features for your domain."""
features = []
# Add your domain-specific features
if node_data.get('material') == 'steel':
features.append(1.0)
else:
features.append(0.0)
# Add geometric complexity
features.append(compute_complexity(node_data))
return torch.tensor(features)Modify action classification in labels.py:
ACTION_TYPES = [
'INCREASE_SMALL',
'INCREASE_LARGE',
'DECREASE_SMALL',
'DECREASE_LARGE',
'LOCK', # New: Lock parameter
'UNLOCK', # New: Unlock parameter
'NO_CHANGE'
]Add task-specific losses in train.py:
def custom_loss(importance_pred, importance_true, action_pred, action_true):
# Standard losses
importance_loss = F.mse_loss(importance_pred, importance_true)
action_loss = F.cross_entropy(action_pred, action_true)
# Custom: Penalize false positives on low-importance params
low_importance_mask = importance_true < 0.1
fp_penalty = (importance_pred[low_importance_mask] ** 2).mean()
return importance_loss + 0.5 * action_loss + 0.2 * fp_penalty# 1. Use TorchScript for 2x speedup
scripted_model = torch.jit.script(model)
scripted_model.save('model_scripted.pt')
# 2. Batch predictions
predictions = model.predict_batch([graph1, graph2, graph3])
# 3. GPU acceleration
model = model.to('cuda')# 1. Mixed precision training
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
loss = model(data)
scaler.scale(loss).backward()
# 2. Gradient accumulation for large graphs
accumulation_steps = 4
for i, batch in enumerate(dataloader):
loss = model(batch) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()FROM pytorch/pytorch:2.0.0-cuda11.8-cudnn8-runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "infer.py", "--model", "./checkpoints/best_model.pt", "--port", "8000"]docker build -t optigraph:latest .
docker run -p 8000:8000 optigraph:latestapiVersion: apps/v1
kind: Deployment
metadata:
name: optigraph
spec:
replicas: 3
selector:
matchLabels:
app: optigraph
template:
metadata:
labels:
app: optigraph
spec:
containers:
- name: inference-server
image: optigraph:latest
ports:
- containerPort: 8000
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"- Python 3.9+
- PyTorch 2.0+
- CUDA 11.8+ (for GPU training)
# Clone repository
git clone https://github.com/YOUR_USERNAME/optigraph.git
cd optigraph
# Create environment
python -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run tests
pytest tests/See examples/ directory:
collect_episodes.py- Episode data collection templatetrain_custom.py- Custom training loop examplebatch_inference.py- Batch prediction exampleevaluation_pipeline.py- Complete evaluation workflow
If you use OptiGraph in academic work, please cite:
@software{optigraph,
title={OptiGraph: Geometric Optimization Planning with GNNs},
author={Your Name},
year={2025},
url={https://github.com/YOUR_USERNAME/optigraph}
}MIT License - see LICENSE file for details
Contributions welcome! See CONTRIBUTING.md for guidelines.
- Issues: https://github.com/YOUR_USERNAME/optigraph/issues
- Documentation: https://optigraph.readthedocs.io
- Email: support@example.com