Skip to content

Commit

Permalink
Merge pull request #237 from TimKam/236-advanced-belief-revision
Browse files Browse the repository at this point in the history
Add out-of-the-box belief revision functions
  • Loading branch information
TimKam authored May 5, 2024
2 parents b312844 + 171cd1f commit 20bb0c7
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
node-version: [12.x, 14.x, 15.x, 20.x]

steps:
- uses: actions/checkout@v3
Expand Down
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ However, JS-son supports the implementation of a custom belief revision function
For example, let us implement the following simple agent:
```JavaScript
let agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [])
const agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [])
```
Now, let us run the agent so that the environment changes the agent's belief about ``a``.
Expand All @@ -581,7 +581,7 @@ const (oldBeliefs, newBeliefs) => ({
...newBeliefs,
a: true
})
let agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [], undefined, false, reviseBeliefs)
const agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [], undefined, false, reviseBeliefs)
```
To test the change, proceed as follows:
Expand All @@ -592,6 +592,48 @@ agent.next({ ...Belief('a', false) })
``agent.beliefs.a`` is ``true``.
JS-son provides an out-of-the-box belief revision function that handles priority rules.
We can import this function as follows:
```JavaScript
const revisePriority = JSson.revisionFunctions.revisePriority
```
Let us now specify an initial belief base and an update thereof.
In both, each belief has a numerical priority value:
```JavaScript
const beliefBase = {
isRaining: Belief('isRaining', true, 0),
temperature: Belief('temperature', 10, 0),
propertyValue: Belief('propertyValue', 500000, 1)
}

const update = {
isRaining: Belief('isRaining', false, 0),
temperature: Belief('temperature', 15, 1),
propertyValue: Belief('propertyValue', 250000, 0)
}

const agent = new Agent('myAgent', beliefBase, {}, [], undefined, false, revisePriority)
```
After applying the belief update, our agent's belief base is as follows:
```JavaScript
{
isRaining: Belief('isRaining', false, 0),
temperature: Belief('temperature', 15, 1),
propertyValue: Belief('propertyValue', 500000, 1)
}
```
Note that in detail, the priorities are interpreted as follows:
* If a belief exists in the update, but not in the agent's belief base, this belief is added.
* If the belief's priority is 0 in the belief base and a belief with the same key exists in the update, the agent's belief is overridden; this behavior is desired for beliefs that are generally defeasible.
* If a belief's priority in the update is higher than the same belief's priority in the agent's belief base, the agent's belief is overridden.
## 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
12 changes: 12 additions & 0 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
```

## Built-In Belief Revision Functions

```eval_rst
.. js:autofunction:: reviseSimpleNonmonotonic
.. js:autofunction:: reviseMonotonic
.. js:autofunction:: revisePriority
```

## Environment

