1
0
Fork
You've already forked brainy
0
forked from spritely/brainy
A super research'y back-burner implementation of the propagator model. Ultra-pre-alpha! Still in the highly experimental stage! Not part of Spritely's core and we don't know if it will be!
Scheme 70.9%
Racket 29.1%
Find a file
2025年04月09日 16:18:04 +02:00
.guix/modules Build brainy with Guix 2025年04月09日 16:18:04 +02:00
historical Moved the Racket stuff to historical/ subdirectory 2022年02月21日 17:10:11 -05:00
src Switch to a src directory 2025年04月09日 16:05:18 +02:00
tests Add factorial test 2022年07月24日 14:06:12 -04:00
.gitignore gitignore 2022年02月21日 14:19:37 -05:00
.guix-channel Add a guix channel 2025年04月09日 16:18:04 +02:00
COPYING Add license 2022年02月21日 14:19:27 -05:00
guix.scm Build brainy with Guix 2025年04月09日 16:18:04 +02:00
LICENSE.txt Add license 2022年02月21日 14:19:27 -05:00
README.org Document Guix use 2025年04月09日 16:18:04 +02:00

This is the very, very, very research-project'y implementation of the propagator model on top of Spritely Goblins. Note that while hosted under the Spritely umbrella, this is not considered a core part of Spritely... we're still in the exploration stage to determine whether or not this will be useful for solving our needs. Brainy is a super pre-alpha back-burner research project!

Brainy takes a lot of syntax inspiration from the Revised Report on the Propagator Model, more so than from Radul's dissertation, but Radul's dissertation is where all the ideas and explanations are drawn from. It's an incredible read, get it printed out and put it next to your nightstand, consume at nighttime with chamomile tea.

What can propagators do?

This implementation is in its infancy. It can currently:

  • Solve simple algebraic equations in any direction
  • That's about it

But in the future, it should be able to:

