Skip to content

Commit 304366f

Browse files
authored
Merge pull request #241 from TimKam/239-beliefs-as-functions
Support functional beliefs
2 parents 4960fb5 + a5efe67 commit 304366f

File tree

8 files changed

+313
-8
lines changed

8 files changed

+313
-8
lines changed

README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ const update = {
618618
const agent = new Agent('myAgent', beliefBase, {}, [], undefined, false, revisePriority)
619619
```
620620
621-
After applying the belief update, our agent's belief base is as follows:
621+
After applying the belief update with `agent.next(update)`, our agent's belief base is as follows:
622622
623623
```JavaScript
624624
{
@@ -647,6 +647,70 @@ const beliefBase = {
647647
}
648648
```
649649
650+
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.
651+
To supports this, JS-son uses the notion of a *functional belief*.
652+
A functional belief can be specified as follows, for example:
653+
654+
```JavaScript
655+
const isSlippery = FunctionalBelief(
656+
'isSlippery',
657+
false,
658+
(oldBeliefs, newBeliefs) =>
659+
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
660+
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value),
661+
1
662+
)
663+
```
664+
665+
The arguments of `FunctionalBelief` have the following meaning:
666+
667+
* `isSlippery` (`id`) is the unique identifier of the (functional) belief,
668+
* `false` (`value`) is the belief's default/initial value;
669+
* The function:
670+
```JavaScript
671+
(oldBeliefs, newBeliefs) =>
672+
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
673+
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value)
674+
```
675+
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.
676+
* `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.
677+
* `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.
678+
679+
Given this functional belief, we can now demonstrate how functional belief revision works:
680+
681+
1. First, we specify our agent:
682+
683+
```JavaScript
684+
const newAgent = new Agent({
685+
id: 'myAgent',
686+
beliefs: { isRaining: Belief('isRaining', true, 0) },
687+
desires,
688+
plans
689+
})
690+
newAgent.next(newBeliefs1)
691+
expect(newAgent.beliefs.isSlippery.value).toBe(true)
692+
newAgent.next(newBeliefs2)
693+
expect(newAgent.beliefs.isSlippery.value).toBe(false)
694+
```
695+
696+
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:
697+
698+
```JavaScript
699+
newAgent.next({ isSlippery})
700+
```
701+
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``.
702+
703+
3. Finally, we executed the reasoning loop again, with a slightly different belief base update:
704+
705+
```JavaScript
706+
newAgent.next({
707+
isSlippery,
708+
isRaining: Belief('isRaining', false, 0)
709+
})
710+
```
711+
712+
Now, the value of ``isSlippery`` is updated to ``false``, as inferred from the update of ``isRaining``.
713+
650714
## Messaging
651715
JS-son agents can send "private" messages to any other JS-son agent, which the environment will then relay to this agent only.
652716
Agents can send these messages in the same way they register the execution of an action as the result of a plan.

spec/src/agent/Agent.spec.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const Belief = require('../../../src/agent/Belief')
2+
const FunctionalBelief = require('../../../src/agent/FunctionalBelief')
23
const Plan = require('../../../src/agent/Plan')
34
const Agent = require('../../../src/agent/Agent')
45

@@ -235,4 +236,68 @@ describe('Agent / next(), configuration object-based', () => {
235236
})
236237
expect(newAgent.next({ ...Belief('dogNice', false) })[0].action).toEqual('Good dog, Hasso!')
237238
})
239+
240+
it('should correctly revise functional beliefs, given new beliefs', () => {
241+
const oldBeliefs = {
242+
isRaining: Belief('isRaining', true, 0),
243+
}
244+
245+
const isSlippery = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 1)
246+
247+
const newBeliefs1 = {
248+
isRaining: Belief('isRaining', false, 0),
249+
isSlippery
250+
}
251+
252+
const newBeliefs2 = {
253+
isRaining: Belief('isRaining', true, 0),
254+
isSlippery
255+
}
256+
257+
const newAgent = new Agent({
258+
id: 'myAgent',
259+
beliefs: oldBeliefs,
260+
desires,
261+
plans
262+
})
263+
newAgent.next(newBeliefs1)
264+
expect(newAgent.beliefs.isSlippery.value).toBe(false)
265+
newAgent.next(newBeliefs2)
266+
expect(newAgent.beliefs.isSlippery.value).toBe(true)
267+
})
268+
269+
it('should correctly revise functional beliefs, given new and old beliefs', () => {
270+
const oldBeliefs = {
271+
isRaining: Belief('isRaining', true, 0),
272+
}
273+
274+
const isSlippery = FunctionalBelief(
275+
'isSlippery',
276+
false,
277+
(oldBeliefs, newBeliefs) =>
278+
(newBeliefs.isRaining && newBeliefs.isRaining.value) ||
279+
(!newBeliefs.isRaining && oldBeliefs.isRaining && oldBeliefs.isRaining.value),
280+
1
281+
)
282+
283+
const newBeliefs1 = {
284+
isSlippery
285+
}
286+
287+
const newBeliefs2 = {
288+
isSlippery,
289+
isRaining: Belief('isRaining', false, 0)
290+
}
291+
292+
const newAgent = new Agent({
293+
id: 'myAgent',
294+
beliefs: oldBeliefs,
295+
desires,
296+
plans
297+
})
298+
newAgent.next(newBeliefs1)
299+
expect(newAgent.beliefs.isSlippery.value).toBe(true)
300+
newAgent.next(newBeliefs2)
301+
expect(newAgent.beliefs.isSlippery.value).toBe(false)
302+
})
238303
})

spec/src/agent/Belief.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const Belief = require('../../../src/agent/Belief')
22

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

5-
describe('belief()', () => {
5+
describe('Belief()', () => {
66
console.warn = jasmine.createSpy('warn')
77

88
it('should create a new belief with the specified key and value', () => {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const FunctionalBelief = require('../../../src/agent/FunctionalBelief')
2+
3+
const beliefWarning = 'JS-son: Created belief with non-JSON object, non-JSON data type value'
4+
const functionalBeliefWarning = 'JS-son: functional belief without proper two-argument function'
5+
6+
describe('FunctionalBelief()', () => {
7+
console.warn = jasmine.createSpy('warn')
8+
9+
it('should create a new functional belief with the specified key, value, rule, and order', () => {
10+
const functionBelief = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 0)
11+
expect(functionBelief.isSlippery).toEqual(false)
12+
expect(functionBelief.value).toEqual(false)
13+
expect(functionBelief.rule.toString()).toEqual(((_, newBeliefs) => newBeliefs.isRaining.value).toString())
14+
expect(functionBelief.order).toEqual(0)
15+
})
16+
17+
it('should create a new functional belief with the specified key, value, rule, order, priority, and priority update spec', () => {
18+
const functionBelief = FunctionalBelief('isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining.value, 0, 2)
19+
expect(functionBelief.isSlippery).toEqual(false)
20+
expect(functionBelief.value).toEqual(false)
21+
expect(functionBelief.rule.toString()).toEqual(((_, newBeliefs) => newBeliefs.isRaining.value).toString())
22+
expect(functionBelief.order).toEqual(0)
23+
expect(functionBelief.priority).toEqual(2)
24+
})
25+
26+
it('should throw warning if base belief is not JSON.stringify-able & not of a JSON data type', () => {
27+
console.warn.calls.reset()
28+
// eslint-disable-next-line no-unused-vars
29+
const functionBelief = FunctionalBelief('function', () => {}, (_, newBeliefs) => newBeliefs.isRaining, 0, 2)
30+
expect(console.warn).toHaveBeenCalledWith(beliefWarning)
31+
})
32+
33+
it('should throw warning if rule is not a function', () => {
34+
console.warn.calls.reset()
35+
// eslint-disable-next-line no-unused-vars
36+
const functionBelief = FunctionalBelief('isSlippery', false, true, 0, 2)
37+
expect(console.warn).toHaveBeenCalledWith(functionalBeliefWarning)
38+
})
39+
40+
it('should throw warning if rule is a function that does not take exactly two arguments', () => {
41+
console.warn.calls.reset()
42+
// eslint-disable-next-line no-unused-vars
43+
const functionBelief = FunctionalBelief('isSlippery', false, newBeliefs => newBeliefs.isRaining, 0)
44+
expect(console.warn).toHaveBeenCalledWith(functionalBeliefWarning)
45+
})
46+
})

spec/src/agent/beliefRevision/revisionFunctions.spec.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
const Agent = require('../../../../src/agent/Agent')
22
const Belief = require('../../../../src/agent/Belief')
3+
const FunctionalBelief = require('../../../../src/agent/FunctionalBelief')
34
const {
45
reviseSimpleNonmonotonic,
56
reviseMonotonic,
67
revisePriority,
7-
revisePriorityStatic } = require('../../../../src/agent/beliefRevision/revisionFunctions')
8+
revisePriorityStatic,
9+
getNonFunctionalBeliefs,
10+
preprocessFunctionalBeliefs } = require('../../../../src/agent/beliefRevision/revisionFunctions')
811

912
const {
1013
beliefs,
@@ -134,3 +137,23 @@ describe('revisionFunctions', () => {
134137
expect(newAgent.beliefs.isRaining.priority).toBe(1)
135138
})
136139
})
140+
141+
describe('functional belief revision functions', () => {
142+
const isRaining = Belief('isRaining', true, 1, false)
143+
const isSlippery = FunctionalBelief(
144+
'isSlippery', false, (_, newBeliefs) => newBeliefs.isRaining, 0
145+
)
146+
147+
it('(getNonFunctionalBeliefs) should filter out functional beliefs, given a belief base', () => {
148+
const nonFunctionalBeliefs = getNonFunctionalBeliefs({isRaining, isSlippery})
149+
expect(Object.keys(nonFunctionalBeliefs).length).toEqual(1)
150+
expect(nonFunctionalBeliefs.isRaining.value).toEqual(true)
151+
})
152+
153+
it('(preprocessFunctionalBeliefs) should filter out non-functional beliefs, given a belief base', () => {
154+
const functionalBeliefs = preprocessFunctionalBeliefs({isRaining, isSlippery})
155+
expect(functionalBeliefs.length).toEqual(1)
156+
expect(functionalBeliefs[0].value).toEqual(false)
157+
})
158+
159+
})

src/agent/Agent.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
const Intentions = require('./Intentions')
22
const defaultBeliefRevisionFunction = require('./beliefRevision/revisionFunctions').reviseSimpleNonmonotonic
3+
const {
4+
preprocessFunctionalBeliefs,
5+
getNonFunctionalBeliefs,
6+
getFunctionalBeliefs,
7+
processFunctionalBeliefs
8+
} = require('./beliefRevision/revisionFunctions')
39

410
const defaultPreferenceFunction = (beliefs, desires) => desireKey => desires[desireKey](beliefs)
511
const defaultGoalRevisionFunction = (beliefs, goals) => goals
@@ -82,7 +88,19 @@ function Agent (
8288
}
8389
this.isActive = true
8490
this.next = function (beliefs) {
85-
this.beliefs = this.reviseBeliefs(this.beliefs, beliefs)
91+
// revision of non-functional beliefs
92+
const oldBeliefs = getNonFunctionalBeliefs(this.beliefs)
93+
this.beliefs = this.reviseBeliefs(this.beliefs, getNonFunctionalBeliefs(beliefs))
94+
// revision of functional beliefs
95+
const newFunctionalBeliefs = processFunctionalBeliefs(
96+
getFunctionalBeliefs(this.beliefs),
97+
getFunctionalBeliefs(beliefs),
98+
oldBeliefs,
99+
beliefs,
100+
reviseBeliefs
101+
)
102+
this.beliefs = { ...this.beliefs, ...newFunctionalBeliefs }
103+
// end: revision of functional beliefs
86104
this.goals = this.reviseGoals(this.beliefs, this.goals)
87105
if (this.isActive) {
88106
if (Object.keys(this.desires).length === 0) {

src/agent/FunctionalBelief.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const Belief = require('./Belief')
2+
3+
const warning = 'JS-son: functional belief without proper two-argument function'
4+
5+
/**
6+
* JS-son agent belief generator
7+
* @param {string} id the belief's unique identifier
8+
* @param {any} value the belief's default value
9+
* @param {function} rule the function that is used to infer the belief, given the agent's current beliefs and the belief update
10+
* @param {number} order the belief's order when belief functions are evaluated
11+
* @param {number} priority the belief's priority in case of belief revision; optional
12+
* @param {boolean} updatePriority whether in case of a belief update, the priority of the defeating belief should be adopted; optional, defaults to true
13+
* @returns {object} JS-son agent functional belief
14+
*/
15+
const FunctionalBelief = (id, value, rule, order, priority, updatePriority=false) => {
16+
if (typeof rule !== 'function' || rule.length !== 2) console.warn(warning)
17+
const baseBelief = Belief(id, value, priority, updatePriority=false)
18+
return { ...baseBelief, rule, order, value }
19+
}
20+
21+
module.exports = FunctionalBelief

0 commit comments

Comments
 (0)