methanol_apc / agents.py
glitchfilter's picture
Upload folder using huggingface_hub
f15113b verified
"""
Multi-Agent Wrappers for Methanol APC Environment.
Provides 4 specialized agents that each control a sub-system:
- ReformerAgent: controls syngas production
- SynthesisAgent: controls the methanol reactor
- PurificationAgent: controls distillation
- SupervisoryAgent: orchestrates all agents, sees full state
Each agent has its own action space (subset of full 13-action space)
and observation space (subset + shared variables).
Usage:
from methanol_apc_env.agents import ReformerAgent, SynthesisAgent
env = MethanolAPCEnvironment()
reformer = ReformerAgent(env)
synthesis = SynthesisAgent(env)
obs = env.reset(task_name="optimization")
r_obs = reformer.observe(obs)
r_action = reformer.default_action()
# Merge all agent actions into full action
full_action = SupervisoryAgent.merge_actions(r_action, s_action, p_action)
obs = env.step(full_action)
"""
from __future__ import annotations
from typing import Dict, Any, List
from dataclasses import dataclass
try:
from models import MethanolAPCAction, MethanolAPCObservation
except ImportError:
from .models import MethanolAPCAction, MethanolAPCObservation
@dataclass
class AgentObservation:
"""Observation slice for a specific agent."""
shared: Dict[str, float] # temperature, pressure, step, done
local: Dict[str, float] # agent-specific readings
controls: List[str] # which action fields this agent controls
class ReformerAgent:
"""Controls the Steam Methane Reformer (SMR).
Actions: reformer_fuel_gas, reformer_steam_flow
Observations: reformer_outlet_temp, steam_to_carbon, syngas_flow, efficiency
"""
CONTROLS = ["reformer_fuel_gas", "reformer_steam_flow"]
def observe(self, obs: MethanolAPCObservation) -> AgentObservation:
return AgentObservation(
shared={"temperature": obs.temperature, "pressure": obs.pressure,
"step": obs.step_number, "done": obs.done},
local={"reformer_outlet_temp": obs.reformer_outlet_temp,
"steam_to_carbon": obs.steam_to_carbon,
"syngas_flow": obs.syngas_flow},
controls=self.CONTROLS,
)
def default_action(self) -> Dict[str, float]:
return {"reformer_fuel_gas": 5.0, "reformer_steam_flow": 15.0}
def rule_based_action(self, obs: MethanolAPCObservation) -> Dict[str, float]:
"""Simple rule-based policy for reformer."""
# Adjust fuel to maintain tube temp ~850C
T_tube = obs.reformer_outlet_temp
fuel = 5.0
if T_tube < 800:
fuel = min(15, fuel + (800 - T_tube) * 0.02)
elif T_tube > 900:
fuel = max(2, fuel - (T_tube - 900) * 0.02)
steam = fuel * 3.0 # maintain S/C ~ 3.0
return {"reformer_fuel_gas": fuel, "reformer_steam_flow": steam}
class SynthesisAgent:
"""Controls the methanol synthesis reactor.
Actions: feed_rate_h2, feed_rate_co, cooling_water_flow, compressor_power,
purge_valve_position, recycle_ratio
Observations: temperature, pressure, reaction_rate, catalyst_health,
h2_co_ratio, bed_temps, profit
"""
CONTROLS = ["feed_rate_h2", "feed_rate_co", "cooling_water_flow",
"compressor_power", "purge_valve_position", "recycle_ratio"]
def observe(self, obs: MethanolAPCObservation) -> AgentObservation:
return AgentObservation(
shared={"temperature": obs.temperature, "pressure": obs.pressure,
"step": obs.step_number, "done": obs.done},
local={"reaction_rate": obs.reaction_rate,
"catalyst_health": obs.catalyst_health,
"h2_co_ratio": obs.h2_co_ratio,
"profit_this_step": obs.profit_this_step,
"cumulative_profit": obs.cumulative_profit,
"stoichiometric_number": obs.stoichiometric_number,
"inert_fraction": obs.inert_fraction},
controls=self.CONTROLS,
)
def default_action(self) -> Dict[str, float]:
return {"feed_rate_h2": 5.0, "feed_rate_co": 2.5,
"cooling_water_flow": 40.0, "compressor_power": 65.0,
"purge_valve_position": 2.0, "recycle_ratio": 3.5}
def rule_based_action(self, obs: MethanolAPCObservation) -> Dict[str, float]:
"""Temperature-based rule controller for synthesis reactor."""
T = obs.temperature
if T > 280:
return {"feed_rate_h2": 2.0, "feed_rate_co": 1.0,
"cooling_water_flow": 90.0, "compressor_power": 40.0,
"purge_valve_position": 2.0, "recycle_ratio": 3.5}
elif T > 260:
return {"feed_rate_h2": 5.0, "feed_rate_co": 2.5,
"cooling_water_flow": 60.0, "compressor_power": 60.0,
"purge_valve_position": 2.0, "recycle_ratio": 3.5}
elif T > 240:
return {"feed_rate_h2": 6.0, "feed_rate_co": 3.0,
"cooling_water_flow": 45.0, "compressor_power": 65.0,
"purge_valve_position": 2.0, "recycle_ratio": 3.5}
else:
return {"feed_rate_h2": 8.0, "feed_rate_co": 4.0,
"cooling_water_flow": 20.0, "compressor_power": 75.0,
"purge_valve_position": 2.0, "recycle_ratio": 3.5}
class PurificationAgent:
"""Controls the distillation column.
Actions: distillation_reflux, reboiler_duty
Observations: product_purity, distillation_duty, overhead_temp
"""
CONTROLS = ["distillation_reflux", "reboiler_duty"]
def observe(self, obs: MethanolAPCObservation) -> AgentObservation:
return AgentObservation(
shared={"temperature": obs.temperature, "pressure": obs.pressure,
"step": obs.step_number, "done": obs.done},
local={"product_purity": obs.product_purity,
"distillation_duty": obs.distillation_duty,
"methanol_produced": obs.methanol_produced},
controls=self.CONTROLS,
)
def default_action(self) -> Dict[str, float]:
return {"distillation_reflux": 3.0, "reboiler_duty": 50.0}
def rule_based_action(self, obs: MethanolAPCObservation) -> Dict[str, float]:
"""Purity-based rule controller for distillation."""
purity = obs.product_purity
if purity < 0.995:
return {"distillation_reflux": min(8.0, 3.0 + (0.995 - purity) * 100),
"reboiler_duty": min(150, 50 + (0.995 - purity) * 2000)}
else:
return {"distillation_reflux": 3.0, "reboiler_duty": 50.0}
class SupervisoryAgent:
"""Orchestrates all sub-agents. Sees full plant state."""
CONTROLS = list(MethanolAPCAction.model_fields.keys())
def observe(self, obs: MethanolAPCObservation) -> AgentObservation:
return AgentObservation(
shared={"temperature": obs.temperature, "pressure": obs.pressure,
"step": obs.step_number, "done": obs.done},
local={"cumulative_profit": obs.cumulative_profit,
"methanol_produced": obs.methanol_produced,
"catalyst_health": obs.catalyst_health,
"product_purity": obs.product_purity,
"total_co2_emissions": obs.total_co2_emissions,
"reaction_rate": obs.reaction_rate},
controls=self.CONTROLS,
)
@staticmethod
def merge_actions(*agent_actions: Dict[str, float]) -> MethanolAPCAction:
"""Merge actions from multiple agents into a single MethanolAPCAction.
Later agents override earlier ones for overlapping controls.
"""
merged = {}
for action_dict in agent_actions:
merged.update(action_dict)
# Fill any missing fields with defaults
defaults = {f: MethanolAPCAction.model_fields[f].default
for f in MethanolAPCAction.model_fields
if MethanolAPCAction.model_fields[f].default is not None}
for k, v in defaults.items():
merged.setdefault(k, v)
return MethanolAPCAction(**merged)