infotheory/aixi/
planner_runtime.rs1use crate::aixi::common::ActionAlphabet;
8use crate::aixi::environment::Environment;
9#[cfg(feature = "aixi-gameengine")]
10use crate::aixi::gameengine::build_builtin_environment as build_gameengine_builtin_environment;
11#[cfg(feature = "vm")]
12use crate::aixi::vm_nyx::{NyxVmConfig, NyxVmEnvironment};
13use crate::aixi::warmstart::{
14 WarmStartExactJhTeacherDataset, validate_warmstart_teacher_dataset_for_compiled_planner_run,
15};
16use crate::spec::{self, AssetRef, BuiltinEnvironmentSpec, CompiledPlannerRunSpec, SpecDocument};
17use std::path::Path;
18
19pub fn load_warmstart_exact_jh_teacher_dataset(
21 compiled: &CompiledPlannerRunSpec,
22 asset_id: &str,
23) -> anyhow::Result<WarmStartExactJhTeacherDataset> {
24 let binding = compiled
25 .resolved_assets()
26 .iter()
27 .find(|entry| entry.id == asset_id)
28 .ok_or_else(|| anyhow::anyhow!("unknown warm-start teacher_dataset_asset '{asset_id}'"))?;
29 let AssetRef::Filesystem(path) = &binding.asset;
30 let bytes = std::fs::read(path).map_err(|err| {
31 anyhow::anyhow!(
32 "failed to read warm-start teacher_dataset_asset '{}': {err}",
33 path.display()
34 )
35 })?;
36 let teacher =
37 WarmStartExactJhTeacherDataset::from_json_slice(&bytes).map_err(anyhow::Error::msg)?;
38 validate_warmstart_teacher_dataset_for_compiled_planner_run(compiled, &teacher)
39 .map_err(anyhow::Error::msg)?;
40 Ok(teacher)
41}
42
43pub fn validate_warmstart_exact_jh_teacher_contract(
45 compiled: &CompiledPlannerRunSpec,
46 teacher: &WarmStartExactJhTeacherDataset,
47) -> anyhow::Result<()> {
48 crate::aixi::warmstart::validate_warmstart_teacher_against_compiled_planner_run(
49 compiled,
50 &teacher.contract,
51 )
52 .map_err(|err| anyhow::anyhow!("{err}"))
53}
54
55pub fn compile_planner_run_document(
57 path: &str,
58 caller: &str,
59) -> anyhow::Result<CompiledPlannerRunSpec> {
60 let config_dir = Path::new(path).parent().unwrap_or(Path::new("."));
61 let document = spec::load_spec_document(path).map_err(anyhow::Error::msg)?;
62 match document {
63 SpecDocument::PlannerRun(spec) => spec
64 .compile_in(&spec::SpecEnvironment::new(config_dir))
65 .map_err(anyhow::Error::msg),
66 other => Err(anyhow::anyhow!(
67 "{caller} expects a planner_run document, found kind '{}'",
68 other.kind_str()
69 )),
70 }
71}
72
73fn build_builtin_environment(spec: BuiltinEnvironmentSpec) -> anyhow::Result<Box<dyn Environment>> {
74 #[cfg(feature = "aixi-gameengine")]
75 {
76 build_gameengine_builtin_environment(spec).map_err(anyhow::Error::new)
77 }
78 #[cfg(not(feature = "aixi-gameengine"))]
79 {
80 Err(anyhow::anyhow!(
81 "builtin environment '{}' requires feature 'aixi-gameengine'",
82 spec.canonical_name()
83 ))
84 }
85}
86
87pub fn build_planner_environment(
89 compiled: &CompiledPlannerRunSpec,
90) -> anyhow::Result<(Box<dyn Environment>, &'static str)> {
91 #[allow(unreachable_patterns)]
92 match &compiled.canonical_spec().environment {
93 spec::EnvironmentSpec::Builtin { builtin } => Ok((
94 build_builtin_environment(*builtin)?,
95 builtin.canonical_name(),
96 )),
97 #[cfg(feature = "vm")]
98 spec::EnvironmentSpec::NyxVm(vm) => {
99 let config = NyxVmConfig::from_environment_spec(vm, compiled.resolved_assets())
100 .map_err(anyhow::Error::msg)?;
101 Ok((Box::new(NyxVmEnvironment::new(config)?), "vm"))
102 }
103 #[cfg(not(feature = "vm"))]
104 other => Err(anyhow::anyhow!(
105 "unsupported environment variant '{}' in this build",
106 other.kind_str()
107 )),
108 }
109}
110
111pub fn validate_action_alphabet(
113 compiled: &CompiledPlannerRunSpec,
114 env: &dyn Environment,
115) -> anyhow::Result<()> {
116 let actual: ActionAlphabet = env.get_num_actions();
117 let expected: ActionAlphabet = compiled.interface().agent_actions;
118 if actual != expected {
119 return Err(anyhow::anyhow!(
120 "action_alphabet_mismatch: planner interface declares {} actions but environment exposes {}",
121 expected,
122 actual
123 ));
124 }
125 Ok(())
126}
127
128pub(crate) fn validate_environment_interface(
130 compiled: &CompiledPlannerRunSpec,
131 env: &dyn Environment,
132) -> anyhow::Result<()> {
133 validate_action_alphabet(compiled, env)?;
134 let interface = compiled.interface();
135 let expected_action_bits: usize = interface.agent_actions.action_bits();
136 let actual_action_bits: usize = env.get_action_bits();
137 if actual_action_bits != expected_action_bits {
138 return Err(anyhow::anyhow!(
139 "action_bits_mismatch: planner interface declares {} action bits for {} actions but environment exposes {}",
140 expected_action_bits,
141 interface.agent_actions,
142 actual_action_bits
143 ));
144 }
145 let actual_observation_bits: usize = env.get_observation_bits();
146 if actual_observation_bits != interface.observation_bits {
147 return Err(anyhow::anyhow!(
148 "observation_bits_mismatch: planner interface declares {} observation bits but environment exposes {}",
149 interface.observation_bits,
150 actual_observation_bits
151 ));
152 }
153 let actual_reward_bits: usize = env.get_reward_bits();
154 if actual_reward_bits != interface.reward_bits {
155 return Err(anyhow::anyhow!(
156 "reward_bits_mismatch: planner interface declares {} reward bits but environment exposes {}",
157 interface.reward_bits,
158 actual_reward_bits
159 ));
160 }
161 Ok(())
162}