TriggerLinks

From OpenCog
Jump to: navigation, search

(design proposal)

From two different directions, I have recently seen the need for fine-grained control of events in the Atomspace, of the form: When Atom A is updated, then some other Atom B has some specific thing done to it (e.g. B is fed to the pattern matcher, or B has its truth value updated, etc.).

It seems to me that this kind of control can be achieved via use of a new sort of link called a TriggerLink. Here I will discuss two possible kinds of TriggerLinks, BindTriggerLinks and ImplicationTriggerLinks.

Currently this is a speculative design proposal, being circulated for comments.

StrengthTriggerLink

As a simple example, suppose we have a BindLink such as


BindLink [123]
    $X
    AndLink
        EvaluationLink 
            PredicateNode “starving”
            ConceptNode “me”
        EvaluationLink 
	    PredicateNode “near” 
	    ConceptNode “me”
            ConceptNode $X
        InheritanceLink 
             $X
             ConceptNode “food”
     ExecutionOutputLink
         PredicateNode “eat”
         ConceptNode $X


and we would like this to be fed to the PatternMatcher whenever the ANDLink is true.

What process will cause the PM to actually be fed this BindLink?

In this case, the trigger should be the event of starvation, as tied to the “starving” PredicateNode. If it ever occurs that

        EvaluationLink [456]
            PredicateNode “starving”
            ConceptNode “me”

(i.e. if the strength of [456] increases past some threshold), then this is good enough reason for the PM to try to apply the BindLink.

To enable this we could create a link such as

StrengthTriggerLink
     [456]
     [123]
     NumberNode .8

The updateStrength() method of the Atom [456] would then have to check for StrengthTriggerLinks associated with the Atom (or, to save time, check a flag that indicates if the Atom has are any StrengthTriggerLinks or not). If it finds a StrengthTriggerLink, then it checks if the strength of the link is above the threshold indicated by the trigger link (.8 in the above example), and if so it carries out the appropriate action on the target of the trigger link. If the target of the trigger link is a BindLink, then the appropriate action is to spawn a new thread running a Pattern Matcher query on this BindLink.

Currently the method updateStrength() lives in a TruthValue object. So we would need some sort of TriggerHappyTruthValue object, that knew how to deal with trigger links upon approprate truth value updates.

RelationshipTriggerLink

As another example, suppose we want to keep a running count of, for instance, how many word-instances have been observed to occur as the subject of any given verb … e.g. the "positiveEvidence" of the truth value of

SatisfyingSetLink [666]
   $X
   EvaluationLink
      PredicateNode “subject” [333]
      PredicateNode “tickle” [222]
      VariableNode $X

We would like this count to be updated whenever a new instance of the relationship is seen, e.g. when

EvaluationLink
    PredicateNode “subject”
    PredicateNode “tickle”
    ConceptNode “Ben”

is observed…

Now, suppose the Atomspace contains the abstract inference rule

ForAllLink [777]
 $W,$X
 ImplicationLink
   EvaluationLink
       PredicateNode $W [888]
       PredicateNode $X
       AnyNode
   SatisfyingSetLink
       $Z
       EvaluationLink
         PredicateNode $W
         PredicateNode $X
         ConceptNode $Z

So for instance, when

EvaluationLink
    PredicateNode “subject”
    PredicateNode “tickle”
    ConceptNode “Ben”

is observed, the above rule will create

SatisfyingSetLink
    $Z
    EvaluationLink
       PredicateNode “subject”
       PredicateNode “tickle”
       ConceptNode $Z

What is needed is to trigger this rule whenever an EvaluationLink involving “subject” is formed….

One way to achieve this would be, writing

PredicateNode “subject” [444]   ,

to have a link such as

RelationshipTriggerLink
   [444]
   [777]
   [888]

where the latter should be interpreted that: Whenever a new link connected to [444] is created, it is checked whether [444] has any RelationshipTriggerLinks. If it does, then the targets of these are followed and appropriately processed.

If the second target is a BindLink, it gets processed by the PM in a new thread.

In this case the second target is an ImplicationLink [777], and the appropriate way to process it is to evaluate it as a BindLink, with [444] bound to position [888]

E.g. if the new link

EvaluationLink
    PredicateNode “subject”
    PredicateNode “tickle”
    ConceptNode “Ben”

is formed, then the BindLink causes the link

SatisfyingSetLink
    $Y
    EvaluationLink
       PredicateNode “subject”
       PredicateNode “tickle”
       ConceptNode $Y

to be formed with positiveEvidence of 1. But if this link already exists, then the formation of this link will trigger the revision rule, which will cause the existing positiveEvidence of the link to be incremented by 1. So we get automated update via the activity of the pattern matcher, carried out not via polling but triggered via addition of a new link to the incoming set of “subject”…

