Skip to content

Commit

Permalink
#94 implement default messaging support (#95)
Browse files Browse the repository at this point in the history
#94 implement default messaging support

allow JS-son agents to send one or several "private" messages as part of a plan to
one or several agents
  • Loading branch information
TimKam authored Jul 15, 2020
1 parent 2759576 commit 6786d63
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 31 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,30 @@ Finally, we run 50 iterations of the scenario:
environment.run(50)
```

## 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.
For example, in the plan below, an agent sends the message ``'Hi!'`` to the agent with the ID ``alice``:

```JavaScript
const messagePlans = [
Plan(_ => true, () => ({ messages: [{ message: 'Hi!', agentId: 'alice' }] }))
]
```

Assuming that the sending agent has the ID ``bob``, the agent ``alice`` will receive the following belief update:

```JavaScript
beliefs = {
...beliefs,
messages: {
bob: ['Hi!']
}
}
```

Note that messages do not need to be strings, but can be of any type, for example objects.

## Further Examples

### Data Science
Expand Down
7 changes: 5 additions & 2 deletions spec/integration/beliefPlan.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const { updateMAS } = require('../mocks/environment')
describe('Integration: belief-plan approach', () => {
const human = new Agent('human', beliefs, desires, plans, preferenceFunctionGen)
const dog = new Agent('dog', dogBeliefs, dogDesires, dogPlans, dogPreferenceFunctionGen)
const emptyMessageObject = { human: {}, dog: {} }

const state = {
dogNice: true,
Expand All @@ -39,12 +40,14 @@ describe('Integration: belief-plan approach', () => {
dogNice: true,
dogHungry: true,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject
}, {
dogNice: true,
dogHungry: false,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject
}]
expect(history).toEqual(expectedHistory)
})
Expand Down
8 changes: 6 additions & 2 deletions spec/integration/full.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const { updateMAS } = require('../mocks/environment')
describe('Environment / run()', () => {
const human = new Agent('human', beliefs, desires, plans, preferenceFunctionGen)
const dog = new Agent('dog', dogBeliefs, dogDesires, dogPlans, dogPreferenceFunctionGen)
const emptyMessageObject = { human: {}, dog: {} }

const state = {
dogNice: true,
Expand All @@ -39,12 +40,15 @@ describe('Environment / run()', () => {
dogNice: true,
dogHungry: true,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject

}, {
dogNice: true,
dogHungry: false,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject
}]
expect(history).toEqual(expectedHistory)
})
Expand Down
80 changes: 57 additions & 23 deletions spec/src/environment/Environment.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Agent = require('../../../src/agent/Agent')
const Plan = require('../../../src/agent/Plan')
const Environment = require('../../../src/environment/Environment')

const {
Expand All @@ -17,28 +18,31 @@ const {

console.log = jasmine.createSpy('log')

const emptyMessageObject = { human: {}, dog: {} }

describe('Environment / run()', () => {
const human = new Agent('human', beliefs, desires, plans, preferenceFunctionGen)
const dog = new Agent('dog', dogBeliefs, dogDesires, dogPlans, dogPreferenceFunctionGen)

const state = {
dogNice: true,
dogHungry: true
}

const update = actions => (actions.some(
action => action.actions.includes('Here, take some food!')) ? { dogHungry: false } : {}
action => action.actions &&
action.actions.includes('Here, take some food!')) ? { dogHungry: false } : {}
)

it('Should process agent actions', () => {
const environment = new Environment([human], state, update)
environment.run(1)
expect(console.log).toHaveBeenCalledWith({ dogNice: true, dogHungry: false })
expect(console.log).toHaveBeenCalledWith(
{ dogNice: true, dogHungry: false, messages: { human: {} } }
)
})

it('Should allow agent-to-agent interaction', () => {
const dog = new Agent('dog', dogBeliefs, dogDesires, dogPlans, dogPreferenceFunctionGen)
// console.log(dog)
// const dog = createAgent('dog')
const updateMAS = actions => {
const stateUpdate = {}
actions.forEach(action => {
Expand Down Expand Up @@ -67,40 +71,42 @@ describe('Environment / run()', () => {
dogNice: true,
dogHungry: true,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject
}, {
dogNice: true,
dogHungry: false,
foodAvailable: false,
dogRecentlyPraised: false
dogRecentlyPraised: false,
messages: emptyMessageObject
}]
expect(JSON.stringify(history)).toEqual(JSON.stringify(expectedHistory))
expect(history).toEqual(expectedHistory)
})

it('Should terminate after the specified number of iterations', () => {
const environment = new Environment([human], state, update)
const history = environment.run(2)
const expectedHistory = [
{ dogNice: true, dogHungry: true },
{ dogNice: true, dogHungry: false },
{ dogNice: true, dogHungry: false }
{ dogNice: true, dogHungry: true, messages: { human: {} } },
{ dogNice: true, dogHungry: false, messages: { human: {} } },
{ dogNice: true, dogHungry: false, messages: { human: {} } }
]
expect(history).toEqual(expectedHistory)
})

it('Should allow for the specification of a custom runner function', () => {
const environment = new Environment(
[human],
[human, dog],
state,
update,
state => console.log(state),
state => state
)
const history = environment.run(2)
const expectedHistory = [
{ dogNice: true, dogHungry: true },
{ dogNice: true, dogHungry: false },
{ dogNice: true, dogHungry: false }
{ dogNice: true, dogHungry: true, messages: emptyMessageObject },
{ dogNice: true, dogHungry: false, messages: emptyMessageObject },
{ dogNice: true, dogHungry: false, messages: emptyMessageObject }
]
expect(history).toEqual(expectedHistory)
})
Expand All @@ -125,30 +131,58 @@ describe('Environment / run()', () => {
return state
}
const history1 = new Environment(
[human],
[human, dog],
state,
update,
state => console.log(state),
stateFilter1
).run(2)
const expectedHistory1 = [
{ dogNice: true, dogHungry: true },
{ dogNice: true, dogHungry: true },
{ dogNice: true, dogHungry: true }
{ dogNice: true, dogHungry: true, messages: emptyMessageObject },
{ dogNice: true, dogHungry: true, messages: emptyMessageObject },
{ dogNice: true, dogHungry: true, messages: emptyMessageObject }
]
expect(history1).toEqual(expectedHistory1)
const history2 = new Environment(
[human],
[human, dog],
state,
update,
state => console.log(state),
stateFilter2
).run(2)
const expectedHistory2 = [
{ dogNice: true, dogHungry: true },
{ dogNice: true, dogHungry: false },
{ dogNice: true, dogHungry: false }
{ dogNice: true, dogHungry: true, messages: emptyMessageObject },
{ dogNice: true, dogHungry: false, messages: emptyMessageObject },
{ dogNice: true, dogHungry: false, messages: emptyMessageObject }
]
expect(history2).toEqual(expectedHistory2)
})

it('Should allow for agent-to-agent message passing', () => {
const messagePlans = [
Plan(_ => true, () => ({ messages: [{ message: 'Hi!', agentId: 'human' }] }))
]
const filledMessageObject = {
...emptyMessageObject,
human2: {},
human: { human2: ['Hi!'] }
}
const human2 = new Agent('human2', beliefs, desires, messagePlans, preferenceFunctionGen)
const env = new Environment(
[human, human2, dog],
state,
update
)
const history = env.run(2)
const expectedHistory = [
{ dogNice: true, dogHungry: true, messages: filledMessageObject },
{ dogNice: true, dogHungry: false, messages: filledMessageObject },
{ dogNice: true, dogHungry: false, messages: filledMessageObject }
]
expect(history).toEqual(expectedHistory)
expect(env.agents.human.beliefs.messages.human2).toContain('Hi!')
expect(env.agents.human.beliefs.messages.human2.length).toEqual(1)
expect(env.agents.human2.beliefs.messages.human2.length).toEqual(0)
expect(env.agents.dog.beliefs.messages.human2.length).toEqual(0)
})
})
37 changes: 33 additions & 4 deletions src/environment/Environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ function Environment (
}
}
) {
this.agents = {}
agents.forEach(agent => (this.agents[agent.id] = agent))
state.messages = {}
this.state = state
this.agents = {}
agents.forEach(agent => {
this.agents[agent.id] = agent
this.state.messages[agent.id] = {}
})
this.update = update
this.render = render
this.stateFilter = stateFilter
Expand All @@ -35,9 +39,34 @@ function Environment (
this.history.push(this.state)
const run = () => {
Object.keys(this.agents).forEach(agentKey => {
const messages = {}
Object.keys(this.state.messages).forEach(recipient => {
Object.keys(this.state.messages[recipient]).forEach(sender => {
if (recipient !== agentKey) {
messages[sender] = []
return
}
messages[sender] = this.state.messages[recipient][sender]
})
})
const preFilteredState = { ...this.state, messages }
const proposedUpdate = this.agents[agentKey].next(
this.stateFilter(this.state, agentKey, this.agents[agentKey].beliefs)
)
this.stateFilter(preFilteredState, agentKey, this.agents[agentKey].beliefs))
proposedUpdate.forEach(action => {
Object.keys(this.state.messages).forEach(key => {
if (this.state.messages[key][agentKey]) this.state.messages[key][agentKey] = []
})
if (action.messages) {
action.messages.forEach(
message => {
if (!this.state.messages[[message.agentId]][agentKey]) {
this.state.messages[[message.agentId]][agentKey] = []
}
this.state.messages[message.agentId][agentKey].push(message.message)
}
)
}
})
const stateUpdate = this.update(proposedUpdate, agentKey, this.state)
this.state = {
...this.state,
Expand Down

0 comments on commit 6786d63

Please sign in to comment.