```eval_rst
Expand Down
4 changes: 2 additions & 2 deletions spec/src/agent/Agent.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Agent / next()', () => {
}
))
const newAgent = new Agent('myAgent', beliefs, desires, newPlans, preferenceFunctionGen, false)
expect(() => newAgent.next()).toThrow(new TypeError("Cannot set property 'test' of undefined"))
expect(() => newAgent.next()).toThrow()
})

it('should allow for a custom belief revision function that rejects belief updates from the environment', () => {
Expand Down Expand Up @@ -167,7 +167,7 @@ describe('Agent / next(), configuration object-based', () => {
determinePreferences: preferenceFunctionGen,
selfUpdatesPossible: false
})
expect(() => newAgent.next()).toThrow(new TypeError("Cannot set property 'test' of undefined"))
expect(() => newAgent.next()).toThrow()
})

it('should support a custom belief revision function that rejects belief updates from the environment', () => {
Expand Down
4 changes: 4 additions & 0 deletions spec/src/agent/Belief.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ describe('belief()', () => {
expect(Belief('test', 'test')).toEqual({ test: 'test' })
})

it('should create a new belief with the specified key, value (explicitly managed), and priority', () => {
expect(Belief('test', 'test', 1)).toEqual({ test: 'test', value: 'test', priority: 1 })
})

it('should not throw a warning if belief is of a JSON data type', () => {
console.warn.calls.reset()
// eslint-disable-next-line no-unused-vars
Expand Down
101 changes: 101 additions & 0 deletions spec/src/agent/beliefRevision/revisionFunctions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const Agent = require('../../../../src/agent/Agent')
const Belief = require('../../../../src/agent/Belief')
const {
reviseSimpleNonmonotonic,
reviseMonotonic,
revisePriority } = require('../../../../src/agent/beliefRevision/revisionFunctions')

const {
beliefs,
desires,
plans
} = require('../../../mocks/human')

describe('revisionFunctions', () => {

it('by default, the belief revision function is simple non-monotonic and updates existing beliefs', () => {
const newAgent = new Agent({
id: 'myAgent',
beliefs,
desires,
plans,
selfUpdatesPossible: false
})
newAgent.next({ ...Belief('dogNice', false) })

const alternativeAgent = new Agent({
id: 'alternativeAgent',
beliefs,
desires,
plans,
selfUpdatesPossible: false,
reviseBeliefs: reviseSimpleNonmonotonic
})
alternativeAgent.next({ ...Belief('dogNice', false) })
expect(alternativeAgent.beliefs.dogNice).toBe(false)
expect(alternativeAgent.beliefs.dogNice).toEqual(newAgent.beliefs.dogNice)
})

it('should support a monotonic belief revision function that rejects updates to existing beliefs', () => {
const newAgent = new Agent({
id: 'myAgent',
beliefs,
desires,
plans,
selfUpdatesPossible: false,
reviseBeliefs: reviseMonotonic
})
newAgent.next({ ...Belief('dogNice', false), ...Belief('weather', 'sunny') })
expect(newAgent.beliefs.dogNice).toBe(true)
expect(newAgent.beliefs.weather).toBe('sunny')
})

it('should support a nonmonotonic belief revision function based on priority rules', () => {
const beliefBase = {
isRaining: Belief('isRaining', true, 0),
temperature: Belief('temperature', 10, 0),
propertyValue: Belief('propertyValue', 500000, 1)
}

const update = {
isRaining: Belief('isRaining', false, 0),
temperature: Belief('temperature', 15, 1),
propertyValue: Belief('propertyValue', 250000, 0)
}
const newAgent = new Agent({
id: 'myAgent',
beliefs: beliefBase,
desires,
plans,
selfUpdatesPossible: false,
reviseBeliefs: revisePriority
})
newAgent.next(update)
expect(newAgent.beliefs.isRaining.value).toBe(false)
expect(newAgent.beliefs.temperature.value).toEqual(15)
expect(newAgent.beliefs.propertyValue.value).toEqual(500000)
})

it('should not allow overriding beliefs of infinite high priority', () => {
const beliefBase = {
isRaining: Belief('isRaining', true, Infinity),
temperature: Belief('temperature', 10, Infinity)
}

const update = {
isRaining: Belief('isRaining', false, Infinity),
temperature: Belief('temperature', 15, undefined)
}
const newAgent = new Agent({
id: 'myAgent',
beliefs: beliefBase,
desires,
plans,
selfUpdatesPossible: false,
reviseBeliefs: revisePriority
})
newAgent.next(update)
expect(newAgent.beliefs.isRaining.value).toBe(true)
expect(newAgent.beliefs.temperature.value).toEqual(10)
})
})
2 changes: 1 addition & 1 deletion src/agent/Agent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const Intentions = require('./Intentions')
const defaultBeliefRevisionFunction = require('./beliefRevision/revisionFunctions').reviseSimpleNonmonotonic

const defaultPreferenceFunction = (beliefs, desires) => desireKey => desires[desireKey](beliefs)
const defaultBeliefRevisionFunction = (oldBeliefs, newBeliefs) => ({ ...oldBeliefs, ...newBeliefs })
const defaultGoalRevisionFunction = (beliefs, goals) => goals

/**
Expand Down
9 changes: 7 additions & 2 deletions src/agent/Belief.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ const warning = 'JS-son: Created belief with non-JSON object, non-JSON data type
/**
* JS-son agent belief generator
* @param {string} id the belief's unique identifier
* @param {any} value
* @param {any} value the belief's value
* @param {number} priority the belief's priority in case of belief revision; optional
* @returns {object} JS-son agent belief
*/
const Belief = (id, value) => {
const Belief = (id, value, priority) => {
const belief = {}
belief[id] = value
if (priority || priority === 0) {
belief.priority = priority
belief['value'] = value
}
try {
const parsedBelief = JSON.parse(JSON.stringify(belief))
if (Object.keys(parsedBelief).length !== Object.keys(belief).length) {
Expand Down
38 changes: 38 additions & 0 deletions src/agent/beliefRevision/revisionFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Revises beliefs by merging old and new beliefs such that a new belief overwrites an old one in
* case of conflict.
* @param {object} oldBeliefs Old belief base (JSON object of beliefs)
* @param {object} newBeliefs New belief base (JSON object of beliefs)
* @returns Revised belief base (JSON object of beliefs)
*/
const reviseSimpleNonmonotonic = (oldBeliefs, newBeliefs) => ({ ...oldBeliefs, ...newBeliefs })

/**
* Revises beliefs by merging old and new beliefs such that an old belief overrides a new one in
* case of conflict.
* @param {object} oldBeliefs Old belief base (JSON object of beliefs)
* @param {object} newBeliefs New belief base (JSON object of beliefs)
* @returns Revised belief base (JSON object of beliefs)
*/
const reviseMonotonic = (oldBeliefs, newBeliefs) => ({ ...newBeliefs, ...oldBeliefs })

/**
* Revises beliefs by merging old and new beliefs such that an old belief overrides a new one in
* case of conflict
* @param {object} oldBeliefs Old belief base (JSON object of beliefs)
* @param {object} newBeliefs New belief base (JSON object of beliefs)
* @returns Revised belief base (JSON object of beliefs)
*/
const revisePriority = (oldBeliefs, newBeliefs) => Object.fromEntries(
new Map(
Object.keys(newBeliefs).map(key =>
!key in oldBeliefs || oldBeliefs[key].priority == 0 || oldBeliefs[key].priority < newBeliefs[key].priority ? [key, newBeliefs[key]] : [key, oldBeliefs[key]]
)
)
)

module.exports = {
reviseSimpleNonmonotonic,
reviseMonotonic,
revisePriority
}
3 changes: 2 additions & 1 deletion src/js-son.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const JSson = {
RemoteAgent: require('./agent/RemoteAgent'),
Environment: require('./environment/Environment'),
GridWorld: require('./environment/GridWorld'),
FieldType: require('./environment/FieldType')
FieldType: require('./environment/FieldType'),
revisionFunctions: require('./agent/beliefRevision/revisionFunctions')
}

module.exports = JSson

0 comments on commit 20bb0c7

Please sign in to comment.