material_forge_env / server /material_forge_env_environment.py
ArshPathan's picture
Upload folder using huggingface_hub
cd470c1
Raw History Blame Contribute Delete
7.57 kB
# MaterialForge Environment Implementation.
# This class acts as the OpenEnv-compliant wrapper for our scientific simulation.
import random
from uuid import uuid4
from openenv.core.env_server.interfaces import Environment
from openenv.core.env_server.types import State
# Direct imports from root-level modules/packages
from environment.config import (
MIN_ATOMS_FOR_COMPLETION,
PROPERTY_NAMES,
VALID_COMPLETION_PHASES,
)
from environment.lattice import Lattice
from models import MaterialForgeAction, MaterialForgeObservation
from environment.physics import (
classify_phase,
compute_lattice_order,
compute_structural_stability,
estimate_properties,
)
from scenarios.scenarios import generate_scenario
# Main environment class: bridges the abstract OpenEnv protocol with our lattice physics.
class MaterialForgeEnvironment(Environment):
"""RL environment for designing crystal structures on a lattice grid.
The agent places, replaces, or removes atoms on an 8x8 grid to achieve
target material properties (hardness, conductivity, thermal_resistance,
elasticity). Episodes end when max_steps is reached or all properties
are within tolerance of the target.
"""
SUPPORTS_CONCURRENT_SESSIONS: bool = True
def __init__(self, rubric=None):
super().__init__(rubric=rubric)
self._lattice = Lattice()
self._state = State(episode_id=str(uuid4()), step_count=0)
self._scenario = {}
self._target = {}
self._cost_budget = 80.0
self._tolerance = 10.0
self._max_steps = 50
self._difficulty = "medium"
# Reset function: triggers a new scenario and initializes the physics lattice.
def reset(self, seed: int | None = None, **kwargs) -> MaterialForgeObservation:
"""Reset environment with a new scenario.
Args:
seed: Optional random seed for reproducibility.
"""
if seed is not None:
random.seed(seed)
difficulty = kwargs.get("difficulty", "medium")
scenario_name = kwargs.get("scenario_name", None)
self._scenario = generate_scenario(difficulty=difficulty, name=scenario_name)
self._target = self._scenario["target"]
self._cost_budget = float(self._scenario["cost_budget"])
self._tolerance = float(self._scenario["tolerance"])
self._max_steps = int(self._scenario["max_steps"])
self._difficulty = self._scenario["difficulty"]
self._lattice = Lattice()
self._state = State(episode_id=str(uuid4()), step_count=0)
self._reset_rubric()
return self._build_observation(done=False, reward=0.0)
# Core execution step: applies the agent's action and computes the resulting physics state.
def step(self, action: MaterialForgeAction) -> MaterialForgeObservation: # type: ignore[override]
"""Execute one step: apply action, compute properties, score, check done."""
self._state.step_count += 1
# Apply action to lattice
success = self._apply_action(action)
# Compute current state via physics heuristics
properties = estimate_properties(self._lattice)
phase = classify_phase(self._lattice)
stability = compute_structural_stability(self._lattice)
order = compute_lattice_order(self._lattice)
# Check termination
done = self._check_done(properties, phase)
# Build observation with advanced scientific score breakdown
score_breakdown = {
"structural_stability": stability,
"lattice_order_index": order,
"action_valid": 1.0 if success else 0.0,
}
obs = MaterialForgeObservation(
grid=self._lattice.get_grid(),
target=self._target,
current_properties=properties,
phase=phase,
total_cost=self._lattice.total_cost(),
cost_budget=self._cost_budget,
step_number=self._state.step_count,
max_steps=self._max_steps,
score_breakdown=score_breakdown,
done=done,
reward=0.0, # placeholder, rubric will compute
metadata={
"difficulty": self._difficulty,
"scenario_name": self._scenario.get("name"),
"tolerance": self._tolerance,
"action_success": success,
"phase_id": phase,
},
)
# Apply rubric to get reward
obs.reward = self._apply_rubric(action, obs)
return obs
# Internal helper to mutate the lattice state.
def _apply_action(self, action: MaterialForgeAction) -> bool:
"""Apply the action to the lattice. Returns True if action was valid."""
if action.action_type == "place":
if action.atom is None:
return False
return self._lattice.place(action.row, action.col, action.atom)
elif action.action_type == "replace":
if action.atom is None:
return False
return self._lattice.replace(action.row, action.col, action.atom)
elif action.action_type == "remove":
return self._lattice.remove(action.row, action.col)
return False
# Evaluation function: checks if the material has reached a stable crystalline phase.
def _check_done(self, properties: dict, phase: str) -> bool:
"""Check if episode should end based on target matching and structural validity."""
if self._state.step_count >= self._max_steps:
return True
atom_count = self._lattice.atom_count()
# Success requires minimal structure size and ordered phase
if atom_count >= MIN_ATOMS_FOR_COMPLETION and phase in VALID_COMPLETION_PHASES:
all_within = all(
abs(properties.get(p, 0.0) - self._target.get(p, 0.0))
<= self._tolerance
for p in PROPERTY_NAMES
)
if all_within:
return True
return False
# State serialization: packages the current grid and metrics into a standardized object.
def _build_observation(self, done: bool, reward: float) -> MaterialForgeObservation:
"""Build an observation from the current lattice state."""
properties = estimate_properties(self._lattice)
phase = classify_phase(self._lattice)
stability = compute_structural_stability(self._lattice)
order = compute_lattice_order(self._lattice)
return MaterialForgeObservation(
grid=self._lattice.get_grid(),
target=self._target,
current_properties=properties,
phase=phase,
total_cost=self._lattice.total_cost(),
cost_budget=self._cost_budget,
step_number=self._state.step_count,
max_steps=self._max_steps,
score_breakdown={
"structural_stability": stability,
"lattice_order_index": order,
},
done=done,
reward=reward,
metadata={
"difficulty": self._difficulty,
"scenario_name": self._scenario.get("name"),
"tolerance": self._tolerance,
},
)
@property
def state(self) -> State:
return self._state