Skip to content

Commit

Permalink
Merge pull request #241 from TimKam/239-beliefs-as-functions
Browse files Browse the repository at this point in the history
Support functional beliefs
  • Loading branch information
TimKam authored Jun 27, 2024
2 parents 4960fb5 + a5efe67 commit 304366f
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 8 deletions.
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ const update = {
const agent = new Agent('myAgent', beliefBase, {}, [], undefined, false, revisePriority)
```
After applying the belief update, our agent's belief base is as follows:
After applying the belief update with `agent.next(update)`, our agent's belief base is as follows:
```JavaScript
{
Expand Down Expand Up @@ -647,6 +647,70 @@ const beliefBase = {
}
```
We may want to specify beliefs that are not simply updated as static objects/properties, but dynamically inferred, based on our current belief base or updates thereof.
To supports this, JS-son uses the notion of a *functional belief*.
A functional belief can be specified as follows, for example:
```JavaScript
const isSlippery = FunctionalBelief(
'isSlippery',
false,
(oldBeliefs, newBeliefs) =>
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value),
1
)
```
The arguments of `FunctionalBelief` have the following meaning:
* `isSlippery` (`id`) is the unique identifier of the (functional) belief,
* `false` (`value`) is the belief's default/initial value;
* The function:
```JavaScript
(oldBeliefs, newBeliefs) =>
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value)
```
specifies the rule according to which the belief is inferred -- in this simple example, the value of `isSlippery` takes the value of the new belief `isRaining` unless the belief does not exist, in which it will check for the existing (old) belief `isRaining` and return `false` if neither a new nor an old `isRaining` belief exists.
* `0` (`order`) is the number used for inducing the order in which the functional belief is revised relative to other functional beliefs: e.g., if another functional belief with order `1` is present, then the latter belief is revised later.
* `2` (`priority`) is the priority that the belief takes when updating the function as well as the default value, analogous to how orders work for non-functional beliefs.
Given this functional belief, we can now demonstrate how functional belief revision works:
1. First, we specify our agent:
```JavaScript
const newAgent = new Agent({
id: 'myAgent',
beliefs: { isRaining: Belief('isRaining', true, 0) },
desires,
plans
})
newAgent.next(newBeliefs1)
expect(newAgent.beliefs.isSlippery.value).toBe(true)
newAgent.next(newBeliefs2)
expect(newAgent.beliefs.isSlippery.value).toBe(false)
```
2. Then, we specify the initial belief base and execute the agent's reasoning loop with a belief base update that merely contains the functional belief:
```JavaScript
newAgent.next({ isSlippery})
```
Because ``isRaining`` is not present in the update, our agent infers ``isSlippery`` from its old belief base, i.e., the value of ``isSlippery`` remains ``true``.
3. Finally, we executed the reasoning loop again, with a slightly different belief base update:
```JavaScript
newAgent.next({
isSlippery,
isRaining: Belief('isRaining', false, 0)
})
```
Now, the value of ``isSlippery`` is updated to ``false``, as inferred from the update of ``isRaining``.
## Messaging
JS-son agents can send "private" messages to any other JS-son agent, which the environment will then relay to this agent only.
Agents can send these messages in the same way they register the execution of an action as the result of a plan.
Expand Down
65 changes: 65 additions & 0 deletions spec/src/agent/Agent.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Belief = require('../../../src/agent/Belief')
const FunctionalBelief = require('../../../src/agent/FunctionalBelief')
const Plan = require('../../../src/agent/Plan')
const Agent = require('../../../src/agent/Agent')

Expand Down Expand Up @@ -235,4 +236,68 @@ describe('Agent / next(), configuration object-based', () => {
})
expect(newAgent.next({ ...Belief('dogNice', false) })[0].action).toEqual('Good dog, Hasso!')
})

it('should correctly revise functional beliefs, given new beliefs', () => {
const oldBeliefs = {
isRaining: Belief('isRaining', true, 0),
}

const isSlippery = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 1)

const newBeliefs1 = {
isRaining: Belief('isRaining', false, 0),
isSlippery
}

const newBeliefs2 = {
isRaining: Belief('isRaining', true, 0),
isSlippery
}

const newAgent = new Agent({
id: 'myAgent',
beliefs: oldBeliefs,
desires,
plans
})
newAgent.next(newBeliefs1)
expect(newAgent.beliefs.isSlippery.value).toBe(false)
newAgent.next(newBeliefs2)
expect(newAgent.beliefs.isSlippery.value).toBe(true)
})

it('should correctly revise functional beliefs, given new and old beliefs', () => {
const oldBeliefs = {
isRaining: Belief('isRaining', true, 0),
}

const isSlippery = FunctionalBelief(
'isSlippery',
false,
(oldBeliefs, newBeliefs) =>
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value),
1
)

const newBeliefs1 = {
isSlippery
}

const newBeliefs2 = {
isSlippery,
isRaining: Belief('isRaining', false, 0)
}

const newAgent = new Agent({
id: 'myAgent',
beliefs: oldBeliefs,
desires,
plans
})
newAgent.next(newBeliefs1)
expect(newAgent.beliefs.isSlippery.value).toBe(true)
newAgent.next(newBeliefs2)
expect(newAgent.beliefs.isSlippery.value).toBe(false)
})
})
2 changes: 1 addition & 1 deletion spec/src/agent/Belief.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const Belief = require('../../../src/agent/Belief')

const warning = 'JS-son: Created belief with non-JSON object, non-JSON data type value'

describe('belief()', () => {
describe('Belief()', () => {
console.warn = jasmine.createSpy('warn')

it('should create a new belief with the specified key and value', () => {
Expand Down
46 changes: 46 additions & 0 deletions spec/src/agent/FunctionalBelief.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const FunctionalBelief = require('../../../src/agent/FunctionalBelief')

const beliefWarning = 'JS-son: Created belief with non-JSON object, non-JSON data type value'
const functionalBeliefWarning = 'JS-son: functional belief without proper two-argument function'

describe('FunctionalBelief()', () => {
console.warn = jasmine.createSpy('warn')

it('should create a new functional belief with the specified key, value, rule, and order', () => {
const functionBelief = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 0)
expect(functionBelief.isSlippery).toEqual(false)
expect(functionBelief.value).toEqual(false)
expect(functionBelief.rule.toString()).toEqual(((_, newBeliefs) => newBeliefs.isRaining.value).toString())
expect(functionBelief.order).toEqual(0)
})

it('should create a new functional belief with the specified key, value, rule, order, priority, and priority update spec', () => {
const functionBelief = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 0, 2)
expect(functionBelief.isSlippery).toEqual(false)
expect(functionBelief.value).toEqual(false)
expect(functionBelief.rule.toString()).toEqual(((_, newBeliefs) => newBeliefs.isRaining.value).toString())
expect(functionBelief.order).toEqual(0)
expect(functionBelief.priority).toEqual(2)
})

it('should throw warning if base belief is not JSON.stringify-able & not of a JSON data type', () => {
console.warn.calls.reset()
// eslint-disable-next-line no-unused-vars
const functionBelief = FunctionalBelief('function', () => {}, (_, newBeliefs) => newBeliefs.isRaining, 0, 2)
expect(console.warn).toHaveBeenCalledWith(beliefWarning)
})

it('should throw warning if rule is not a function', () => {
console.warn.calls.reset()
// eslint-disable-next-line no-unused-vars
const functionBelief = FunctionalBelief('isSlippery', false, true, 0, 2)
expect(console.warn).toHaveBeenCalledWith(functionalBeliefWarning)
})

it('should throw warning if rule is a function that does not take exactly two arguments', () => {
console.warn.calls.reset()
// eslint-disable-next-line no-unused-vars
const functionBelief = FunctionalBelief('isSlippery', false, newBeliefs => newBeliefs.isRaining, 0)
expect(console.warn).toHaveBeenCalledWith(functionalBeliefWarning)
})
})
25 changes: 24 additions & 1 deletion spec/src/agent/beliefRevision/revisionFunctions.spec.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const Agent = require('../../../../src/agent/Agent')
const Belief = require('../../../../src/agent/Belief')
const FunctionalBelief = require('../../../../src/agent/FunctionalBelief')
const {
reviseSimpleNonmonotonic,
reviseMonotonic,
revisePriority,
revisePriorityStatic } = require('../../../../src/agent/beliefRevision/revisionFunctions')
revisePriorityStatic,
getNonFunctionalBeliefs,
preprocessFunctionalBeliefs } = require('../../../../src/agent/beliefRevision/revisionFunctions')

const {
beliefs,
Expand Down Expand Up @@ -134,3 +137,23 @@ describe('revisionFunctions', () => {
expect(newAgent.beliefs.isRaining.priority).toBe(1)
})
})

describe('functional belief revision functions', () => {
const isRaining = Belief('isRaining', true, 1, false)
const isSlippery = FunctionalBelief(
'isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining, 0
)

it('(getNonFunctionalBeliefs) should filter out functional beliefs, given a belief base', () => {
const nonFunctionalBeliefs = getNonFunctionalBeliefs({isRaining, isSlippery})
expect(Object.keys(nonFunctionalBeliefs).length).toEqual(1)
expect(nonFunctionalBeliefs.isRaining.value).toEqual(true)
})

it('(preprocessFunctionalBeliefs) should filter out non-functional beliefs, given a belief base', () => {
const functionalBeliefs = preprocessFunctionalBeliefs({isRaining, isSlippery})
expect(functionalBeliefs.length).toEqual(1)
expect(functionalBeliefs[0].value).toEqual(false)
})

})
20 changes: 19 additions & 1 deletion src/agent/Agent.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const Intentions = require('./Intentions')
const defaultBeliefRevisionFunction = require('./beliefRevision/revisionFunctions').reviseSimpleNonmonotonic
const {
preprocessFunctionalBeliefs,
getNonFunctionalBeliefs,
getFunctionalBeliefs,
processFunctionalBeliefs
} = require('./beliefRevision/revisionFunctions')

const defaultPreferenceFunction = (beliefs, desires) => desireKey => desires[desireKey](beliefs)
const defaultGoalRevisionFunction = (beliefs, goals) => goals
Expand Down Expand Up @@ -82,7 +88,19 @@ function Agent (
}
this.isActive = true
this.next = function (beliefs) {
this.beliefs = this.reviseBeliefs(this.beliefs, beliefs)
// revision of non-functional beliefs
const oldBeliefs = getNonFunctionalBeliefs(this.beliefs)
this.beliefs = this.reviseBeliefs(this.beliefs, getNonFunctionalBeliefs(beliefs))
// revision of functional beliefs
const newFunctionalBeliefs = processFunctionalBeliefs(
getFunctionalBeliefs(this.beliefs),
getFunctionalBeliefs(beliefs),
oldBeliefs,
beliefs,
reviseBeliefs
)
this.beliefs = { ...this.beliefs, ...newFunctionalBeliefs }
// end: revision of functional beliefs
this.goals = this.reviseGoals(this.beliefs, this.goals)
if (this.isActive) {
if (Object.keys(this.desires).length === 0) {
Expand Down
21 changes: 21 additions & 0 deletions src/agent/FunctionalBelief.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const Belief = require('./Belief')

const warning = 'JS-son: functional belief without proper two-argument function'

/**
* JS-son agent belief generator
* @param {string} id the belief's unique identifier
* @param {any} value the belief's default value
* @param {function} rule the function that is used to infer the belief, given the agent's current beliefs and the belief update
* @param {number} order the belief's order when belief functions are evaluated
* @param {number} priority the belief's priority in case of belief revision; optional
* @param {boolean} updatePriority whether in case of a belief update, the priority of the defeating belief should be adopted; optional, defaults to true
* @returns {object} JS-son agent functional belief
*/
const FunctionalBelief = (id, value, rule, order, priority, updatePriority=false) => {
if (typeof rule !== 'function' || rule.length !== 2) console.warn(warning)
const baseBelief = Belief(id, value, priority, updatePriority=false)
return { ...baseBelief, rule, order, value }
}

module.exports = FunctionalBelief
Loading

0 comments on commit 304366f

Please sign in to comment.