/**
* Basal AtomSpace - Minimal Implementation
*
* Full AtomSpace: ~65,000 lines C++
* This version: ~300 lines JavaScript
*
* What we keep:
* - Node (named, typed, unique)
* - Link (typed, ordered outgoing set, unique)
* - Values (mutable data on atoms)
* - AtomSpace (uniqueness index + incoming sets)
* - Basic queries
*
* What we drop:
* - 100+ specialized atom types (we use plain type strings)
* - Complex pattern matching (we use simple filters)
* - Scheme/Python bindings (JS only)
* - Thread safety machinery (single-threaded or use workers)
* - C++ smart pointer complexity (JS has GC)
*/
import { createHash } from 'crypto'
// ============================================================================
// ATOMS (~50 lines vs 3,543 in C++)
// ============================================================================
class Atom {
constructor(type) {
this.type = type
this.values = new Map() // key -> any value
this.incoming = new Set() // Links containing this atom
this._hash = null
}
// Attach a value to this atom
setValue(key, value) {
this.values.set(key, value)
}
getValue(key) {
return this.values.get(key)
}
get hash() {
if (!this._hash) this._hash = this._computeHash()
return this._hash
}
_computeHash() {
throw new Error('Subclass must implement')
}
}
class Node extends Atom {
constructor(type, name) {
super(type)
this.name = name
}
_computeHash() {
return createHash('sha256')
.update(`N:${this.type}:${this.name}`)
.digest('hex')
.slice(0, 16)
}
toString() {
return `(${this.type} "${this.name}")`
}
}
class Link extends Atom {
constructor(type, outgoing) {
super(type)
this.outgoing = outgoing // Array of Atoms
}
_computeHash() {
const outHashes = this.outgoing.map(a => a.hash).join(',')
return createHash('sha256')
.update(`L:${this.type}:[${outHashes}]`)
.digest('hex')
.slice(0, 16)
}
toString() {
const out = this.outgoing.map(a => a.toString()).join(' ')
return `(${this.type} ${out})`
}
}
// ============================================================================
// ATOMSPACE (~100 lines vs 2,923 in C++)
// ============================================================================
class AtomSpace {
constructor() {
this.atoms = new Map() // hash -> Atom (uniqueness)
this.byType = new Map() // type -> Set<Atom>
this.byName = new Map() // name -> Atom (for nodes)
}
/**
* Add an atom, returning existing if duplicate
* This is the key operation - ensures global uniqueness
*/
add(atom) {
const hash = atom.hash
// Already exists? Return existing
if (this.atoms.has(hash)) {
return this.atoms.get(hash)
}
// Store it
this.atoms.set(hash, atom)
// Index by type
if (!this.byType.has(atom.type)) {
this.byType.set(atom.type, new Set())
}
this.byType.get(atom.type).add(atom)
// Index nodes by name
if (atom instanceof Node) {
this.byName.set(`${atom.type}:${atom.name}`, atom)
}
// Update incoming sets for links
if (atom instanceof Link) {
for (const child of atom.outgoing) {
child.incoming.add(atom)
}
}
return atom
}
// Convenience: create and add a node
addNode(type, name) {
return this.add(new Node(type, name))
}
// Convenience: create and add a link
addLink(type, outgoing) {
return this.add(new Link(type, outgoing))
}
// Find a node by type and name
getNode(type, name) {
return this.byName.get(`${type}:${name}`)
}
// Get all atoms of a type
getByType(type) {
return this.byType.get(type) || new Set()
}
// Get incoming links of specific type
getIncoming(atom, linkType = null) {
if (!linkType) return [...atom.incoming]
return [...atom.incoming].filter(l => l.type === linkType)
}
// Simple query: find links matching a pattern
query(pattern) {
const { type, outgoing } = pattern
const candidates = this.getByType(type)
return [...candidates].filter(link => {
if (!(link instanceof Link)) return false
if (outgoing && outgoing.length !== link.outgoing.length) return false
for (let i = 0; i < (outgoing?.length || 0); i++) {
const p = outgoing[i]
const a = link.outgoing[i]
// null = wildcard
if (p === null) continue
// Atom reference = exact match
if (p instanceof Atom && p !== a) return false
// Object = match properties
if (typeof p === 'object') {
if (p.type && p.type !== a.type) return false
if (p.name && a instanceof Node && p.name !== a.name) return false
}
}
return true
})
}
// Stats
get size() {
return this.atoms.size
}
}
// ============================================================================
// VALUES (~20 lines vs 5,082 in C++)
// ============================================================================
// In C++, Values are complex classes with type hierarchies
// In JS, we just use plain objects/arrays and attach directly
// Common value keys (by convention)
const TRUTH_VALUE = Symbol('tv') // [strength, confidence]
const ATTENTION_VALUE = Symbol('av') // [sti, lti, vlti]
const COUNT_VALUE = Symbol('count') // observation count
// Helper to set truth value
function setTruthValue(atom, strength, confidence = 1.0) {
atom.setValue(TRUTH_VALUE, [strength, confidence])
}
function getTruthValue(atom) {
return atom.getValue(TRUTH_VALUE) || [0, 0]
}
// ============================================================================
// EXAMPLE USAGE
// ============================================================================
function demo() {
const as = new AtomSpace()
// Create some concepts
const dog = as.addNode('ConceptNode', 'dog')
const animal = as.addNode('ConceptNode', 'animal')
const mammal = as.addNode('ConceptNode', 'mammal')
const cat = as.addNode('ConceptNode', 'cat')
// Create relationships
const dogIsAnimal = as.addLink('InheritanceLink', [dog, animal])
const catIsAnimal = as.addLink('InheritanceLink', [cat, animal])
const dogIsMammal = as.addLink('InheritanceLink', [dog, mammal])
const mammalIsAnimal = as.addLink('InheritanceLink', [mammal, animal])
// Attach truth values
setTruthValue(dogIsAnimal, 1.0, 0.99)
setTruthValue(catIsAnimal, 1.0, 0.99)
// Query: what inherits from animal?
const results = as.query({
type: 'InheritanceLink',
outgoing: [null, animal] // [?, animal]
})
console.log('Things that are animals:')
for (const link of results) {
console.log(' ', link.outgoing[0].name)
}
// Navigate incoming set: what is dog?
console.log('\nWhat is dog?')
for (const link of as.getIncoming(dog, 'InheritanceLink')) {
console.log(' dog is-a', link.outgoing[1].name)
}
console.log('\nAtomSpace size:', as.size, 'atoms')
}
export { Atom, Node, Link, AtomSpace, TRUTH_VALUE, setTruthValue, getTruthValue }
// Run demo if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
demo()
}