sim

package
v0.0.0-...-20992a5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 28, 2024 License: MIT Imports: 11 Imported by: 0

README

sim GoDoc

This package contains all of the code for the simulator itself.

Overview

The simulator runs over multiple iterations. Each iteration starts with every Agent in the Network attempting to contact another Agent in the list of Agents that are directly related to it (joined by a single link). The list of Agents related to it is obtained from the Network, and this list is always returned in a random order. Each Agent can only accept a single Mail in its Mail queue, so during this process each Agent will iterate over all other Agents directly related to it until it finds an Agent that has an empty Mail queue and can accept its Mail.

Once all Agents have completed the process of trying to send a Mail the next phase begins. Now each Agent reads the Mail it has in its Mail queue if any. The Mail contains the Identifier of the Agent that sent it, and the receiving Agent uses this to look up the sending Agent from the Network. Each Agent has three properties: Influence, Susceptibility, and Contrariness. The receiving Agent compares its properties to that of the sending Agent and uses a simple algorithm to decide how to update its Color. If the sending Agent has a Influence higher than the receiving Agent's Susceptibility then the receiving Agent will update its Color. If the receiving Agent's Contrariness is higher than the sending Agent's Influence then the receiving Agent will update to a random Color different from its previous Color, and from the Color of the sending Agent. If the receiving Agent's Contrariness is lower than the sending Agent's Influence then the receiving Agent updates its Color to the same as the sending Agent.

The RunSim function in orgnetsim.go is the entry point for a simulation. A RelationshipMgr (the interface to a Network) is passed into this function along with a number of iterations. As each iteration is performed, the Agents held within the RelationshipMgr are updated, and a log is taken of the number of Agents with each Color, and the number of "conversations" that happened in the iteration. At the end of the simulation, two slices are returned. The first slice is a two dimensional slice. The first dimension is Color, the second dimension is the number of iterations. Each element contains the count of the number of Agents with the given Color on the specified iteration. The second slice contains a count of the number of "conversations" that occured between all agents for each iteration.

After a simulation run has completed the Agents and Links can be accessed from the RelationshipMgr. Each Agent keeps a count of the number of times it updated its Color. Each Link keeps a count of the number of conversations that happen across that link. These can be accessed like this:

var n RelationshipMgr

for _, a := range n.Agents() {
	agc := a.State().ChangeCount
    ...
}

for _, l := range n.Links() {
	ls := l.Strength
    ...
}

Getting started

An example run of a simulation can be executed as a unit test with code like this

func TestGenerateNetworkRunSim(t *testing.T) {

	s := HierarchySpec{
		Levels:           4,
		TeamSize:         5,
		TeamLinkLevel:    3,
		LinkTeamPeers:    true,
		LinkTeams:        false,
		InitColors:       []Color{Grey, Red},
		MaxColors:        4,
		EvangelistAgents: false,
		LoneEvangelist:   false,
		AgentsWithMemory: true,
	}

	n, err := GenerateHierarchy(s)
	AssertSuccess(t, err)

	colors, conversations := RunSim(n, 500)
	WriteOutput(t, s, n, colors, conversations)
}

This simulation uses the GenerateHierarchy function to generate a hierarchal network, and then passes that network into the RunSim function. The simulation is run for 500 iterations and then the slices returned from the simulation, the change count of each node, and the strength of each link are written into out.csv along with the list of parameters provided in the HierarchySpec struct used to generate the network. This is a convenient output for loading the results into a spreadsheet so that you can plot graphs of the number of Agents with each Color.

There are test methods in orgnetsim_test.go that can be used to generate a JSON file containing a hierarchical network from a hierarchy spec, and that can run a simulation from a network saved in a JSON file.

RelationshipMgr aka the Network

In the examples networks with regular features have been generated automatically using the networkgenerator. However the Network struct (implements RelationshipMgr) has been designed so that it can be created from a JSON description of the network. Here is an example:

json := '{"nodes":[{"id":"id_1"},{"id":"id_2"},{"id":"id_3"}],"links":[{"agent1Id":"id_1","agent2Id":"id_2"},{"agent1Id":"id_1","agent2Id":"id_3"}]}'
n, err := NewNetwork(json)
	
serJSON := n.Serialise()

In JSON each Agent in the "nodes" array has a "type" property which specifies which type of Agent to use (Agent or AgentWithMemory). If this property is not specified then it will default to Agent. If there is more than one Agent with the same "id" value then the last Agent encountered in the list of "nodes" will be the only one present in the network when unmarshalling is complete. Similarly if there are duplicate links (more than one link between the same pair of nodes) only the last link will be present in the network.

The network struct has been designed so that any organisational network (such as your team/business unit org chart etc) can be used as the basis of a simulation. Teams need not be regularly sized, any graph can be specified as individual nodes and links between them. After the simulation is complete it will be possible to marshal the Network back to json. When the network is returned as json the Agent states including the ChangeCount and Link strengths are recorded. This is done to allow simulations to be run for many iterations or even stepped through in smaller increments.

Options to control a simulation

The number of possible competing ideas in a simulation is controlled by the MaxColors constant in color.go.

A networkgenerator can be used to generate hierarchical networks with different structures. The generator is controlled by the fields on a HierarchySpec.

s := HierarchySpec{
		Levels:           4,
		TeamSize:         5,
		TeamLinkLevel:    3,
		LinkTeamPeers:    true,
		LinkTeams:        false,
		InitColors:       []Color{Grey, Red},
		MaxColors:        4,
		EvangelistAgents: false,
		LoneEvangelist:   false,
		AgentsWithMemory: true,
	}

The Network generated will always be Hierarchical with a single parent Agent. The number of Levels are the number of layers in the Hierarchy including the parent Agent. The TeamSize controls how many Agents are in each team. So in this example the parent Agent is the first layer, five Agents will be linked to the parent in the second layer, there will be five Agents linked to each agent in the second layer in the third layer, and in the fourth and final layer, five Agents will be linked to each Agent in the third layer. In total there will be 1 + 5 + 5*5 + 5*5*5 = 156 Agents.

The LinkTeamPeers option allows you to control how Agents within a team are connected on the Network. If this option is false, Agents will only be connected to their direct ancestors and children in the Hierarchy. With LinkTeamPeers set to true each team that is created will have each Agent within the team connected to every other Agent within that team. So if there are five Agents, within a team each of them will have an additional four links to connect them to their peers within the team.

The LinkTeams option allows the user to create additional connectivity between teams within a deep hierarchy. This is used together with the TeamLinkLevel option. When LinkTeams is set to true, a single member is selected from each team and additional links are created to connect that Agent with her equivalent on every other team within the same level. The TeamLinkLevel is used to control at which layer in the hierarchy these additional links are created between teams. These options can be used to model the effect of intentionally introducing a cross-organisational initiative to encourage communication between teams at lower levels of the hierarchy.

The InitColors option allows the user to set the Colors that the Agents on the network will be initialised to. If this is set to nil all Agents will be initialised with Grey. If Colors are specified within this slice then the Agents in the network will be randomly assigned a Color from this slice. The Colors will be randomly distributed over the Network.

EvangelistAgents is used to specify a set of Agents selected from teams at a specified level who are set to the Color Blue, and will never choose to change their Color. A single Agent is selected from each team in the level specified by the TeamLinkLevel option. The Agents are modified so that their Susceptibility score can never be beaten, and their Color is set to Blue. This option is used to model the effect of selecting a small determined set of individuals across the organisation to introduce a specific idea or cultural change.

LoneEvangelist is similar to EvangelistAgents except there is only one agent who is an evangelist for a particular idea but she is connected to a single individual from each team at the level specified by TeamLinkLevel. This is modeling a similar effect, but it is a particularly determined individual who is well connected across the organisation trying to introduce a new idea or cultural change.

