use super::material::realized;
use super::space::{advanced, interleaving_space};
use super::types::{
Counterexample, EXPLORATION_STARVED, ExplorationBound, ExplorationMode, ExplorationReading,
ExplorationRefusal, ExplorationSite, ExplorationStanding, Interleaving, InterleavingSpace,
StrandSet,
};
use crate::descriptor::PopulationRef;
use crate::generate::{
ByteSource, CaseIndex, CommandSequence, GenerationHalt, GenerationPlan, InputOrigin,
RejectionAllowance, RootSeed, SizeProgression, admit_every_sequence, decode_arbitrary, drive,
};
use crate::properties::{Holding, TransitionContract, holds_over_history};
use crate::report::{
ByteBudget, CaseBudget, FailureClass, GenerationProfile, TrialConclusion, TrialFinding,
};
const CHOICE_GENERATOR: &str = "interleaving-choices";
const CHOICE_GENERATOR_REVISION: u32 = 1;
pub fn explored<State, Command: Clone>(
set: &StrandSet<Command>,
contract: &TransitionContract<State, Command>,
bound: ExplorationBound,
population: PopulationRef,
seed: RootSeed,
) -> Result<ExplorationReading, ExplorationRefusal> {
let space = interleaving_space(set);
if within_bound(space, bound) {
Ok(enumerated(set, contract, space))
} else {
sampled(set, contract, bound, population, seed, space)
}
}
#[must_use]
#[track_caller]
pub fn concluded(reading: &ExplorationReading) -> TrialConclusion {
match reading.standing() {
ExplorationStanding::CounterexampleFound(counterexample) => {
TrialConclusion::Refused(counterexample.finding().clone())
}
ExplorationStanding::SpaceExhaustedAllHold => TrialConclusion::Passed,
ExplorationStanding::SampledAllHold => sampled_conclusion(reading.mode()),
}
}
fn sampled_conclusion(mode: ExplorationMode) -> TrialConclusion {
match mode {
ExplorationMode::Sampled {
halt: GenerationHalt::CaseBudgetMet,
census: _,
} => TrialConclusion::Passed,
ExplorationMode::Exhaustive | ExplorationMode::Sampled { .. } => {
crate::properties::concluded(
Holding::Fails,
FailureClass::RefusedByCheck,
EXPLORATION_STARVED,
)
}
}
}
fn within_bound(space: InterleavingSpace, bound: ExplorationBound) -> bool {
match space {
InterleavingSpace::Counted(count) => count <= u128::from(bound.interleavings()),
InterleavingSpace::BeyondCount => false,
}
}
fn enumerated<State, Command: Clone>(
set: &StrandSet<Command>,
contract: &TransitionContract<State, Command>,
space: InterleavingSpace,
) -> ExplorationReading {
let mut material = vec![0u8; set.steps()];
let mut explored = 0u64;
loop {
let realization = realized(set, &material);
let ordinal = explored;
explored = explored.saturating_add(1u64);
if let TrialConclusion::Refused(finding) =
holds_over_history(contract, &realization.commands)
{
let counterexample = Counterexample::found(
ExplorationSite::Enumerated { ordinal },
Interleaving::declared(realization.choices),
finding,
);
return ExplorationReading::read(
space,
ExplorationMode::Exhaustive,
explored,
ExplorationStanding::CounterexampleFound(counterexample),
);
}
if !advanced(&mut material, &realization.radixes) {
break;
}
}
ExplorationReading::read(
space,
ExplorationMode::Exhaustive,
explored,
ExplorationStanding::SpaceExhaustedAllHold,
)
}
fn sampled<State, Command: Clone>(
set: &StrandSet<Command>,
contract: &TransitionContract<State, Command>,
bound: ExplorationBound,
population: PopulationRef,
seed: RootSeed,
space: InterleavingSpace,
) -> Result<ExplorationReading, ExplorationRefusal> {
let overflow = ExplorationRefusal::SampleBytesOverflow {
samples: bound.samples(),
steps: set.steps(),
};
let Ok(step_bytes) = u64::try_from(set.steps()) else {
return Err(overflow);
};
let Some(bytes) = u64::from(bound.samples()).checked_mul(step_bytes) else {
return Err(overflow);
};
let plan = GenerationPlan::declared(
population,
GenerationProfile::declared(CHOICE_GENERATOR, CHOICE_GENERATOR_REVISION),
InputOrigin::Seeded(seed),
CaseBudget::declared(bound.samples()),
ByteBudget::declared(bytes),
RejectionAllowance::NoRejections,
SizeProgression::Constant { width: set.width() },
)
.map_err(ExplorationRefusal::SamplingPlanRefused)?;
let source = ByteSource::of_plan(&plan);
let generated = drive(&plan, &source, decode_arbitrary::<u8>, admit_every_sequence);
let mode = ExplorationMode::Sampled {
census: generated.census(),
halt: generated.halt(),
};
let (explored, counterexample) = sampled_counterexample(set, contract, generated.sequences());
let standing = counterexample.map_or(
ExplorationStanding::SampledAllHold,
ExplorationStanding::CounterexampleFound,
);
Ok(ExplorationReading::read(space, mode, explored, standing))
}
fn sampled_counterexample<State, Command: Clone>(
set: &StrandSet<Command>,
contract: &TransitionContract<State, Command>,
sequences: &[CommandSequence<u8>],
) -> (u64, Option<Counterexample>) {
let mut explored = 0u64;
for sequence in sequences {
let realization = realized(set, sequence.commands());
explored = explored.saturating_add(1u64);
let TrialConclusion::Refused(finding) = holds_over_history(contract, &realization.commands)
else {
continue;
};
return (
explored,
Some(sampled_found(sequence.case(), realization.choices, finding)),
);
}
(explored, None)
}
fn sampled_found(case: CaseIndex, choices: Vec<u8>, finding: TrialFinding) -> Counterexample {
Counterexample::found(
ExplorationSite::Sampled { case },
Interleaving::declared(choices),
finding,
)
}