OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / examples / atomspace / episodic-space.scm

258 lines · scm ref:
;
; episodic-space.scm -- Demo of storing AtomSpaces within Atoms
;
; There are many ways to represent episodic memories; one particularly
; interesting one is to store them into AtomSpaces that are then attached
; as a Value on an Atom. This offers several benefits over other
; representations: the entire collection can be treated as a coherent
; whole, and thus added or removed at will. Atoms located in such spaces
; are not in the mainspace, and thus do not pollute the mainspace,
; avoiding cross-talk and naming collisions. Searches can be limited to
; the episodic subspace. Different episodic subspaces can be joined
; together, using the frame concept. Thus, storing AtomSpaces as values
; provides multiple advantages over using one giant AtomSpace for
; everything. This demo example shows how this can be done.
;
; ---------------------------------------------------------------------
(use-modules (opencog))

; Get a reference to the current AtomSpace; this is our main space.
(define base-space (cog-atomspace))

; Add some Atom. Anything will do.
(ConceptNode "foo")

; Brief review of how Values work. Values can be vectors of numbers,
; strings, or even of other Atoms. In just a moment, we'll also be
; attaching AtomSpaces, as well.
(cog-set-value!
	(ConceptNode "foo") (Predicate "bunch o numbers")
		(FloatValue 1 2 3.14159 4 5 6))

(cog-set-value!
	(ConceptNode "foo") (Predicate "some words")
		(StringValue "once" "upon" "a" "time"))

(cog-set-value!
	(ConceptNode "foo") (Predicate "some atoms")
		(LinkValue (Concept "dog") (Concept "cat") (Concept "mouse")))

; Verify that the above worked. `cog-keys` returns all the keys
; attached to the Atom, and `cog-value` returns the value for a
; specific key.
(cog-keys (ConceptNode "foo"))
(cog-value (ConceptNode "foo") (Predicate "some words"))

; Print a summary report of what kinds of Atoms ar in the AtomSpace.
(cog-report-counts)

; And now, the main event: create some AtomSpaces, and store them as
; values, too. AtomSpaces will be identified by name. Thus, they behave
; much like Nodes. Some advice about naming and name collisions will be
; given later. So, just like above, put some AtomSpaces in the key-value
; store:
(cog-set-value!
	(ConceptNode "foo") (Predicate "real life")
		(AtomSpace "happy thoughts"))

(cog-set-value!
	(ConceptNode "foo") (Predicate "repressed mem")
		(AtomSpace "crushing defeat"))

; As before, verify that the AtomSpaces can be found at the given keys.
(cog-value (ConceptNode "foo") (Predicate "real life"))
(cog-value (ConceptNode "foo") (Predicate "repressed mem"))

; To continue with the demo, populate these AtomSpaces with some data.
; Since the guile bindings always work with a current atomspace (per
; thread), we have to switch to that to use it.
(define as-one (cog-value (ConceptNode "foo") (Predicate "real life")))
(cog-set-atomspace! as-one)

; Add some content.
(ListLink (Concept "mom") (Concept "dad"))
(ListLink (Concept "first crush") (Concept "Gilanda"))

; Review the content.
(cog-prt-atomspace)

; Switch to the other AtomSpace.
(cog-set-atomspace! base-space)
(cog-set-atomspace!
	(cog-value (ConceptNode "foo") (Predicate "repressed mem")))

; Add some content, and review it.
(ListLink (Concept "misdemeanor") (Concept "vandalism"))
(ListLink (Concept "furious") (Concept "anger"))
(cog-prt-atomspace)

; Return to the base AtomSpace, and make sure it does NOT contain
; anything that was added to the other two. The look at the other two,
; to make sure they haven't changed, and still contain what they did
; before.
(cog-set-atomspace! base-space)
(cog-prt-atomspace)
(cog-prt-atomspace (cog-value (ConceptNode "foo") (Predicate "real life")))
(cog-prt-atomspace (cog-value (ConceptNode "foo") (Predicate "repressed mem")))

; Some commentary about AtomSpace management (this is important!) The
; `AtomSpace` function is defined as
;      (define-public (AtomSpace x)
;          (cog-add-atomspace (cog-new-atomspace x)))
; This it creates a new AtomSpace *and* inserts it into the current
; AtomSpace. The name is used to identify it, and so it behave a lot
; like a Node. For example, saying `(AtomSpace "bar")` twice in a row
; will return the *same* AtomSpace. Thus (just like a Node) it is enough
; to know it's name.
;
; By contrast, `(cog-new-atomspace x)` create a new AtomSpace every time
; it is called, so calling `(cog-new-atomspace "blah")` twice in a row
; will give you two *different* AtomSpaces, having the same name. Both
; of them will be floating in the void, and so if you drop references to
; them, they will be garbage-collected. To get one unique copy, you must
; use `cog-add-atomspace`, with will return the oldest AtomSpace having
; the given name.
;
; ------------------------------------------------------
; All of the above is very interesting, but useless if we can't save
; and restore contents. So the rest of this demo examines how to do that.

; As usual, load the modules that provide storage.
(use-modules (opencog persist))
(use-modules (opencog persist-file))

; We'll store to a file in the temp directory.
; Open it, save, and close
(define fsn (FileStorageNode "/tmp/foo"))
(cog-set-value! fsn (*-open-*))
(cog-set-value! fsn (*-store-atomspace-*) (cog-atomspace))
(cog-set-value! fsn (*-close-*))

; Now, edit `/tmp/foo` with your favorite file editor. Notice that
; it contains the contents of the main space, but NOT of the episodic
; spaces. These need to be saved explicitly, and in separate files.
; Why? The whole point of episodic memory is that you don't have to
; deal with one giant ball with everything in it; instead, you can work
; with only those chunks that you want and need, when you want them,
; and as you need them.

; So, let's save the other two parts.
(define as-one (cog-value (ConceptNode "foo") (Predicate "real life")))
(define as-two (cog-value (ConceptNode "foo") (Predicate "repressed mem")))
(define fsa (FileStorageNode "/tmp/foo-one"))
(define fsb (FileStorageNode "/tmp/foo-two"))
(cog-set-value! fsa (*-open-*))
(cog-set-value! fsb (*-open-*))
(cog-set-value! fsa (*-store-atomspace-*) as-one)
(cog-set-value! fsa (*-close-*))
(cog-set-value! fsb (*-store-atomspace-*) as-two)
(cog-set-value! fsb (*-close-*))

; Return to home base.
(cog-set-atomspace! base-space)

; View `/tmp/foo-one` and `/tmp/foo-two` and verify that they contain
; what is expected.

; ------------------------------------------------------
; For the next part of the demo, you should exit the guile shell,
; and restart it from scratch. The goal here is to restore the contents
; from the saved files.
;
; The code below is nearly identical to the store code above, except
; that it loads, instead of storing. Otherwise, its just a cut-n-paste.

(use-modules (opencog) (opencog persist))
(use-modules (opencog persist-file))

; Save a reference to our main working space.
(define base-space (cog-atomspace))

(define fsn (FileStorageNode "/tmp/foo"))

(cog-set-value! fsn (*-open-*))
(cog-set-value! fsn (*-load-atomspace-*) (cog-atomspace))
(cog-set-value! fsn (*-close-*))

; Verify that the contents are as expected.
(cog-prt-atomspace)
(cog-keys (Concept "foo"))
(cog-value (Concept "foo") (Predicate "real life"))

; Now, restore the two batches of episodic memories.
(define as-one (cog-value (ConceptNode "foo") (Predicate "real life")))
(define as-two (cog-value (ConceptNode "foo") (Predicate "repressed mem")))
(define fsa (FileStorageNode "/tmp/foo-one"))
(define fsb (FileStorageNode "/tmp/foo-two"))
(cog-set-value! fsa (*-open-*))
(cog-set-value! fsb (*-open-*))
(cog-set-value! fsa (*-load-atomspace-*) as-one)
(cog-set-value! fsa (*-close-*))
(cog-set-value! fsb (*-load-atomspace-*) as-two)
(cog-set-value! fsb (*-close-*))

; Return to home base.
(cog-set-atomspace! base-space)

; Verify that the contents are as expected
(cog-prt-atomspace)
(cog-prt-atomspace as-one)
(cog-prt-atomspace as-two)

; ------------------------------------------------------
; Much as above, except that RocksDB is used, instead of a flat file.
(use-modules (opencog persist))
(use-modules (opencog persist-rocks))

(define rsn (RocksStorageNode "rocks:///tmp/blob"))

(cog-set-value! rsn (*-open-*))
(cog-set-value! rsn (*-store-atomspace-*) (cog-atomspace))
; The *-store-atomspace-* message will accept an AtomSpace as an
; argument, in which case it will be stored, instead of the current
; AtomSpace. So, for example:
(cog-set-value! rsn (*-store-atomspace-*) (AtomSpace "happy thoughts"))

; This also works:
(cog-set-value! rsn (*-store-atomspace-*) (cog-value (ConceptNode "foo") (Predicate "repressed mem")))
(cog-set-value! rsn (*-close-*))

; ------------------------------------------------------
; As above, exit guile, and then restart from scratch. The below will
; restore everything that was saved.

(use-modules (opencog) (opencog persist))
(use-modules (opencog persist-rocks))

(define base-space (cog-atomspace))
(define rsn (RocksStorageNode "rocks:///tmp/blob"))

(cog-set-value! rsn (*-open-*))
(cog-set-value! rsn (*-load-atomspace-*) (cog-atomspace))
(cog-set-value! rsn (*-close-*))
(cog-prt-atomspace)

; Don't need to actually call `(cog-set-value! rsn (*-close-*))` above,
; but it does show that it's harmless to close & reopen the database.

; Some short-hand, convenient defines.
(define as-one (cog-value (ConceptNode "foo") (Predicate "real life")))
(define as-two (cog-value (ConceptNode "foo") (Predicate "repressed mem")))

(cog-set-value! rsn (*-open-*))
; Here's one way to restore:
(cog-set-value! rsn (*-load-atomspace-*) as-one)

; Here's another way. It results in the same thing.
(cog-set-atomspace! as-two)
(cog-set-value! rsn (*-load-atomspace-*) (cog-atomspace))
(cog-set-atomspace! base-space)
(cog-set-value! rsn (*-close-*))

; Verify that everything looks as expected.
(cog-prt-atomspace as-one)
(cog-prt-atomspace as-two)
(cog-prt-atomspace)

; ---- THE END. That's all, folks! ----