///|
pub(all) enum CorpusOrigin {
Seeded(Int64)
Generated(Int64)
Mutated(Int, String)
Crossed(Int, Int)
Reduced(Int)
Replayed
} derive(Debug, Eq)
///|
pub(all) struct CorpusEntry {
id : Int
tree : DerivationTree
observation : TargetObservation
signature : FeedbackSignature
failure : FailureFingerprint?
origin : CorpusOrigin
input_key : String
mut executions : Int
mut selections : Int
mut discoveries : Int
mut energy : Int
} derive(Debug, Eq)
///|
pub fn CorpusEntry::tree(self : CorpusEntry) -> DerivationTree {
self.tree
}
///|
pub fn CorpusEntry::observation(self : CorpusEntry) -> TargetObservation {
self.observation
}
///|
pub fn CorpusEntry::is_failure(self : CorpusEntry) -> Bool {
self.failure is Some(_)
}
///|
pub fn CorpusEntry::record_selection(self : CorpusEntry) -> Unit {
self.selections += 1
}
///|
pub fn CorpusEntry::record_execution(self : CorpusEntry) -> Unit {
self.executions += 1
}
///|
pub fn CorpusEntry::record_discovery(self : CorpusEntry) -> Unit {
self.discoveries += 1
}
///|
pub fn CorpusEntry::set_energy(self : CorpusEntry, value : Int) -> Unit {
self.energy = value.max(1)
}
///|
pub(all) struct CorpusLimits {
max_entries : Int
max_total_bytes : Int
max_failures : Int
max_input_bytes : Int
} derive(Debug, Eq)
///|
pub fn CorpusLimits::default() -> CorpusLimits {
{
max_entries: 4096,
max_total_bytes: 64 * 1024 * 1024,
max_failures: 1024,
max_input_bytes: 1024 * 1024,
}
}
///|
pub(all) enum AdmissionReason {
FirstInput
NovelFeature(Int)
NovelSignature
NewFailure(FailureFingerprint)
} derive(Debug, Eq)
///|
pub(all) enum RejectionReason {
DuplicateInput
NoNovelty
EntryCapacity
ByteCapacity
FailureCapacity
InputTooLarge(Int)
} derive(Debug, Eq)
///|
pub(all) enum AdmissionDecision {
Admitted(Int, AdmissionReason)
Rejected(RejectionReason)
} derive(Debug, Eq)
///|
pub(all) struct CorpusStatistics {
entries : Int
failures : Int
total_bytes : Int
unique_inputs : Int
unique_features : Int
unique_signatures : Int
unique_failures : Int
} derive(Debug, Eq)
///|
pub struct Corpus {
entries : Array[CorpusEntry]
limits : CorpusLimits
registry : FeatureRegistry
input_ids : @hashmap.HashMap[String, Int]
failure_ids : @hashmap.HashMap[FailureFingerprint, Int]
mut total_bytes : Int
mut failure_count : Int
}
///|
pub fn Corpus::new(limits? : CorpusLimits = CorpusLimits::default()) -> Corpus {
{
entries: [],
limits,
registry: FeatureRegistry::new(),
input_ids: @hashmap.HashMap([]),
failure_ids: @hashmap.HashMap([]),
total_bytes: 0,
failure_count: 0,
}
}
///|
fn hex_digit(value : Int) -> Char {
if value < 10 {
('0'.to_int() + value).unsafe_to_char()
} else {
('a'.to_int() + value - 10).unsafe_to_char()
}
}
///|
fn bytes_key(input : Bytes) -> String {
let builder = StringBuilder(size_hint=input.length() * 2 + 16)
builder.write_string(input.length().to_string())
builder.write_char(':')
for byte in input {
let value = byte.to_int()
builder.write_char(hex_digit(value / 16))
builder.write_char(hex_digit(value % 16))
}
builder.to_string()
}
///|
pub fn Corpus::length(self : Corpus) -> Int {
self.entries.length()
}
///|
pub fn Corpus::is_empty(self : Corpus) -> Bool {
self.entries.is_empty()
}
///|
pub fn Corpus::entry(self : Corpus, id : Int) -> CorpusEntry? {
if id < 0 || id >= self.entries.length() {
None
} else {
Some(self.entries[id])
}
}
///|
pub fn Corpus::entries(self : Corpus) -> Array[CorpusEntry] {
self.entries.copy()
}
///|
pub fn Corpus::find_input(self : Corpus, input : Bytes) -> CorpusEntry? {
match self.input_ids.get(bytes_key(input)) {
None => None
Some(id) => self.entry(id)
}
}
///|
pub fn Corpus::failure_entry(
self : Corpus,
fingerprint : FailureFingerprint,
) -> CorpusEntry? {
match self.failure_ids.get(fingerprint) {
None => None
Some(id) => self.entry(id)
}
}
///|
fn Corpus::capacity_rejection(
self : Corpus,
input_bytes : Int,
is_failure : Bool,
) -> RejectionReason? {
if input_bytes > self.limits.max_input_bytes {
Some(InputTooLarge(input_bytes))
} else if self.entries.length() >= self.limits.max_entries {
Some(EntryCapacity)
} else if self.total_bytes > self.limits.max_total_bytes - input_bytes {
Some(ByteCapacity)
} else if is_failure && self.failure_count >= self.limits.max_failures {
Some(FailureCapacity)
} else {
None
}
}
///|
pub fn Corpus::consider(
self : Corpus,
tree : DerivationTree,
observation : TargetObservation,
origin : CorpusOrigin,
) -> AdmissionDecision {
let input = tree.render()
let key = bytes_key(input)
if self.input_ids.contains(key) {
return Rejected(DuplicateInput)
}
let fingerprint = observation.failure_fingerprint()
match self.capacity_rejection(input.length(), fingerprint is Some(_)) {
Some(reason) => return Rejected(reason)
None => ()
}
let already_had_signature = self.registry.signature_frequency(
observation.signature(),
) >
0
let feature_delta = self.registry.observe(observation)
let failure_is_novel = match fingerprint {
None => false
Some(value) => !self.failure_ids.contains(value)
}
let reason = if self.entries.is_empty() {
FirstInput
} else if failure_is_novel {
NewFailure(fingerprint.unwrap())
} else if !feature_delta.novel_features.is_empty() {
NovelFeature(feature_delta.novel_features.length())
} else if !already_had_signature {
NovelSignature
} else {
return Rejected(NoNovelty)
}
let id = self.entries.length()
let entry : CorpusEntry = {
id,
tree,
observation,
signature: feature_delta.signature,
failure: fingerprint,
origin,
input_key: key,
executions: 0,
selections: 0,
discoveries: 0,
energy: 1,
}
self.entries.push(entry)
self.input_ids.set(key, id)
self.total_bytes += input.length()
match fingerprint {
None => ()
Some(value) => {
self.failure_count += 1
if failure_is_novel {
self.failure_ids.set(value, id)
}
}
}
Admitted(id, reason)
}
///|
pub fn Corpus::statistics(self : Corpus) -> CorpusStatistics {
{
entries: self.entries.length(),
failures: self.failure_count,
total_bytes: self.total_bytes,
unique_inputs: self.input_ids.length(),
unique_features: self.registry.unique_feature_count(),
unique_signatures: self.registry.unique_signature_count(),
unique_failures: self.failure_ids.length(),
}
}
///|
pub fn Corpus::feature_frequency(self : Corpus, value : Feature) -> Int {
self.registry.feature_frequency(value)
}
///|
pub fn Corpus::signature_frequency(
self : Corpus,
value : FeedbackSignature,
) -> Int {
self.registry.signature_frequency(value)
}
///|
pub fn Corpus::smallest_failure(
self : Corpus,
fingerprint : FailureFingerprint,
) -> CorpusEntry? {
let mut result : CorpusEntry? = None
for entry in self.entries {
if entry.failure == Some(fingerprint) {
match result {
None => result = Some(entry)
Some(previous) =>
if entry.tree.byte_length() < previous.tree.byte_length() {
result = Some(entry)
}
}
}
}
result
}