The final option is AgentsWithMemory. When set to true this uses a different Agent model that also contains memory. An Agent remembers all the previous Colors it has updated itself to. When deciding to update to a new Color it will never choose a Color that it has already been set to in the past. Without Agent memory the simulation is useful for modeling the uptake of an idea or change that is less likely to be permanent, like the preference for wearing a particular colour, or perhaps political affiliations. Whereas using Agents with memory is more useful to model the introduction of ideas that are likely to involve a permanent change such as competing technologies where adopting the technology will result in a certain amount of lock-in.

Documentation

Index

Constants

View Source
const MaxDefinedColors int = 7

MaxDefinedColors is the number of defined colors.

Variables

This section is empty.

Functions

func GenerateHierarchy

func GenerateHierarchy(s HierarchySpec) (*Network, *NetworkOptions, error)

GenerateHierarchy generates a hierarchical network

Types

type Agent

type Agent interface {
	Initialise(n RelationshipMgr)
	Identifier() string
	AgentName() string
	State() *AgentState
	SendMail(n RelationshipMgr) int
	ReadMail(n RelationshipMgr) Color
	ClearMail()
	GetColor() Color
	PostMsg(msg string) bool
	ReceiveMsg() (string, bool)
}

Agent is an interface that allows interaction with an Agent

func GenerateRandomAgent

func GenerateRandomAgent(id string, name string, initColors []Color, withMemory bool) Agent

GenerateRandomAgent creates an Agent with random properties

type AgentLink struct {
	Agent Agent
	Link  *Link
}

AgentLink holds both the Link and the Agent in the AgentLinkMap

type AgentState

type AgentState struct {
	ID             string      `json:"id"`
	Name           string      `json:"name"`
	Color          Color       `json:"color"`
	Susceptability float64     `json:"susceptability"`
	Influence      float64     `json:"influence"`
	Contrariness   float64     `json:"contrariness"`
	Mail           chan string `json:"-"`
	ChangeCount    int         `json:"change"`
	Type           string      `json:"type"`
	X              float64     `json:"fx,omitempty"`
	Y              float64     `json:"fy,omitempty"`
}

An AgentState carries the state of a node in the network

func (*AgentState) AgentName

func (a *AgentState) AgentName() string

State returns the Name of this Agent

func (*AgentState) ClearMail

func (a *AgentState) ClearMail()

ClearMail throws away any message on the Agent's Mail channel

func (*AgentState) GetColor

func (a *AgentState) GetColor() Color

GetColor returns the Color of this Agent

func (*AgentState) Identifier

func (a *AgentState) Identifier() string

Identifier returns the Identifier for the Agent

func (*AgentState) Initialise

func (a *AgentState) Initialise(n RelationshipMgr)

Initialise ensures the agent is correctly initialised

func (*AgentState) PostMsg

func (a *AgentState) PostMsg(msg string) bool

PostMsg tries to add an entry into an Agent's Mail channel, if it succeeds, that Agent will be blocked for any other Agent trying to send a Mail and this function returns true (the Agent is now Matched). If it returns false the Agent is already matched by another Agent.

func (*AgentState) ReadMail

func (a *AgentState) ReadMail(n RelationshipMgr) Color

ReadMail checks for any messages it received in its own Mail queue. If it receives one then it decides whether to update its color.

func (*AgentState) ReceiveMsg

func (a *AgentState) ReceiveMsg() (string, bool)

ReceiveMsg picks a message up from the Agent's Mail channel

func (*AgentState) SendMail

func (a *AgentState) SendMail(n RelationshipMgr) int

SendMail iterates over a randomly ordered slice of related agents trying to find a match. It sends a mail to the first successful match it finds.

func (*AgentState) SetColor

func (a *AgentState) SetColor(color Color)

SetColor changes the color of the current Agent and counts the number of times the Agent changes color It also adds each color to a memory so that once it changes it's mind it doesn't change back

func (*AgentState) State

func (a *AgentState) State() *AgentState

State returns the struct containing the state of this Agent

func (*AgentState) UpdateColor

