OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-layer-discovery / experiments / ladder / port / diffstate.py

70 lines · python ref:
#!/usr/bin/env python3
# Differential check: our port's state dump vs a G0 (Linas) state dump, over the (atom, key) pairs the port
# claims to reproduce. Counts must match exactly; other values to a relative 1e-9.
#   diffstate.py <linas-state.tsv> <ours.tsv>      exit 0 = identical on the ported set
import math, re, sys

PORTED = {
    'word': {'*-TruthValueKey-*'},
    'pair': {'*-TruthValueKey-*', '*-FrequencyKey-*', '*-Mutual Info Key-*'},
    'marg': {'*-Norm Key-*', '*-FrequencyKey-*', '*-Entropy Key-*', '*-Mutual Info Key-*'},
    'total': {'*-Left Total Key-*', '*-Right Total Key-*', '*-Supp Dimension Key-*', '*-Total Entropy Key-*', '*-Total MI Key-*'},
    'section': {'*-TruthValueKey-*'},
    'bond': {'*-TruthValueKey-*'},
}
# --counts-only: compare only TruthValueKey counts, on words, ANY pairs, named-bond pairs and Sections.
COUNTS_ONLY = '--counts-only' in sys.argv
if COUNTS_ONLY: sys.argv.remove('--counts-only')
BOND = re.compile(r'^\(EdgeLink \(BondNode "([^"]*)"\) \(ListLink \(WordNode "[^"]*"\) \(WordNode "[^"]*"\)\)\)$')
EDGE = re.compile(r'^\(EdgeLink \(BondNode "ANY"\) \(ListLink \((WordNode|AnyNode) "[^"]*"\) \((WordNode|AnyNode) "[^"]*"\)\)\)$')

def kind(atom):
    if atom.startswith('(WordNode "'): return 'word'
    if atom.startswith('(Section (WordNode "'): return 'section'
    b = BOND.match(atom)
    if b and b.group(1) != 'ANY': return 'bond'
    m = EDGE.match(atom)
    if not m: return None
    a, b = m.group(1), m.group(2)
    return {('WordNode', 'WordNode'): 'pair', ('AnyNode', 'AnyNode'): 'total'}.get((a, b), 'marg')

def num(s):
    return {'+inf.0': math.inf, '-inf.0': -math.inf, 'inf': math.inf, '-inf': -math.inf}.get(s, None) if s in ('+inf.0', '-inf.0', 'inf', '-inf') else float(s)

def load(path):
    d = {}
    for line in open(path):
        parts = line.rstrip('\n').split('\t')
        if len(parts) != 3: continue
        atom, key, vals = parts
        k = kind(atom)
        m = re.match(r'^\(PredicateNode "(.*)"\)$', key)
        if not k or not m or m.group(1) not in PORTED[k]: continue
        if COUNTS_ONLY and m.group(1) != '*-TruthValueKey-*': continue
        if not COUNTS_ONLY and k in ('section', 'bond'): continue
        d[(atom, m.group(1))] = [num(x) for x in vals.split()]
    return d

def same(a, b, exact):
    if len(a) != len(b): return False
    for x, y in zip(a, b):
        if math.isinf(x) or math.isinf(y):
            if x != y: return False
        elif exact:
            if x != y: return False
        elif abs(x - y) > 1e-9 * max(abs(x), abs(y), 1e-300) and abs(x - y) > 1e-15:
            return False
    return True

L, O = load(sys.argv[1]), load(sys.argv[2])
only_l = sorted(set(L) - set(O)); only_o = sorted(set(O) - set(L))
bad = [k for k in sorted(set(L) & set(O)) if not same(L[k], O[k], k[1] == '*-TruthValueKey-*')]
by = {}
for k in set(L) & set(O): by[k[1]] = by.get(k[1], 0) + 1
print(f'linas {len(L)}  ours {len(O)}  both {len(set(L) & set(O))}  only-linas {len(only_l)}  only-ours {len(only_o)}  value-mismatch {len(bad)}')
for key, n in sorted(by.items()): print(f'  {key}: {n}')
for k in only_l[:5]: print('ONLY-LINAS', k)
for k in only_o[:5]: print('ONLY-OURS', k)
for k in bad[:8]: print('MISMATCH', k, L[k], O[k])
sys.exit(0 if not (only_l or only_o or bad) else 1)