///|
/// Import concrete spans as an atomic qualitative network; coordinates are discarded.
pub fn observe(
names : Array[String],
spans : Array[Interval],
) -> Network raise AllenError {
let net = Network::new(names)
if names.length() != spans.length() {
raise InvalidFormat("observation length mismatch")
}
let n = net.size()
let cells = net.cells.copy()
let sources : Array[Constraint] = []
for i = 0; i < n; i = i + 1 {
for j = i + 1; j < n; j = j + 1 {
let r = spans[i].relate(spans[j])
cells[i * n + j] = RelSet::single(r)
cells[j * n + i] = RelSet::single(r.converse())
sources.push({
left: names[i],
right: names[j],
allowed: RelSet::single(r),
label: "observation",
})
}
}
{ names: net.names.copy(), cells, sources, }
}
///|
pub(all) struct ObservationConflict {
left : String
right : String
expected : RelSet
observed : Relation
}
///|
/// Check only supplied observations without asserting anything about missing spans.
pub fn check_observations(
net : Network,
names : Array[String],
spans : Array[Interval],
) -> Array[ObservationConflict] raise AllenError {
let observed = observe(names, spans)
let indexes = names.map(name => net.index(name))
let out : Array[ObservationConflict] = []
for i = 0; i < names.length(); i = i + 1 {
for j = i; j < names.length(); j = j + 1 {
let expected = net.cells[indexes[i] * net.size() + indexes[j]]
let actual = observed.cells[i * names.length() + j].items()[0]
if !expected.has(actual) {
out.push({
left: names[i],
right: names[j],
expected,
observed: actual,
})
}
}
}
out
}
///|
/// Add measured qualitative evidence while preserving all earlier source labels.
pub fn add_observations(
net : Network,
names : Array[String],
spans : Array[Interval],
) -> Network raise AllenError {
let observed = observe(names, spans)
// Validate even singleton observations, which have no pairwise source rows.
for name in names {
let _ = net.index(name)
}
let mut out = net
for constraint in observed.sources {
out = out.constrain(constraint)
}
out
}