func (a *AgentState) UpdateColor(n RelationshipMgr, ra *AgentState) (Color, bool)

UpdateColor looks at the properties of the passed agent and decides what the agent should update its color to

type AgentWithMemory

type AgentWithMemory struct {
	AgentState
	PreviousColors map[Color]struct{} `json:"-"`
	ShortMemory    map[Color]struct{} `json:"-"`
	MaxColors      int                `json:"-"`
}

An AgentWithMemory is a node in the network that has memory

func (*AgentWithMemory) Initialise

func (a *AgentWithMemory) Initialise(n RelationshipMgr)

Initialise ensures the agent is correctly initialised

func (*AgentWithMemory) ReadMail

func (a *AgentWithMemory) ReadMail(n RelationshipMgr) Color

ReadMail checks for any messages it received in its own Mail queue. If it receives one then it decides whether to update its color.

func (*AgentWithMemory) SetColor

func (a *AgentWithMemory) SetColor(color Color)

SetColor changes the color of the current Agent and counts the number of times the Agent changes color It also adds each color to a short term memory. It will only change its color if it hears about another color twice. Once it has updated its color it will clear its short term memory and update its long term memory with its previous color so that it doesn't get set to the same color twice

func (*AgentWithMemory) State

func (a *AgentWithMemory) State() *AgentState

State returns the struct containing the state of this Agent

type Color

type Color int

A Color of an Agent

const (
	Grey Color = iota
	Blue
	Red
	Green
	Yellow
	Orange
	Purple
)

A List of named colours for Agents

func RandomlySelectAlternateColor

func RandomlySelectAlternateColor(color Color, maxColors int) Color

RandomlySelectAlternateColor selects a Color other than the one passed and other than Grey unless there is only one color to choose from

func (Color) String

func (i Color) String() string

type HierarchySpec

type HierarchySpec struct {
	Levels           int     `json:"levels"`
	TeamSize         int     `json:"teamSize"`
	TeamLinkLevel    int     `json:"teamLinkLevel"`
	LinkTeamPeers    bool    `json:"linkTeamPeers"`
	LinkTeams        bool    `json:"linkTeams"`
	InitColors       []Color `json:"initColors"`
	MaxColors        int     `json:"maxColors"`
	EvangelistAgents bool    `json:"evangelistAgents"`
	LoneEvangelist   bool    `json:"loneEvangelist"`
	AgentsWithMemory bool    `json:"agentsWithMemory"`
}

HierarchySpec provides parameters to the GenerateHierarchy function specifying the features of the Hierarchical network to generate

type Link struct {
	Agent1ID string  `json:"source"`
	Agent2ID string  `json:"target"`
	Strength int     `json:"strength,omitempty"`
	Length   float64 `json:"length,omitempty"`
}

A Link between Agents in the Network

type Network

type Network struct {
	Edges         []*Link                         `json:"links"`
	Nodes         []Agent                         `json:"nodes"`
	AgentsByID    map[string]Agent                `json:"-"`
	AgentLinkMap  map[string]map[string]AgentLink `json:"-"`
	MaxColorCount int                             `json:"maxColors"`
}

A Network of Agents

func NewNetwork

func NewNetwork(jsonBody string) (*Network, error)

NewNetwork creates a new Network structure from the passed json string

func (*Network) AddAgent

func (n *Network) AddAgent(a Agent)

AddAgent adds a new Agent to the network

func (n *Network) AddLink(a1 Agent, a2 Agent)

AddLink adds a new Link between the two passed agents

func (*Network) Agents

func (n *Network) Agents() []Agent

Agents returns a list of the Agents Communicating on the Network

func (*Network) GetAgentByID

func (n *Network) GetAgentByID(id string) Agent

GetAgentByID returns a reference to the Agent with the given ID or nil if it doesn't exist

func (*Network) GetRelatedAgents

func (n *Network) GetRelatedAgents(a Agent) []Agent

