use std::{collections::BTreeMap, marker::PhantomData};
use eredu_checkpoint::recipe::{DerivedWeightRecipe, RecipeMetadata};
use eredu_nn::{NeuralBackend, Tensor};
use crate::{
LayerWeightResidency, LayeredArchitecture, LayerwisePolicy, LayerwiseRuntime, ParameterBackend,
ParameterGroupOwner, RealtimeWeightComponentRequirement, RealtimeWeightComponentRole,
RealtimeWeightLoweringRequirement, RuntimeState, SelectedRealtimeRealization,
SubmissionBackend, WeightBinding, WeightBindingPlan,
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RealtimeArchitectureConstructionIdentity {
architecture: crate::RealtimeIdentity,
speech_schedule: crate::RealtimeIdentity,
state_layout: crate::RealtimeIdentity,
}
impl RealtimeArchitectureConstructionIdentity {
pub fn new(
architecture: crate::RealtimeIdentity,
speech_schedule: crate::RealtimeIdentity,
state_layout: crate::RealtimeIdentity,
) -> Self {
Self {
architecture,
speech_schedule,
state_layout,
}
}
}
pub trait RealtimeArchitectureIdentity {
fn realtime_construction_identity(
&self,
) -> Result<RealtimeArchitectureConstructionIdentity, String>;
}
pub struct RealizedRealtimeState<S> {
state: S,
realization: crate::SelectedRealtimeStateRealization,
}
impl<S> RealizedRealtimeState<S> {
pub fn new(state: S, realization: crate::SelectedRealtimeStateRealization) -> Self {
Self { state, realization }
}
pub fn into_parts(self) -> (S, crate::SelectedRealtimeStateRealization) {
(self.state, self.realization)
}
}
pub struct RealizedRealtimePolicy<P> {
policy: P,
residency: LayerWeightResidency,
}
impl<P> RealizedRealtimePolicy<P> {
pub fn new(policy: P, residency: LayerWeightResidency) -> Self {
Self { policy, residency }
}
pub fn into_parts(self) -> (P, LayerWeightResidency) {
(self.policy, self.residency)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RealtimeMaterializationComponent {
requirement: RealtimeWeightComponentRequirement,
source_provenance: Vec<eredu_checkpoint::store::TensorSourceProvenance>,
}
impl RealtimeMaterializationComponent {
pub fn new(
requirement: RealtimeWeightComponentRequirement,
source_provenance: impl IntoIterator<Item = eredu_checkpoint::store::TensorSourceProvenance>,
) -> Result<Self, RealtimeModelContractError> {
let source_provenance = source_provenance.into_iter().collect::<Vec<_>>();
if requirement.source_occurrences().len() != source_provenance.len()
|| requirement
.source_occurrences()
.iter()
.zip(&source_provenance)
.any(|(expected, actual)| expected.as_str() != actual.catalog_key)
{
return Err(RealtimeModelContractError::SourceProvenanceMismatch {
target: requirement.target().as_str().to_owned(),
});
}
Ok(Self {
requirement,
source_provenance,
})
}
pub const fn requirement(&self) -> &RealtimeWeightComponentRequirement {
&self.requirement
}
pub const fn recipe(&self) -> Option<&DerivedWeightRecipe> {
self.requirement.recipe()
}
pub const fn recipe_output(&self) -> Option<&RecipeMetadata> {
self.requirement.recipe_output()
}
pub fn source_provenance(&self) -> &[eredu_checkpoint::store::TensorSourceProvenance] {
&self.source_provenance
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RealtimeMaterializationTask {
lowering: RealtimeWeightLoweringRequirement,
owner: ParameterGroupOwner,
components: Vec<RealtimeMaterializationComponent>,
}
impl RealtimeMaterializationTask {
pub fn new(
lowering: RealtimeWeightLoweringRequirement,
owner: ParameterGroupOwner,
components: impl IntoIterator<Item = RealtimeMaterializationComponent>,
) -> Result<Self, RealtimeModelContractError> {
let components = components.into_iter().collect::<Vec<_>>();
let expected = lowering.components();
if components.len() != expected.len()
|| components
.iter()
.zip(expected)
.any(|(actual, expected)| actual.requirement() != expected)
{
return Err(RealtimeModelContractError::ComponentCoverageMismatch {
target: lowering.target().as_str().to_owned(),
});
}
let primary = components
.iter()
.find(|component| {
component.requirement().role() == RealtimeWeightComponentRole::Primary
})
.expect("selected lowering validation guarantees one primary component");
if primary
.recipe_output()
.is_some_and(|output| output.shape != lowering.descriptor().physical_shape())
{
return Err(RealtimeModelContractError::RecipeGeometryMismatch {
target: lowering.target().as_str().to_owned(),
});
}
if let (Some(output), eredu_checkpoint::SourceTensorEncoding::Safetensors(stored)) =
(primary.recipe_output(), lowering.descriptor().source())
{
if output.dtype != eredu_checkpoint::recipe::RecipeDtype::from(stored.clone()) {
return Err(RealtimeModelContractError::RecipeEncodingMismatch {
target: lowering.target().as_str().to_owned(),
});
}
}
Ok(Self {
lowering,
owner,
components,
})
}
pub const fn lowering(&self) -> &RealtimeWeightLoweringRequirement {
&self.lowering
}
pub const fn owner(&self) -> &ParameterGroupOwner {
&self.owner
}
pub fn components(&self) -> &[RealtimeMaterializationComponent] {
&self.components
}
}
#[derive(Debug, Clone)]
pub struct RealtimeTaskBindingPlan {
pinned: Vec<WeightBinding>,
units: BTreeMap<ParameterGroupOwner, Vec<WeightBinding>>,
}
impl RealtimeTaskBindingPlan {
pub fn into_parts(
self,
) -> (
Vec<WeightBinding>,
BTreeMap<ParameterGroupOwner, Vec<WeightBinding>>,
) {
(self.pinned, self.units)
}
}
pub fn preflight_realtime_materialization_tasks<B: ParameterBackend>(
tasks: &[RealtimeMaterializationTask],
source: &dyn eredu_checkpoint::store::CheckpointSource,
) -> Result<(), RealtimeModelContractError> {
for task in tasks {
let transformed = matches!(
task.lowering().kind(),
crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
);
let targets = task
.components()
.iter()
.map(|component| component.requirement().target().as_str())
.collect::<std::collections::BTreeSet<_>>();
for component in task.components() {
for admitted in component.source_provenance() {
let actual = source
.source_provenance(&admitted.catalog_key)
.map_err(|error| RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
})?;
if &actual != admitted {
return Err(RealtimeModelContractError::BindingPlan {
detail: format!(
"realtime component {:?} differs from admitted source provenance",
component.requirement().target().as_str()
),
});
}
}
if let Some(owner) = component.requirement().recipe_owner() {
if owner != component.requirement().target() && !targets.contains(owner.as_str()) {
return Err(RealtimeModelContractError::BindingPlan {
detail: format!("realtime alias owner {:?} is absent", owner.as_str()),
});
}
}
if let Some(recipe) = component.recipe() {
let actual = recipe.infer(source).map_err(|error| {
RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
}
})?;
if component.recipe_output() != Some(&actual) {
return Err(RealtimeModelContractError::BindingPlan {
detail: format!(
"realtime component {:?} recipe output drifted",
component.requirement().target().as_str()
),
});
}
B::preflight_recipe(recipe, source).map_err(|error| {
RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
}
})?;
} else if component.requirement().recipe_owner().is_none() && !transformed {
return Err(RealtimeModelContractError::BindingPlan {
detail: format!(
"realtime component {:?} has neither recipe nor alias owner",
component.requirement().target().as_str()
),
});
}
}
}
Ok(())
}
pub fn realtime_task_binding_plan(
tasks: &[RealtimeMaterializationTask],
source: &dyn eredu_checkpoint::store::CheckpointSource,
) -> Result<RealtimeTaskBindingPlan, RealtimeModelContractError> {
let mut pinned = Vec::new();
let mut units = BTreeMap::<ParameterGroupOwner, Vec<WeightBinding>>::new();
for task in tasks {
let destination = match task.owner() {
ParameterGroupOwner::StaticRole(_) | ParameterGroupOwner::StaticAnyOf(_) => &mut pinned,
ParameterGroupOwner::ExecutionUnit { .. } => {
units.entry(task.owner().clone()).or_default()
}
};
let transformed = matches!(
task.lowering().kind(),
crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
);
for component in task.components() {
let requirement = component.requirement();
let target = requirement.target().as_str();
let binding = if transformed {
if !source.is_authoritative_materialized_key(target) {
return Err(RealtimeModelContractError::BindingPlan {
detail: format!(
"transformed realtime output {target:?} is not authoritative"
),
});
}
let metadata = source.source_metadata(target).map_err(|error| {
RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
}
})?;
WeightBinding::new(
target,
target,
eredu_checkpoint::store::TensorSelection::Full,
metadata.encoded_byte_len,
)
} else {
let output = component.recipe_output().ok_or_else(|| {
RealtimeModelContractError::BindingPlan {
detail: format!("realtime recipe component {target:?} has no output"),
}
})?;
match requirement.recipe_owner() {
Some(owner) if owner != requirement.target() => {
WeightBinding::alias(target, owner.as_str(), output.byte_len())
}
_ => WeightBinding::from_recipe(
target,
component.recipe().cloned().ok_or_else(|| {
RealtimeModelContractError::BindingPlan {
detail: format!("realtime component {target:?} has no recipe"),
}
})?,
output.byte_len(),
),
}
}
.map_err(|error| RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
})?
.with_logical_target(task.lowering().target().as_str())
.map_err(|error| RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
})?;
destination.push(binding);
}
}
WeightBindingPlan::new(&pinned).map_err(|error| RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
})?;
for bindings in units.values() {
WeightBindingPlan::new(bindings).map_err(|error| {
RealtimeModelContractError::BindingPlan {
detail: error.to_string(),
}
})?;
}
Ok(RealtimeTaskBindingPlan { pinned, units })
}
#[derive(Debug)]
pub struct PreparedRealtimeModelContract {
selected: SelectedRealtimeRealization,
tasks: Vec<RealtimeMaterializationTask>,
}
impl PreparedRealtimeModelContract {
pub fn new(
selected: SelectedRealtimeRealization,
tasks: impl IntoIterator<Item = RealtimeMaterializationTask>,
) -> Result<Self, RealtimeModelContractError> {
let tasks = tasks.into_iter().collect::<Vec<_>>();
if tasks.len() != selected.weight_lowerings().len() {
return Err(RealtimeModelContractError::TaskCoverageMismatch);
}
for (task, selected_lowering) in tasks.iter().zip(selected.weight_lowerings()) {
if selected_lowering != &task.lowering {
return Err(RealtimeModelContractError::TaskSelectionMismatch {
target: task.lowering.target().as_str().to_owned(),
});
}
let expected_owner = selected
.execution_parameters()
.groups()
.iter()
.find_map(|group| {
group
.group()
.members()
.iter()
.any(|member| member.target() == task.lowering.target().as_str())
.then(|| group.owner())
})
.ok_or_else(|| RealtimeModelContractError::TaskOwnerUnavailable {
target: task.lowering.target().as_str().to_owned(),
})?;
if expected_owner != &task.owner {
return Err(RealtimeModelContractError::TaskOwnerMismatch {
target: task.lowering.target().as_str().to_owned(),
});
}
}
Ok(Self { selected, tasks })
}
pub const fn selected(&self) -> &SelectedRealtimeRealization {
&self.selected
}
pub fn tasks(&self) -> &[RealtimeMaterializationTask] {
&self.tasks
}
pub fn into_parts(
self,
) -> (
SelectedRealtimeRealization,
Vec<RealtimeMaterializationTask>,
) {
(self.selected, self.tasks)
}
}
pub trait RealtimeModelConstructionMechanisms<A, B>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
A: LayeredArchitecture<B, Self::State>,
Self::State: RuntimeState<B>,
Self::ResidentPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>,
Self::BoundedPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>,
{
type State: RuntimeState<B>;
type PolicyError;
type ResidentPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>;
type BoundedPolicy: LayerwisePolicy<B, A::Unit, Error = Self::PolicyError>;
type Error;
#[allow(clippy::too_many_arguments)]
fn prepare_resident_materialization(
&mut self,
architecture: &mut A,
units: &mut [A::Unit],
source_architecture: Option<&mut A>,
source_units: Option<&mut [A::Unit]>,
tasks: &[RealtimeMaterializationTask],
selected: &SelectedRealtimeRealization,
context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
) -> Result<(), Self::Error>;
fn resident_policy(
&mut self,
architecture: &mut A,
units: Vec<A::Unit>,
selected: &SelectedRealtimeRealization,
context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
) -> Result<RealizedRealtimePolicy<Self::ResidentPolicy>, Self::Error>;
#[allow(clippy::too_many_arguments)]
fn bounded_policy(
&mut self,
architecture: &mut A,
source_architecture: Option<&mut A>,
tasks: &[RealtimeMaterializationTask],
selected: &SelectedRealtimeRealization,
context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
) -> Result<RealizedRealtimePolicy<Self::BoundedPolicy>, Self::Error>;
fn realize_state(
&mut self,
selected: &crate::SelectedRealtimeStateRealization,
context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
) -> Result<RealizedRealtimeState<Self::State>, Self::Error>;
}
pub enum RealtimeLayerwiseRuntime<A, B, S, R, P>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
R: LayerwisePolicy<B, A::Unit>,
P: LayerwisePolicy<B, A::Unit>,
{
Resident(LayerwiseRuntime<A, B, S, R>),
Bounded(LayerwiseRuntime<A, B, S, P>),
}
pub struct ConstructedRealtimeExecution<A, B, M>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
M: RealtimeModelConstructionMechanisms<A, B>,
A: LayeredArchitecture<B, M::State>,
{
selected: SelectedRealtimeRealization,
execution: RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
mechanisms: M,
backend: PhantomData<fn() -> B>,
}
impl<A, B, M> ConstructedRealtimeExecution<A, B, M>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
M: RealtimeModelConstructionMechanisms<A, B>,
A: LayeredArchitecture<B, M::State>,
{
pub const fn selected(&self) -> &SelectedRealtimeRealization {
&self.selected
}
pub const fn execution(
&self,
) -> &RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
&self.execution
}
pub fn execution_mut(
&mut self,
) -> &mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
&mut self.execution
}
pub const fn mechanisms(&self) -> &M {
&self.mechanisms
}
pub fn mechanisms_mut(&mut self) -> &mut M {
&mut self.mechanisms
}
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
SelectedRealtimeRealization,
RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
M,
) {
(self.selected, self.execution, self.mechanisms)
}
}
pub struct ConstructedRealtimeModel<A, B, M>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
M: RealtimeModelConstructionMechanisms<A, B>,
A: LayeredArchitecture<B, M::State>,
{
execution: ConstructedRealtimeExecution<A, B, M>,
state: M::State,
}
impl<A, B, M> ConstructedRealtimeModel<A, B, M>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
M: RealtimeModelConstructionMechanisms<A, B>,
A: LayeredArchitecture<B, M::State>,
{
pub const fn selected(&self) -> &SelectedRealtimeRealization {
self.execution.selected()
}
pub const fn execution(
&self,
) -> &RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
self.execution.execution()
}
pub fn execution_mut(
&mut self,
) -> &mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy> {
self.execution.execution_mut()
}
pub const fn state(&self) -> &M::State {
&self.state
}
pub fn state_mut(&mut self) -> &mut M::State {
&mut self.state
}
#[allow(clippy::type_complexity)]
pub fn execution_and_state_mut(
&mut self,
) -> (
&mut RealtimeLayerwiseRuntime<A, B, M::State, M::ResidentPolicy, M::BoundedPolicy>,
&mut M::State,
) {
(self.execution.execution_mut(), &mut self.state)
}
#[allow(clippy::type_complexity)]
pub fn constructed_execution_and_state_mut(
&mut self,
) -> (&mut ConstructedRealtimeExecution<A, B, M>, &mut M::State) {
(&mut self.execution, &mut self.state)
}
pub const fn mechanisms(&self) -> &M {
self.execution.mechanisms()
}
pub fn mechanisms_mut(&mut self) -> &mut M {
self.execution.mechanisms_mut()
}
pub fn into_execution_and_state(self) -> (ConstructedRealtimeExecution<A, B, M>, M::State) {
(self.execution, self.state)
}
}
#[allow(clippy::type_complexity)]
pub fn construct_realtime_model<A, B, M>(
mut architecture: A,
mut source_architecture: Option<A>,
prepared: PreparedRealtimeModelContract,
mut mechanisms: M,
context: &<<B as NeuralBackend>::Tensor as Tensor>::Context,
) -> Result<
ConstructedRealtimeModel<A, B, M>,
RealtimeModelConstructionError<A::Error, M::PolicyError, M::Error>,
>
where
B: SubmissionBackend<Executor = <<B as NeuralBackend>::Tensor as Tensor>::Context>,
M: RealtimeModelConstructionMechanisms<A, B>,
A: LayeredArchitecture<B, M::State> + RealtimeArchitectureIdentity,
A::Error: std::fmt::Display,
M::PolicyError: std::fmt::Display,
M::Error: std::fmt::Display,
{
let (selected, tasks) = prepared.into_parts();
let requires_source_architecture = selected.weight_lowerings().iter().any(|lowering| {
matches!(
lowering.kind(),
crate::WeightLoweringKind::Transform | crate::WeightLoweringKind::DerivedTransform
)
});
if requires_source_architecture != source_architecture.is_some() {
return Err(RealtimeModelConstructionError::Contract(
"selected realtime lowering and source-format architecture presence differ".into(),
));
}
validate_architecture::<A, B, M::State>(&architecture, &selected, false, context)
.map_err(widen_error)?;
if let Some(source) = source_architecture.as_ref() {
validate_architecture::<A, B, M::State>(source, &selected, true, context)
.map_err(widen_error)?;
}
let (state, state_realization) = mechanisms
.realize_state(selected.state(), context)
.map_err(RealtimeModelConstructionError::Mechanism)?
.into_parts();
if &state_realization != selected.state() || state.layout() != selected.state().layout() {
return Err(RealtimeModelConstructionError::Contract(
"realized realtime state differs from selection".into(),
));
}
let execution = match selected.residency() {
LayerWeightResidency::FullyResident => {
let mut units = construct_units::<A, B, M::State>(&architecture, &selected, context)
.map_err(widen_error)?;
let mut source_units = source_architecture
.as_ref()
.map(|source| construct_units::<A, B, M::State>(source, &selected, context))
.transpose()
.map_err(widen_error)?;
mechanisms
.prepare_resident_materialization(
&mut architecture,
&mut units,
source_architecture.as_mut(),
source_units.as_deref_mut(),
&tasks,
&selected,
context,
)
.map_err(RealtimeModelConstructionError::Mechanism)?;
let (policy, residency) = mechanisms
.resident_policy(&mut architecture, units, &selected, context)
.map_err(RealtimeModelConstructionError::Mechanism)?
.into_parts();
if residency != selected.residency() {
return Err(RealtimeModelConstructionError::Contract(
"realized realtime weight residency differs from selection".into(),
));
}
RealtimeLayerwiseRuntime::Resident(LayerwiseRuntime::new(architecture, policy))
}
LayerWeightResidency::LayerwiseHost(_) | LayerWeightResidency::DenseDiskStream(_) => {
let (policy, residency) = mechanisms
.bounded_policy(
&mut architecture,
source_architecture.as_mut(),
&tasks,
&selected,
context,
)
.map_err(RealtimeModelConstructionError::Mechanism)?
.into_parts();
if residency != selected.residency() {
return Err(RealtimeModelConstructionError::Contract(
"realized realtime weight residency differs from selection".into(),
));
}
RealtimeLayerwiseRuntime::Bounded(LayerwiseRuntime::new(architecture, policy))
}
};
Ok(ConstructedRealtimeModel {
execution: ConstructedRealtimeExecution {
selected,
execution,
mechanisms,
backend: PhantomData,
},
state,
})
}
fn widen_error<A, P, M>(
error: RealtimeModelConstructionError<A, std::convert::Infallible, std::convert::Infallible>,
) -> RealtimeModelConstructionError<A, P, M>
where
A: std::fmt::Display,
P: std::fmt::Display,
M: std::fmt::Display,
{
match error {
RealtimeModelConstructionError::Architecture(error) => {
RealtimeModelConstructionError::Architecture(error)
}
RealtimeModelConstructionError::Contract(error) => {
RealtimeModelConstructionError::Contract(error)
}
RealtimeModelConstructionError::Mechanism(error) => match error {},
RealtimeModelConstructionError::Policy(error) => match error {},
}
}
fn construct_units<A, B, S>(
architecture: &A,
selected: &SelectedRealtimeRealization,
context: &<B::Tensor as Tensor>::Context,
) -> Result<
Vec<A::Unit>,
RealtimeModelConstructionError<A::Error, std::convert::Infallible, std::convert::Infallible>,
>
where
B: NeuralBackend,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S>,
A::Error: std::fmt::Display,
{
(0..selected.execution_units().len())
.map(|ordinal| {
let address = selected
.execution_units()
.address(ordinal)
.expect("selected execution ordinal has an address");
architecture
.build_unit(address.group(), address.index(), context)
.map_err(RealtimeModelConstructionError::Architecture)
})
.collect()
}
fn validate_architecture<A, B, S>(
architecture: &A,
selected: &SelectedRealtimeRealization,
source: bool,
context: &<B::Tensor as Tensor>::Context,
) -> Result<
(),
RealtimeModelConstructionError<A::Error, std::convert::Infallible, std::convert::Infallible>,
>
where
B: NeuralBackend,
S: RuntimeState<B>,
A: LayeredArchitecture<B, S> + RealtimeArchitectureIdentity,
A::Error: std::fmt::Display,
{
if !source {
let actual = architecture
.realtime_construction_identity()
.map_err(RealtimeModelConstructionError::Contract)?;
let requirements = selected.requirements();
let expected = RealtimeArchitectureConstructionIdentity::new(
requirements.architecture().clone(),
requirements.speech_schedule_identity().clone(),
requirements.state_layout_identity().clone(),
);
if actual != expected {
return Err(RealtimeModelConstructionError::Contract(
"constructed realtime architecture identities differ from selection".into(),
));
}
}
if architecture
.execution_graph()
.map_err(RealtimeModelConstructionError::Architecture)?
!= *selected.execution_graph()
{
return Err(RealtimeModelConstructionError::Contract(
"constructed realtime execution graph differs from selection".into(),
));
}
for group in 0..selected.execution_graph().groups().len() {
let actual = architecture
.group_unit_count(group)
.map_err(RealtimeModelConstructionError::Architecture)?;
let expected = selected
.execution_units()
.group_range(group)
.expect("selected layout contains every graph group")
.len();
if actual != expected {
return Err(RealtimeModelConstructionError::Contract(format!(
"constructed realtime group {group} unit count differs from selection"
)));
}
}
let actual = architecture
.parameter_description(context)
.map_err(RealtimeModelConstructionError::Architecture)?;
let expected = if source {
selected.source_parameters()
} else {
selected.execution_parameters()
};
if &actual != expected {
return Err(RealtimeModelConstructionError::Contract(
"constructed realtime parameter topology differs from selection".into(),
));
}
if !source
&& architecture
.state_layout()
.map_err(RealtimeModelConstructionError::Architecture)?
!= *selected.state().layout()
{
return Err(RealtimeModelConstructionError::Contract(
"constructed realtime state layout differs from selection".into(),
));
}
Ok(())
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum RealtimeModelContractError {
#[error("realtime source provenance differs for target {target}")]
SourceProvenanceMismatch {
target: String,
},
#[error("realtime recipe geometry differs for target {target}")]
RecipeGeometryMismatch {
target: String,
},
#[error("realtime recipe encoding differs for target {target}")]
RecipeEncodingMismatch {
target: String,
},
#[error("realtime component payload differs for target {target}")]
ComponentCoverageMismatch {
target: String,
},
#[error("realtime materialization tasks do not exactly cover selected targets")]
TaskCoverageMismatch,
#[error("realtime materialization task differs from selection for target {target}")]
TaskSelectionMismatch {
target: String,
},
#[error("realtime materialization target {target} has no execution owner")]
TaskOwnerUnavailable {
target: String,
},
#[error("realtime materialization owner differs from selection for target {target}")]
TaskOwnerMismatch {
target: String,
},
#[error("realtime binding plan is invalid: {detail}")]
BindingPlan {
detail: String,
},
}
#[derive(Debug, thiserror::Error)]
pub enum RealtimeModelConstructionError<A, P, M>
where
A: std::fmt::Display,
P: std::fmt::Display,
M: std::fmt::Display,
{
#[error("realtime architecture construction failed: {0}")]
Architecture(A),
#[error("invalid realtime model construction: {0}")]
Contract(String),
#[error("realtime model mechanism failed: {0}")]
Mechanism(M),
#[error("realtime residency policy failed: {0}")]
Policy(P),
}