ALL THIS while interoperating with Spritely Goblins, which means that with Spritely Goblins you can finally have a brain in a vat! Goblins makes the implementation of propagators pretty straightforward; it already has transactionality tools that means that Brainy can detect and protect itself from contradictory information corrupting things. (In the future when we have Truth Maintenance Systems, you'll have a whole additional layer for surviving contemplating the multiple contradictory worldviews that realistically we all inhabit!) Let's build a global network of cooperating brains!

In fact, being built on Goblins you can hook the brains together over CapTP, creating locally consistent but globally coordinating brains! This means you can finally put together that supervillain set of orbiting brain lasers / beowulf cluster of atomic supermen you've kept putting off for so long! Whew!

A note on propagators history

Propagator networks had been around for a long time before Radul wrote his dissertation, largely refined by students of Gerald Sussman, the first propagator network being implemented by Richard Stallman in 1975 with many students between that time making refinements. The book Building Problem Solvers collects many interesting kinds of propagator network designs. But Radul's dissertation unified many of these ideas by introducing the idea that partial information types in cells are the unifying abstraction. This is the approach which Brainy takes.

Getting this up and running

Build the package with

guix build -f guix.scm

Add the package to a profile with

guix shell -L .guix/modules brainy

Or add this repository as a channel!

tutorial

Baby's first propagator network

Uh, here's a bare minimum example of usage.

 (use-modules (brainy) (brainy prun))

Let's spawn a propagator cell. (prun stands for "propagator-run"; it takes care of a bunch of things for you so that you can start hacking with Brainy at the REPL fast):

GUILE> (define x (prun (spawn-pcell)))

What's its contents? Currently nothing!

GUILE> (prun (content x))
; => #<<nothing>>

Well we can add some information with add-content:

GUILE> (prun (add-content x 25))
GUILE> (prun (content x))
; => 25

Well okay, that works but it's kind of boring. We already knew it contained the information 25!

Let's make two more cells and hook up a propagator adder:

GUILE> (prun (owp:+ x y z))
; => #<local-object ^propagator>

Hm, well that did something... it returned a propagator object! But that's not particularly interesting.

What are the contents of our cells currently?

GUILE> (prun (content x))
; => 25
GUILE> (prun (content y))
; => #<<nothing>>
GUILE> (prun (content z))
; => #<<nothing>>

Okay, well let's add a value to y:

GUILE> (prun (add-content y 42))

Now not only does y contain a value... but the adder has propagated information to z as well:

> (prun (content y))
; => 42
> (prun (content z))
; => 67

So before we get any further, let's observe that a propagator network is a constraint based graph of information that converges on values (whew!) and contains two kinds of information:

  • cells: Which hold values (or as much information as they know about values, which can range from nothing to some partial information to settling on a final value). Cells notify the propagators they are attached to when they have new, more precise information. However they will refuse to adopt contradictory information (but can handle changes through partial information).
  • propagators: Machines which connect together cells and do not hold any state! Propagators re-run and publish results to each connected cell whenever they get in more information from attached cells.

In this way, new information added to the network "propagates" through the network until no more information is to be shared, at which case the network settles down.

Constraints solve in any direction!

Ho-hum. This doesn't seem particularly interesting. Okay, that's because it isn't! owp:+ only feeds information forward (the owp: stands for "one way propagation"). But there's another version called p:+ that does something more interesting... p:+ can solve an equation in any direction!

GUILE> (define x (prun (spawn-pcell 3)))
GUILE> (define y (prun (spawn-pcell)))
GUILE> (define z (prun (spawn-pcell 5)))
GUILE> (prun (p:+ x y z))
GUILE> (prun (content y))
; => 2

Oh hey look, the propagator figured out that the middle value should be 2!

If we now try to submit contradictory information:

GUILE> (prun (add-content y 56))
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
inconsistency "Inconsistency at unknown! Tried to add 55 but already had 2"
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
GUILE [1]>

Type ",q" to exit the debugger. So okay, it won't allow that kind of nonsense. Good!

What's surprising is that the implementation of p:+ is extremely simple. Here it is:

(define (p:+ x y z)
 (owp:+ x y z)
 (owp:- z x y)
 (owp:- z y x))

Another way to think about this is (with parenthetical circles being cells and bracket squares being propagators):

 _----_ _----_
 .' _=_ '.
 | .' '. |
 | .' (5) '. |
 | | |^| | |
 | [-]<-'|'->[-] |
 | ^ | ^ |
 V | | | V
 (3).' [+] '.(2)
 | ^ ^ |
 '.____.' '.____.'

(See Figure 2-3 from Radul's dissertation for a prettier version of this!)

Expression-style looks familiar

But this kinda looks too level, almost like assembly. It would be nice if we could run things expression-style. And we can! Let's use the e: style prefix expression-based propagator constructors, which automatically construct a "return" cell".

GUILE> (define should-be-twelve (prun (e:/ 24 2)))
GUILE> (prun (content should-be-twelve))
; => 12

Okay now THAT looks much more familiar!

Farenheit to celsius, two ways

Now that we've got all this set up, let's show off something more interesting... a celsius-to-farenheit converter.

The unidirectional version of this is straightforward enough:

(define (unidirectional-f2c f)
 (/ (* (- f 32) 5) 9))

But that version can only solve in one direction. We're going to set up one that can solve for any direction. For funsies, this includes the constant "32"... we'll show that if you supply everything else, it can solve for that too.

Here's what this looks like. Cells are ascii art "circles" like (_) whereas propagators are "squares" like [_]. (This is taken right from Alexey Radul's dissertation, page 28.)

 Fahrenheit Celsius
 f f-32 c*9 c
 (_)->[-]->(_)->[*]->(_)->[/]->(_)
 ^ ^ ^
 | | |
thirty-two (_) five (_) (_) nine
 ^ ^ ^
 | | |
 [32] [5] [9]

You're looking at the propagator network we're about to construct!

For funsies, we'll show that it can solve in any direction by allowing the user to specify the thirty-two part of the equation.

 (define* (fahrenheit-celsius #:key
 [fahrenheit nothing] [celsius nothing]
 [thirty-two 32])
 (define-pcell f fahrenheit)
 (define-pcell p32 thirty-two)
 (define f-32
 (e:- f p32))
 (define c*9
 (e:* f-32 5))
 (define c
 (e:/ c*9 9))
 (add-content c celsius)
 (values f c p32))

Now this gets interesting!

 ;; Solve for celsius!
 GUILE> (define-values (f c thirty-two)
 (prun (fahrenheit-celsius #:fahrenheit 113)))
 GUILE> (prun (content c))
 ; => 45
 ;; Solve for farehrenheit!
 GUILE> (define-values (f c thirty-two)
 (prun (fahrenheit-celsius #:celsius 45)))
 GUILE> (prun (content f))
 ; => 113
 ;; Solve for... thirty-two!
 GUILE> (define-values (f c thirty-two)
 (prun (fahrenheit-celsius #:fahrenheit 113
 #:celsius 45
 #:thirty-two nothing)))
 GUILE> (prun (content thirty-two))
 ; => 32

What if... you change thirty-two to be 64 but leave the other values the same? Try it!

And yes, we could also make the above farenheit-celsius into a simple expression propagator constructor:

 (define (e:f2c f)
 (e:/ (e:* (e:- f 32) 5) 9))

This looks JUST LIKE the original simple version... and it does solve forward...

 GUILE> (define c (prun (e:f2c 113)))
 GUILE> (prun (content c))
 ; => 45

But this version can still solve in the other direction!

 GUILE> (define f (prun (spawn-pcell)))
 GUILE> (define c (prun (e:f2c f)))
 GUILE> (prun (add-content c 45))
 GUILE> (prun (content f))
 ; => 113

Not bad!

What's next

A lot more is coming. Sorry to be so tantalyzing, you found this repo early!