GetRelatedAgents returns a slice of Agents adjacent in the Network to the passed Agent The returned slice of Agents is always deliberately shuffled into random order

func (*Network) IncrementLinkStrength

func (n *Network) IncrementLinkStrength(id1 string, id2 string) error

IncrementLinkStrength updates the strength field of the link connecting Agents id1 and id2. Returns an error if no link is found

func (n *Network) Links() []*Link

Links returns a list of the links between Agents on the Network

func (*Network) MaxColors

func (n *Network) MaxColors() int

MaxColors returns the maximum number of color states that the agents are permitted on this network

func (*Network) PopulateMaps

func (n *Network) PopulateMaps() error

PopulateMaps creates the map lookups from the Links and Nodes arrays

func (*Network) Serialise

func (n *Network) Serialise() string

Serialise returns a json representation of the Network

func (*Network) SetMaxColors

func (n *Network) SetMaxColors(c int)

SetMaxColors sets the maximum number of color states that the agents are permitted on this network

func (*Network) UnmarshalJSON

func (n *Network) UnmarshalJSON(b []byte) error

UnmarshalJSON implements unmarshalling of Agents of different types

type NetworkOptions

type NetworkOptions struct {
	LinkTeamPeers    bool     `json:"linkTeamPeers"`
	LinkedTeamList   []string `json:"linkedTeamList"`
	EvangelistList   []string `json:"evangelistList"`
	LoneEvangelist   []string `json:"loneEvangelist"`
	InitColors       []Color  `json:"initColors"`
	MaxColors        int      `json:"maxColors"`
	AgentsWithMemory bool     `json:"agentsWithMemory"`
}

NetworkOptions contains information about how the network is set up for the simulation

func CreateNetworkOptions

func CreateNetworkOptions(s HierarchySpec) *NetworkOptions

CreateNetworkOptions creates a new network modifier from the passed HierarchySpec

func (*NetworkOptions) AddEvangelists

func (o *NetworkOptions) AddEvangelists(rm RelationshipMgr) error

AddEvangelists sets a list of individuals to Blue and increases their susceptibility so that they cannot be influenced by another Agent

func (*NetworkOptions) AddLoneEvangelist

func (o *NetworkOptions) AddLoneEvangelist(rm RelationshipMgr) error

AddLoneEvangelist links a single Agent to a list of other Agents across the Network. The first agent in the LoneEvangelist list is the Evangelist and all subsequent Agents are connected to her. If the Lone Evangelist Id does not exist in the network she is created.

func (o *NetworkOptions) AddTeamPeerLinks(rm RelationshipMgr) error

AddTeamPeerLinks links all Agents related to the same parent node to each other. Turns a strictly hierarchical network in to a more realistic communication network.

func (*NetworkOptions) CloneModify

func (o *NetworkOptions) CloneModify(rm RelationshipMgr) (RelationshipMgr, error)

CloneModify clones the agents and links in the passed RelationshipMgr into a new RelationshipMgr changing the Agent type and initial colors of all Agents on the Network, then it modifies the links as specified in the passed Options struct.

func (*NetworkOptions) LinkTeams

func (o *NetworkOptions) LinkTeams(rm RelationshipMgr) error

LinkTeams creates links between a specified set of individuals from across teams in the network

func (*NetworkOptions) ModifyNetwork

func (o *NetworkOptions) ModifyNetwork(rm RelationshipMgr) error

ModifyNetwork takes a RelationshipMgr as input and adds links as specified in the passed Options struct. Note this method will ignore the InitColors and AgentsWithMemory options because a new set of Agents require to be generated in order to set these options. To do that use the CloneModify function instead.

type ParseOptions

type ParseOptions struct {
	Identifier int               `json:"identifier"`
	Parent     int               `json:"parent"`
	Name       int               `json:"name"`
	Regex      map[string]string `json:"regex"`
	Delimiter  string            `json:"delimiter"`
}

