use crate::symbolic::codegen::codegen_aot_resolution::{
AotResolutionStatus, AotResolver, ResolvedAotArtifact,
};
use crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest;
use crate::symbolic::codegen::codegen_provider_api::{BackendKind, MatrixBackend, PreparedProblem};
use log::{info, warn};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendSelectionPolicy {
NumericOnly,
LambdifyOnly,
AotOnly,
PreferAotThenLambdify,
PreferAotThenNumeric,
PreferLambdifyThenNumeric,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectedBackendKind {
Numeric,
Lambdify,
AotCompiled,
AotRegisteredButNotBuilt,
AotMissing,
}
#[derive(Debug, Clone)]
pub struct SelectedBackend<'a> {
pub problem: PreparedProblem<'a>,
pub requested_backend: BackendKind,
pub effective_backend: SelectedBackendKind,
pub matrix_backend: MatrixBackend,
pub aot_resolution: Option<ResolvedAotArtifact>,
}
#[derive(Debug, Clone)]
pub struct SelectedManifestBackend {
pub requested_backend: BackendKind,
pub effective_backend: SelectedBackendKind,
pub matrix_backend: MatrixBackend,
pub aot_resolution: Option<ResolvedAotArtifact>,
}
impl<'a> SelectedBackend<'a> {
pub fn is_compiled_aot(&self) -> bool {
self.effective_backend == SelectedBackendKind::AotCompiled
}
}
pub fn select_backend<'a>(
problem: &PreparedProblem<'a>,
policy: BackendSelectionPolicy,
resolver: Option<&AotResolver>,
) -> SelectedBackend<'a> {
let selection = match policy {
BackendSelectionPolicy::NumericOnly => {
select_non_aot(problem, SelectedBackendKind::Numeric)
}
BackendSelectionPolicy::LambdifyOnly => {
select_non_aot(problem, SelectedBackendKind::Lambdify)
}
BackendSelectionPolicy::AotOnly => select_aot(problem, resolver),
BackendSelectionPolicy::PreferAotThenLambdify => {
let selection = select_aot(problem, resolver);
match selection.effective_backend {
SelectedBackendKind::AotCompiled
| SelectedBackendKind::AotRegisteredButNotBuilt => selection,
SelectedBackendKind::AotMissing => {
select_non_aot(problem, SelectedBackendKind::Lambdify)
}
_ => unreachable!("AOT selection should only produce AOT states"),
}
}
BackendSelectionPolicy::PreferAotThenNumeric => {
let selection = select_aot(problem, resolver);
match selection.effective_backend {
SelectedBackendKind::AotCompiled
| SelectedBackendKind::AotRegisteredButNotBuilt => selection,
SelectedBackendKind::AotMissing => {
select_non_aot(problem, SelectedBackendKind::Numeric)
}
_ => unreachable!("AOT selection should only produce AOT states"),
}
}
BackendSelectionPolicy::PreferLambdifyThenNumeric => {
select_non_aot(problem, SelectedBackendKind::Lambdify)
}
};
match selection.effective_backend {
SelectedBackendKind::AotCompiled => info!(
"Selected compiled AOT backend for {:?} matrix backend",
selection.matrix_backend
),
SelectedBackendKind::AotRegisteredButNotBuilt => {
warn!("Selected AOT backend, but artifact is registered and not built yet")
}
SelectedBackendKind::AotMissing => warn!(
"AOT artifact is missing; effective backend is {:?}",
selection.requested_backend
),
SelectedBackendKind::Numeric | SelectedBackendKind::Lambdify => info!(
"Selected {:?} backend for {:?} matrix backend",
selection.effective_backend, selection.matrix_backend
),
}
selection
}
pub fn select_backend_by_manifest(
manifest: &PreparedProblemManifest,
policy: BackendSelectionPolicy,
resolver: Option<&AotResolver>,
) -> SelectedManifestBackend {
let matrix_backend = manifest.matrix_backend;
let selected_aot = || {
let aot_resolution = resolver.map(|resolver| resolver.resolve_by_manifest(manifest));
let effective_backend = match aot_resolution.as_ref().map(|resolved| resolved.status) {
Some(AotResolutionStatus::Compiled) => SelectedBackendKind::AotCompiled,
Some(AotResolutionStatus::RegisteredButNotBuilt) => {
SelectedBackendKind::AotRegisteredButNotBuilt
}
Some(AotResolutionStatus::Missing) | None => SelectedBackendKind::AotMissing,
};
SelectedManifestBackend {
requested_backend: BackendKind::Aot,
effective_backend,
matrix_backend,
aot_resolution,
}
};
let selected = match policy {
BackendSelectionPolicy::NumericOnly => SelectedManifestBackend {
requested_backend: BackendKind::Numeric,
effective_backend: SelectedBackendKind::Numeric,
matrix_backend,
aot_resolution: None,
},
BackendSelectionPolicy::LambdifyOnly
| BackendSelectionPolicy::PreferLambdifyThenNumeric => SelectedManifestBackend {
requested_backend: BackendKind::Lambdify,
effective_backend: SelectedBackendKind::Lambdify,
matrix_backend,
aot_resolution: None,
},
BackendSelectionPolicy::PreferAotThenLambdify => {
let aot = selected_aot();
if matches!(
aot.effective_backend,
SelectedBackendKind::AotCompiled | SelectedBackendKind::AotRegisteredButNotBuilt
) {
aot
} else {
SelectedManifestBackend {
requested_backend: BackendKind::Lambdify,
effective_backend: SelectedBackendKind::Lambdify,
matrix_backend,
aot_resolution: None,
}
}
}
BackendSelectionPolicy::AotOnly => selected_aot(),
BackendSelectionPolicy::PreferAotThenNumeric => {
let aot = selected_aot();
if aot.effective_backend == SelectedBackendKind::AotMissing {
SelectedManifestBackend {
requested_backend: BackendKind::Numeric,
effective_backend: SelectedBackendKind::Numeric,
matrix_backend,
aot_resolution: aot.aot_resolution,
}
} else {
aot
}
}
};
match selected.effective_backend {
SelectedBackendKind::AotCompiled => info!(
"Selected compiled AtomView AOT backend for {:?} matrix backend",
selected.matrix_backend
),
SelectedBackendKind::AotRegisteredButNotBuilt => {
warn!("Selected AtomView AOT backend, but artifact is not built yet")
}
SelectedBackendKind::AotMissing => warn!(
"AtomView AOT artifact is missing; effective backend is {:?}",
selected.requested_backend
),
SelectedBackendKind::Numeric | SelectedBackendKind::Lambdify => info!(
"Selected {:?} backend for {:?} AtomView manifest",
selected.effective_backend, selected.matrix_backend
),
}
selected
}
fn select_non_aot<'a>(
problem: &PreparedProblem<'a>,
effective_backend: SelectedBackendKind,
) -> SelectedBackend<'a> {
let requested_backend = match effective_backend {
SelectedBackendKind::Numeric => BackendKind::Numeric,
SelectedBackendKind::Lambdify => BackendKind::Lambdify,
_ => unreachable!("non-AOT selection must resolve to numeric or lambdify"),
};
SelectedBackend {
problem: problem.clone(),
requested_backend,
effective_backend,
matrix_backend: problem.matrix_backend(),
aot_resolution: None,
}
}
fn select_aot<'a>(
problem: &PreparedProblem<'a>,
resolver: Option<&AotResolver>,
) -> SelectedBackend<'a> {
let resolution = resolver.map(|resolver| resolver.resolve_prepared_problem(problem));
let effective_backend = match resolution.as_ref().map(|resolved| resolved.status) {
Some(AotResolutionStatus::Compiled) => SelectedBackendKind::AotCompiled,
Some(AotResolutionStatus::RegisteredButNotBuilt) => {
SelectedBackendKind::AotRegisteredButNotBuilt
}
Some(AotResolutionStatus::Missing) | None => SelectedBackendKind::AotMissing,
};
SelectedBackend {
problem: problem.clone(),
requested_backend: BackendKind::Aot,
effective_backend,
matrix_backend: problem.matrix_backend(),
aot_resolution: resolution,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbolic::codegen::codegen_aot_driver::generated_aot_crate_from_prepared_problem;
use crate::symbolic::codegen::codegen_aot_registry::AotRegistry;
use crate::symbolic::codegen::codegen_provider_api::{
BackendKind, MatrixBackend, PreparedDenseProblem, PreparedProblem,
};
use crate::symbolic::codegen::codegen_runtime_api::{
DenseJacobianChunkingStrategy, ResidualChunkingStrategy,
};
use crate::symbolic::codegen::codegen_tasks::{JacobianTask, ResidualTask};
use crate::symbolic::codegen::rust_backend::codegen_aot_build::{
AotBuildProfile, AotBuildRequest,
};
use crate::symbolic::symbolic_engine::Expr;
use std::fs;
use tempfile::tempdir;
fn sample_prepared_problem() -> PreparedProblem<'static> {
let residuals = Box::leak(Box::new(vec![Expr::parse_expression("x + 1")]));
let jacobian = Box::leak(Box::new(vec![vec![Expr::parse_expression("1")]]));
let vars = Box::leak(Box::new(vec!["x"]));
PreparedProblem::dense(PreparedDenseProblem::new(
BackendKind::Aot,
MatrixBackend::Dense,
ResidualTask {
fn_name: "eval_residual",
residuals,
variables: vars,
params: None,
}
.runtime_plan(ResidualChunkingStrategy::Whole),
JacobianTask {
fn_name: "eval_jacobian",
jacobian,
variables: vars,
params: None,
}
.runtime_plan(DenseJacobianChunkingStrategy::Whole),
))
}
#[test]
fn selection_prefers_compiled_aot_when_artifact_exists() {
let prepared = sample_prepared_problem();
let manifest =
crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(&prepared);
let dir = tempdir().expect("tempdir should exist");
let build = AotBuildRequest::new(
generated_aot_crate_from_prepared_problem(
"generated_backend_select_fixture",
"generated_backend_select_module",
&prepared,
),
dir.path(),
AotBuildProfile::Release,
)
.materialize()
.expect("build request should materialize");
fs::create_dir_all(&build.artifact_dir).expect("artifact dir should be creatable");
fs::write(&build.expected_rlib, b"fake rlib").expect("expected rlib should be writable");
let mut registry = AotRegistry::new();
registry.register_materialized_build(manifest, &build);
let resolver = AotResolver::new(registry);
let selected = select_backend(
&prepared,
BackendSelectionPolicy::PreferAotThenLambdify,
Some(&resolver),
);
assert_eq!(selected.requested_backend, BackendKind::Aot);
assert_eq!(selected.effective_backend, SelectedBackendKind::AotCompiled);
assert!(selected.is_compiled_aot());
}
#[test]
fn selection_falls_back_to_lambdify_when_aot_is_missing() {
let prepared = sample_prepared_problem();
let resolver = AotResolver::new(AotRegistry::new());
let selected = select_backend(
&prepared,
BackendSelectionPolicy::PreferAotThenLambdify,
Some(&resolver),
);
assert_eq!(selected.requested_backend, BackendKind::Lambdify);
assert_eq!(selected.effective_backend, SelectedBackendKind::Lambdify);
assert!(selected.aot_resolution.is_none());
}
#[test]
fn selection_keeps_registered_but_not_built_aot_for_aot_only_policy() {
let prepared = sample_prepared_problem();
let manifest =
crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(&prepared);
let dir = tempdir().expect("tempdir should exist");
let build = AotBuildRequest::new(
generated_aot_crate_from_prepared_problem(
"generated_backend_pending_fixture",
"generated_backend_select_module",
&prepared,
),
dir.path(),
AotBuildProfile::Release,
)
.materialize()
.expect("build request should materialize");
let mut registry = AotRegistry::new();
registry.register_materialized_build(manifest, &build);
let resolver = AotResolver::new(registry);
let selected = select_backend(&prepared, BackendSelectionPolicy::AotOnly, Some(&resolver));
assert_eq!(selected.requested_backend, BackendKind::Aot);
assert_eq!(
selected.effective_backend,
SelectedBackendKind::AotRegisteredButNotBuilt
);
assert!(selected.aot_resolution.is_some());
}
#[test]
fn manifest_selection_preserves_atom_native_matrix_contract() {
let prepared = sample_prepared_problem();
let manifest =
crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(&prepared);
let selected = select_backend_by_manifest(
&manifest,
BackendSelectionPolicy::PreferAotThenLambdify,
Some(&AotResolver::new(AotRegistry::new())),
);
assert_eq!(selected.matrix_backend, MatrixBackend::Dense);
assert_eq!(selected.requested_backend, BackendKind::Lambdify);
assert_eq!(selected.effective_backend, SelectedBackendKind::Lambdify);
assert!(selected.aot_resolution.is_none());
}
#[test]
fn manifest_selection_reuses_registered_artifact_without_expr_runtime_plan() {
let prepared = sample_prepared_problem();
let manifest =
crate::symbolic::codegen::codegen_manifest::PreparedProblemManifest::from(&prepared);
let dir = tempdir().expect("tempdir should exist");
let build = AotBuildRequest::new(
generated_aot_crate_from_prepared_problem(
"generated_manifest_select_fixture",
"generated_manifest_select_module",
&prepared,
),
dir.path(),
AotBuildProfile::Release,
)
.materialize()
.expect("build request should materialize");
fs::create_dir_all(&build.artifact_dir).expect("artifact dir should be creatable");
fs::write(&build.expected_rlib, b"fake rlib").expect("expected rlib should be writable");
let mut registry = AotRegistry::new();
registry.register_materialized_build(manifest.clone(), &build);
let selected = select_backend_by_manifest(
&manifest,
BackendSelectionPolicy::AotOnly,
Some(&AotResolver::new(registry)),
);
assert_eq!(selected.requested_backend, BackendKind::Aot);
assert_eq!(selected.effective_backend, SelectedBackendKind::AotCompiled);
assert_eq!(selected.matrix_backend, MatrixBackend::Dense);
assert!(selected.aot_resolution.is_some());
}
}