We have said above that the TriggerLink is followed “whenever a new link connected to [444] is created”…. How in practice does this happen? What we would need, it seems, is that when a link from A to B is created, some sort of “link_added” method associated with both A and B is invoked. If A doesn’t have any triggers associated with it, then A.link_added(Link L) won’t do anything and won’t take much time. If A has triggers associated with it, then these triggers will be activated when link_added is called.

..

Generalizing a Bit

In general, either StrengthTriggerLink or RelationshipTriggerLink should be able to point to either a BindLink or an ImplicationLink, and should be able to occur with either 2 or 3 arguments, the optional 3rd argument indicating which term in the source of the Bind or Implication Link should be bound to the 1st argument (where an error would be thrown if the 1st and 3rd arguments don’t unify).

Triggering schema execution

Next, suppose we want a TriggerLink to trigger execution of some Scheme, Python or Haskell code...

We can do this by wrapping this code in a GroundedSchemaNode, and then pointing a TriggerLink at this GSN, e.g.

RelationshipTriggerLink
      ConceptNode "cheese"
      ExecutionOutputLink
           GroundedSchemaNode "newCheeseProcessor.py"

PingTriggerLinks

Jim Rutt has suggested what he calls "PingTriggerLinks", as a "no side-effects" way to trigger procedures. Atoms could have a method called "Ping" that would trigger all PingTriggerLinks associated with that Atom. An Atom.ping API call would process the PingTriggerLinks associated with the Atom without requiring any changes to the Atom.

For instance,

ConceptNode "Red Button"

PingTriggerLink
   ConceptNode "Red Buttont"
   ExecutionOutputLink
       GroundedSchemaNode  "destroyworld.py"

This form provides for a clean way for "Red Button" to serve as the equivalent of a function variable when "programming in atomspace" - "Red Button" could be used semantically to mean "if the shit really hits the fan, do something". In geopolitics during the Cold War it's linked functionality would be "load and launch the missiles", TODAY it could be: "convene the G7 and wring handkerchiefs". This allows us to easily and cleanly build up indirection and thereby modularity.

As a tangible example, we could have an artificial deer having a "Red Button" throughout it's life, but with rather different meanings when it is a new born fawn, a yearling, and a big mean old buck. Other logic could evolve to "press the Red Button" (ie Ping it) when in extreme distress, without worrying about "what was behind the Red Button" thus achieving modularity in a way that is transparent in the Atomspace.

There might be others ways to achieve that, but a "no-side-effects' TriggerLink would be one useful and clear way to do it.

More complex examples would also be possible, e.g.

PingTriggerLink
   ConceptNode "Red Button"
   IfElseLink
         ExecutionOutputLink
               GroundedPredicateNode "Presidential_Authority_Received.py"
         SequentialAndLink                                                                 <----- TRUE branch
                   ExecutionOutputLink
                           GroundedSchemaNode "activate_ICBMs.py"
                   ExecutionOutputLink
                           GroundedSchemaNode "activate_warheads.py"
                   ExecutionOutputLink
                           GroundedSchemaNode "launch_ICBMs.py"
         SequentialAndLink                                                               <----- FALSE branch
                   ExecutionOutputLink
                           GroundedSchemaNode "notify_president_of_unauthorized_use.py"
                   ExecutionOutputLink
                           GroundedSchemaNode "press_release_generator.py"
  

On the other hand, it would be possible to achieve the same effect as a PingTriggerLink via different methods, e.g. by saying

  EquivalenceLink <1>
     SchemaNode "Red Button"
     GroundedSchemaNode  "destroyworld.py"


, along with a convention that invoking ExecutionOutputLink on an ungrounded SchemaNode should invoke a search for Equivalent GroundedSchemaNodes and execution of all of these with strength/confidence above a certain threshold, would do the trick...

But it's not totally clear this would be better than a PingTriggerLink... It seems a judgment call.

Scheduled Execution Links

A related idea is to provide an Atomspace mechanism for tasks that are to be run periodically or when some condition is true. Perhaps support for these tasks could be compressed to a queued job mechanism. Analogous to cron in unix. Some tasks could be "house keeping" tasks that run every hour or every day. Others could be tasks waiting for a condition to true to fully execute, that perhaps check the condition once every few seconds. We could have cron like functionality to specify run once, or repeating.

For instance, we could have something like

ScheduledExecutionLink 
    NumberNode    <--- run every NumberNode millisecs
    ConceptNode "Repeating"     <-- alternatively "RunOnce" 
    some stuff to execute
        might have a conditional check first

and Deleting the ScheduledExecutionLink would delete any pending jobs associated with it.