Files
Atomizer/tests/test_training_data_exporter.py
Anto01 2b3573ec42 feat: Add AtomizerField training data export and intelligent model discovery
Major additions:
- Training data export system for AtomizerField neural network training
- Bracket stiffness optimization study with 50+ training samples
- Intelligent NX model discovery (auto-detect solutions, expressions, mesh)
- Result extractors module for displacement, stress, frequency, mass
- User-generated NX journals for advanced workflows
- Archive structure for legacy scripts and test outputs
- Protocol documentation and dashboard launcher

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 12:01:50 -05:00

387 lines
14 KiB
Python

"""
Unit tests for TrainingDataExporter
Tests the training data export functionality for AtomizerField.
"""
import pytest
import json
import tempfile
import shutil
from pathlib import Path
from optimization_engine.training_data_exporter import TrainingDataExporter, create_exporter_from_config
class TestTrainingDataExporter:
"""Test suite for TrainingDataExporter class."""
@pytest.fixture
def temp_dir(self):
"""Create a temporary directory for tests."""
temp_path = Path(tempfile.mkdtemp())
yield temp_path
# Cleanup after test
if temp_path.exists():
shutil.rmtree(temp_path)
@pytest.fixture
def sample_exporter(self, temp_dir):
"""Create a sample exporter for testing."""
return TrainingDataExporter(
export_dir=temp_dir / "test_export",
study_name="test_study",
design_variable_names=["thickness", "width", "length"],
objective_names=["max_stress", "mass"],
constraint_names=["stress_constraint"],
metadata={"test_key": "test_value"}
)
@pytest.fixture
def sample_simulation_files(self, temp_dir):
"""Create sample .dat and .op2 files for testing."""
sim_dir = temp_dir / "sim_files"
sim_dir.mkdir(parents=True, exist_ok=True)
dat_file = sim_dir / "test_model.dat"
op2_file = sim_dir / "test_model.op2"
# Create dummy files with some content
dat_file.write_text("$ Nastran input deck\nGRID,1,0,0.0,0.0,0.0\n")
op2_file.write_bytes(b"DUMMY OP2 BINARY DATA" * 100)
return {
"dat_file": dat_file,
"op2_file": op2_file
}
def test_exporter_initialization(self, temp_dir):
"""Test that exporter initializes correctly."""
export_dir = temp_dir / "test_export"
exporter = TrainingDataExporter(
export_dir=export_dir,
study_name="beam_study",
design_variable_names=["thickness", "width"],
objective_names=["stress", "mass"]
)
assert exporter.export_dir == export_dir
assert exporter.study_name == "beam_study"
assert exporter.design_variable_names == ["thickness", "width"]
assert exporter.objective_names == ["stress", "mass"]
assert exporter.trial_count == 0
assert len(exporter.exported_trials) == 0
assert export_dir.exists()
def test_readme_creation(self, sample_exporter):
"""Test that README.md is created."""
readme_path = sample_exporter.export_dir / "README.md"
assert readme_path.exists()
content = readme_path.read_text()
assert "test_study" in content
assert "AtomizerField Training Data" in content
assert "thickness" in content
assert "max_stress" in content
def test_export_trial_success(self, sample_exporter, sample_simulation_files):
"""Test successful trial export."""
design_vars = {"thickness": 3.5, "width": 50.0, "length": 200.0}
results = {
"objectives": {"max_stress": 245.3, "mass": 1.25},
"constraints": {"stress_constraint": -54.7},
"max_displacement": 1.23
}
success = sample_exporter.export_trial(
trial_number=1,
design_variables=design_vars,
results=results,
simulation_files=sample_simulation_files
)
assert success
assert sample_exporter.trial_count == 1
assert len(sample_exporter.exported_trials) == 1
# Check directory structure
trial_dir = sample_exporter.export_dir / "trial_0001"
assert trial_dir.exists()
assert (trial_dir / "input").exists()
assert (trial_dir / "output").exists()
assert (trial_dir / "input" / "model.bdf").exists()
assert (trial_dir / "output" / "model.op2").exists()
assert (trial_dir / "metadata.json").exists()
def test_export_trial_metadata(self, sample_exporter, sample_simulation_files):
"""Test that metadata.json is created correctly."""
design_vars = {"thickness": 3.5, "width": 50.0, "length": 200.0}
results = {
"objectives": {"max_stress": 245.3, "mass": 1.25},
"constraints": {"stress_constraint": -54.7},
"max_displacement": 1.23
}
sample_exporter.export_trial(
trial_number=42,
design_variables=design_vars,
results=results,
simulation_files=sample_simulation_files
)
metadata_path = sample_exporter.export_dir / "trial_0042" / "metadata.json"
assert metadata_path.exists()
with open(metadata_path, 'r') as f:
metadata = json.load(f)
assert metadata["trial_number"] == 42
assert metadata["atomizer_study"] == "test_study"
assert metadata["design_parameters"] == design_vars
assert metadata["results"]["objectives"] == {"max_stress": 245.3, "mass": 1.25}
assert metadata["results"]["constraints"] == {"stress_constraint": -54.7}
assert metadata["results"]["max_displacement"] == 1.23
assert "timestamp" in metadata
def test_export_trial_missing_dat_file(self, sample_exporter, temp_dir):
"""Test export fails gracefully when .dat file is missing."""
design_vars = {"thickness": 3.5}
results = {"objectives": {"max_stress": 245.3}}
# Create only .op2 file
sim_dir = temp_dir / "sim_files"
sim_dir.mkdir(parents=True, exist_ok=True)
op2_file = sim_dir / "test_model.op2"
op2_file.write_bytes(b"DUMMY OP2 DATA")
simulation_files = {
"dat_file": sim_dir / "nonexistent.dat", # Does not exist
"op2_file": op2_file
}
success = sample_exporter.export_trial(
trial_number=1,
design_variables=design_vars,
results=results,
simulation_files=simulation_files
)
assert not success
assert sample_exporter.trial_count == 0
def test_export_trial_missing_op2_file(self, sample_exporter, temp_dir):
"""Test export fails gracefully when .op2 file is missing."""
design_vars = {"thickness": 3.5}
results = {"objectives": {"max_stress": 245.3}}
# Create only .dat file
sim_dir = temp_dir / "sim_files"
sim_dir.mkdir(parents=True, exist_ok=True)
dat_file = sim_dir / "test_model.dat"
dat_file.write_text("$ Nastran input")
simulation_files = {
"dat_file": dat_file,
"op2_file": sim_dir / "nonexistent.op2" # Does not exist
}
success = sample_exporter.export_trial(
trial_number=1,
design_variables=design_vars,
results=results,
simulation_files=simulation_files
)
assert not success
assert sample_exporter.trial_count == 0
def test_export_multiple_trials(self, sample_exporter, sample_simulation_files):
"""Test exporting multiple trials."""
for i in range(1, 6):
design_vars = {"thickness": 2.0 + i * 0.5, "width": 40.0 + i * 5, "length": 180.0 + i * 10}
results = {
"objectives": {"max_stress": 200.0 + i * 10, "mass": 1.0 + i * 0.1}
}
success = sample_exporter.export_trial(
trial_number=i,
design_variables=design_vars,
results=results,
simulation_files=sample_simulation_files
)
assert success
assert sample_exporter.trial_count == 5
assert len(sample_exporter.exported_trials) == 5
# Verify all trial directories exist
for i in range(1, 6):
trial_dir = sample_exporter.export_dir / f"trial_{i:04d}"
assert trial_dir.exists()
assert (trial_dir / "metadata.json").exists()
def test_finalize(self, sample_exporter, sample_simulation_files):
"""Test finalize creates study_summary.json."""
# Export a few trials
for i in range(1, 4):
design_vars = {"thickness": 2.0 + i * 0.5, "width": 50.0, "length": 200.0}
results = {"objectives": {"max_stress": 200.0 + i * 10, "mass": 1.2}}
sample_exporter.export_trial(i, design_vars, results, sample_simulation_files)
# Finalize
sample_exporter.finalize()
# Check study_summary.json
summary_path = sample_exporter.export_dir / "study_summary.json"
assert summary_path.exists()
with open(summary_path, 'r') as f:
summary = json.load(f)
assert summary["study_name"] == "test_study"
assert summary["total_trials"] == 3
assert summary["design_variables"] == ["thickness", "width", "length"]
assert summary["objectives"] == ["max_stress", "mass"]
assert summary["constraints"] == ["stress_constraint"]
assert "export_timestamp" in summary
assert summary["metadata"] == {"test_key": "test_value"}
def test_create_exporter_from_config_disabled(self):
"""Test that None is returned when export is disabled."""
config = {
"training_data_export": {
"enabled": False,
"export_dir": "/some/path"
}
}
exporter = create_exporter_from_config(config)
assert exporter is None
def test_create_exporter_from_config_missing_export_dir(self):
"""Test that None is returned when export_dir is missing."""
config = {
"training_data_export": {
"enabled": True
}
}
exporter = create_exporter_from_config(config)
assert exporter is None
def test_create_exporter_from_config_success(self, temp_dir):
"""Test successful exporter creation from config."""
config = {
"study_name": "beam_optimization",
"training_data_export": {
"enabled": True,
"export_dir": str(temp_dir / "export")
},
"design_variables": [
{"name": "thickness"},
{"parameter": "width"},
{"other_key": "value"} # Should use "var_2"
],
"objectives": [
{"name": "max_stress"},
{"name": "mass"}
],
"constraints": [
{"name": "stress_limit"}
],
"version": "1.0",
"optimization": {
"algorithm": "NSGA-II",
"n_trials": 100
}
}
exporter = create_exporter_from_config(config)
assert exporter is not None
assert exporter.study_name == "beam_optimization"
assert exporter.design_variable_names == ["thickness", "width", "var_2"]
assert exporter.objective_names == ["max_stress", "mass"]
assert exporter.constraint_names == ["stress_limit"]
assert exporter.study_metadata["atomizer_version"] == "1.0"
assert exporter.study_metadata["optimization_algorithm"] == "NSGA-II"
assert exporter.study_metadata["n_trials"] == 100
def test_create_exporter_from_config_defaults(self, temp_dir):
"""Test exporter creation with minimal config."""
config = {
"training_data_export": {
"enabled": True,
"export_dir": str(temp_dir / "export")
}
}
exporter = create_exporter_from_config(config)
assert exporter is not None
assert exporter.study_name == "unnamed_study"
assert exporter.design_variable_names == []
assert exporter.objective_names == []
assert exporter.constraint_names == []
def test_file_content_preservation(self, sample_exporter, temp_dir):
"""Test that file contents are preserved during copy."""
# Create files with specific content
sim_dir = temp_dir / "sim_files"
sim_dir.mkdir(parents=True, exist_ok=True)
dat_content = "$ Nastran Input Deck\nGRID,1,0,0.0,0.0,0.0\nGRID,2,0,1.0,0.0,0.0\n"
op2_content = b"\x00\x01\x02\x03NASTRAN_BINARY_DATA\xFF\xFE\xFD\xFC" * 50
dat_file = sim_dir / "model.dat"
op2_file = sim_dir / "model.op2"
dat_file.write_text(dat_content)
op2_file.write_bytes(op2_content)
simulation_files = {"dat_file": dat_file, "op2_file": op2_file}
# Export trial
sample_exporter.export_trial(
trial_number=1,
design_variables={"thickness": 3.0},
results={"objectives": {"stress": 200.0}},
simulation_files=simulation_files
)
# Verify copied files have same content
exported_dat = sample_exporter.export_dir / "trial_0001" / "input" / "model.bdf"
exported_op2 = sample_exporter.export_dir / "trial_0001" / "output" / "model.op2"
assert exported_dat.read_text() == dat_content
assert exported_op2.read_bytes() == op2_content
def test_trial_numbering_format(self, sample_exporter, sample_simulation_files):
"""Test that trial directories use correct numbering format."""
# Test various trial numbers
trial_numbers = [1, 9, 10, 99, 100, 999, 1000]
for trial_num in trial_numbers:
sample_exporter.export_trial(
trial_number=trial_num,
design_variables={"thickness": 3.0},
results={"objectives": {"stress": 200.0}},
simulation_files=sample_simulation_files
)
expected_dir = sample_exporter.export_dir / f"trial_{trial_num:04d}"
assert expected_dir.exists()
# Verify specific formatting
assert (sample_exporter.export_dir / "trial_0001").exists()
assert (sample_exporter.export_dir / "trial_0009").exists()
assert (sample_exporter.export_dir / "trial_0010").exists()
assert (sample_exporter.export_dir / "trial_0099").exists()
assert (sample_exporter.export_dir / "trial_0100").exists()
assert (sample_exporter.export_dir / "trial_0999").exists()
assert (sample_exporter.export_dir / "trial_1000").exists()
if __name__ == "__main__":
pytest.main([__file__, "-v"])