///|
fn learning_result(
experiment : Experiment,
model : Machine?,
status : LearningStatus,
rounds : Array[LearningRound],
counterexamples : Array[Difference],
) -> LearningResult {
{
model,
status,
statistics: experiment.statistics(),
rounds,
counterexamples,
}
}
///|
/// Learn only through experiment queries. The oracle searches for counterexamples;
/// its evidence determines what a Supported status actually means.
/// Resource limits return Inconclusive with the last completed hypothesis, if any.
pub fn learn(
experiment : Experiment,
oracle : Oracle,
max_states~ : Int32 = 256,
max_rounds~ : Int32 = 64,
max_table_cells~ : Int32 = 1000000,
max_table_symbols~ : Int32 = 4000000,
max_prefixes~ : Int32 = 20000,
) -> LearningResult raise {
if max_states < 1 ||
max_states > 4096 ||
max_rounds < 1 ||
max_table_cells < 1 ||
max_table_symbols < 1 ||
max_prefixes < 1 {
raise InvalidArgument("invalid learning budget")
}
let table = ObservationTable::new(
experiment,
max_cells=max_table_cells,
max_symbols=max_table_symbols,
max_prefixes~,
max_states~,
)
let mut last = None
let rounds = []
let counterexamples = []
try {
for index in 1..<=max_rounds {
table.stabilize()
let hypothesis = table.hypothesis()
last = Some(hypothesis)
let decision = (oracle.check)(hypothesis, experiment)
let mut counterexample_length = None
if decision is Counterexample(word) {
counterexample_length = Some(word.length())
}
rounds.push({
index,
states: hypothesis.state_count(),
prefixes: table.prefixes.length(),
suffixes: table.suffixes.length(),
table_cells: table.cells,
table_symbols: table.symbols,
queries: experiment.queries,
counterexample_length,
})
match decision {
Counterexample(word) => {
let actual = experiment.query(word, bypass_cache=true)
let expected = hypothesis.trace(word)
guard actual != expected else {
raise InvalidArgument("oracle returned a non-counterexample")
}
// Ignore any tail after the first mismatching output. This is a
// distinguishing prefix, not a globally shortest counterexample claim.
let mut end = 0
for i in 0..
return learning_result(
experiment,
last,
Supported(evidence),
rounds,
counterexamples,
)
Inconclusive(reason) =>
return learning_result(
experiment,
last,
Inconclusive(reason),
rounds,
counterexamples,
)
}
}
learning_result(
experiment,
last,
Inconclusive("learning-rounds"),
rounds,
counterexamples,
)
} catch {
LimitExceeded(reason) =>
learning_result(
experiment,
last,
Inconclusive(reason),
rounds,
counterexamples,
)
InconsistentObservation(word~, expected~, actual~) =>
learning_result(
experiment,
last,
Unstable(word~, expected~, actual~),
rounds,
counterexamples,
)
error => raise error
}
}