ParseOptions contains details about how to parse data from the incoming file Identifier provides the index of the column to use as an Agnent Identifier Parent provides the index of the column to use as the Identifier of a Parent Agent Regex provides the regular expressions to use to extract data from the columns. It is in the form of a map, the key being the index of the column, the value being the regex to apply. When a Regex is supplied for the parent or identifier columns it will be used to extract the value to use as the Identifier. If it is applied to another column then the row will be skipped where the regex does not match the contents in that column. Any regex supplied for a column that doesn't exist within a row will result in that row being skipped. If no regex is supplied for the parent and identifier columns then a default is applied which will strip leading and trailing whitespace. Delimiter is the delimiter to use when slicing rows into columns

func (*ParseOptions) GetColRegex

func (po *ParseOptions) GetColRegex(col int) (*regexp.Regexp, bool)

GetColRegex returns the Regexp that must be applied to the indicated column

func (*ParseOptions) GetOtherRegex

func (po *ParseOptions) GetOtherRegex() map[int]*regexp.Regexp

GetOtherRegex returns the compiled regular expressions for columns other than the parent and identifier columns

func (*ParseOptions) IdentifierRegex

func (po *ParseOptions) IdentifierRegex() *regexp.Regexp

IdentifierRegex returns the Regexp that must be applied to the Identifier column

func (*ParseOptions) ParentRegex

func (po *ParseOptions) ParentRegex() *regexp.Regexp

ParentRegex returns the Regexp that must be applied to the Parent column

func (*ParseOptions) ParseDelim

func (po *ParseOptions) ParseDelim(data []string) (RelationshipMgr, error)

ParseDelim takes a hierarchy expressed in a comma separated file and generates a Network out of it po are the ParseOptions that control how the parse will operate. returns a RelationshipMgr containing all the Agents with links to parent Agents as described in the input data. If the same Id is listed in multiple rows as specified after any regular expressions is applied the first row is used and subsequent rows are ignored.

func (*ParseOptions) ParseEdges

func (po *ParseOptions) ParseEdges(edges []string, n RelationshipMgr) (RelationshipMgr, error)

ParseEdges is a variant of ParseDelim that takes a list of edges and adds the links in to an existing network. This is useful when the network is expressed as separate lists of nodes and edges, or when the nodes data is a list of parent child relationships and there are additional relationships to be added to the network to complete it. ParseDelim may be used to parse a list of nodes with or without edges. The edges are expected to be in the form of a list of pairs of IDs. Unlike ParseDelim this function will not add any Agents that are not already in the network. It will also ignore any edges that are not between two Agents that are already in the network.

type RelationshipMgr

type RelationshipMgr interface {
	GetRelatedAgents(a Agent) []Agent
	GetAgentByID(id string) Agent
	IncrementLinkStrength(id1 string, id2 string) error
	AddAgent(a Agent)
	AddLink(a1 Agent, a2 Agent)
	Agents() []Agent
	Links() []*Link
	MaxColors() int
	SetMaxColors(c int)
	PopulateMaps() error
}

RelationshipMgr is an interface for the Network

type Results

type Results struct {
	Iterations    int     `json:"iterations"`
	Colors        [][]int `json:"colors"`
	Conversations []int   `json:"conversations"`
}

Results contains the results from a Sim run over a number of iterations

type Runner

type Runner interface {
	Run() Results
	GetRelationshipMgr() RelationshipMgr
}

Runner is used to run a simulation for a specified number of steps on its network

func NewRunner

func NewRunner(n RelationshipMgr, iterations int) Runner

NewRunner returns an instance of a sim Runner

type RunnerInfo

type RunnerInfo struct {
	RelationshipMgr RelationshipMgr `json:"network"`
	Iterations      int             `json:"iterations"`
}

RunnerInfo specifies the number of iterations and steps to run and records the results

func (*RunnerInfo) GetRelationshipMgr

func (ri *RunnerInfo) GetRelationshipMgr() RelationshipMgr

GetRelationshipMgr returns the internal network state

func (*RunnerInfo) Run

func (ri *RunnerInfo) Run() Results

Run runs the simulation

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL