Skip to content

Commit

Permalink
Refactoring: made code consistent with published papers (in particula…
Browse files Browse the repository at this point in the history
…r: agent.py).
  • Loading branch information
ambimanus committed Jul 1, 2016
1 parent 5f75cf7 commit 95f6a89
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 329 deletions.
9 changes: 8 additions & 1 deletion README.md
Expand Up @@ -3,4 +3,11 @@ In decentralized systems, where the search space of a given optimization problem

For this purpose, the heuristic [COHDA (Combinatorial Optimization Heuristic for Distributed Agents)](http://www.uni-oldenburg.de/en/computingscience/ui/research/topics/cohda/) was developed. The approach is based on self-organization strategies and yields a distributed combinatorial optimization process in an asynchronous communication environment. Central components for data storage or coordination purposes are not necessary.

I created COHDA as part of my [PhD thesis](http://oops.uni-oldenburg.de/1960/) (in German).
I created COHDA as part of my [PhD thesis](http://oops.uni-oldenburg.de/1960/) (in German).


## Requirements

- Python 2.7
- numpy
- progressbar 2.2 (pip install progressbar==2.2)
437 changes: 166 additions & 271 deletions src/agent.py

Large diffs are not rendered by default.

22 changes: 14 additions & 8 deletions src/cli.py
Expand Up @@ -32,8 +32,6 @@ def pr(name, low, high, val):
agents = dict()
Agent = import_object(sc[KW_AGENT_MODULE], sc[KW_AGENT_TYPE])
for i, aid in enumerate(sc[KW_AGENT_IDS]):
# Determine seed for MOCOMixin
seed = cfg.rnd.randint(0, cfg.rng_seed_max)
# Get weights
if KW_OPT_W_DICT in sc:
w = sc[KW_OPT_W_DICT][aid]
Expand All @@ -45,18 +43,26 @@ def pr(name, low, high, val):
else:
sol_init = sc[KW_SOL_INIT][i]
# Start agent process
a = Agent(aid, w, sol_init,
cfg.rnd.randint(cfg.agent_delay_min, cfg.agent_delay_max),
seed, sc[KW_OPT_P_REFUSE][i])
a = Agent(aid, w, sol_init)
# If desired, set p_refuse and seed for feasibility check
if KW_OPT_P_REFUSE in sc and sc[KW_OPT_P_REFUSE][i] > 0:
seed = cfg.rnd.randint(0, cfg.rng_seed_max)
a.set_p_refuse(sc[KW_OPT_P_REFUSE][i], seed)
if 'Stigspace' in sc[KW_AGENT_TYPE]:
Stigspace.set_active(True)
agents[aid] = a

INFO('Connecting agents')
for a, neighbors in sc[KW_NETWORK].items():
for n in neighbors:
DEBUG('', 'Connecting', n, '->', a)
agents[a].add_peer(n, agents[n])
# Consistency check
assert a != n, 'cannot add myself as neighbor!'
# Add neighbor
DEBUG('', 'Connecting', a, '->', n)
if n not in agents[a].neighbors:
agents[a].neighbors[n] = agents[n]
else:
WARNING(n, 'is already neighbor of', a)

sim = Simulator(cfg, agents)

Expand Down Expand Up @@ -87,7 +93,7 @@ def pr(name, low, high, val):
agent_delay_max=None,
log_to_file=False,
)
sc = scenarios.SC(cfg.rnd, cfg.seed, opt_h='random')
sc = scenarios.SC(cfg.rnd, cfg.seed, opt_h=5)
# sc = scenarios.SVSM(cfg.rnd, cfg.seed)
# sc = scenarios.CHP(cfg.rnd, cfg.seed, opt_m=10, opt_n=400, opt_q=16, opt_q_constant=20.0)
#
Expand Down
28 changes: 28 additions & 0 deletions src/main.py
@@ -0,0 +1,28 @@
# coding=utf-8

import scenarios
import cli
from configuration import Configuration


if __name__ == '__main__':
cfg = Configuration(
seed=0,
msg_delay_min=None,
msg_delay_max=None,
agent_delay_min=None,
agent_delay_max=None,
log_to_file=False,
)
sc = scenarios.SC(cfg.rnd, cfg.seed, opt_h=5)
# sc = scenarios.SVSM(cfg.rnd, cfg.seed)
# sc = scenarios.CHP(cfg.rnd, cfg.seed, opt_m=10, opt_n=400, opt_q=16, opt_q_constant=20.0)
#
# sc = scenarios.SC(cfg.rnd, cfg.seed, opt_h=5,
# agent_type='AgentStigspaceMMMSSP')
# sc = scenarios.SVSM(cfg.rnd, cfg.seed, agent_type='AgentStigspaceSVSM')
# sc = scenarios.CHP(cfg.rnd, cfg.seed, opt_m=30, opt_n=200,
# agent_type='AgentStigspaceMMMSSP')
cfg.scenario = sc

cli.run(cfg)
77 changes: 77 additions & 0 deletions src/model.py
@@ -0,0 +1,77 @@
# coding=utf-8

import numpy as np


"""
Data models, directly corresponding to the formal model from the 2016 IJBIC
paper (in press).
"""


class Schedule_Selection():

def __init__(self, aid, schedule, v_lambda):
"""Initializes a schedule selection.
Arguments:
aid -- agent identifier (str)
schedule -- the selected schedule (ndarray)
v_lamdba -- the selection counter (int)
"""
self.aid = aid
self.schedule = schedule
self.v_lambda = v_lambda

def __eq__(self, other):
if isinstance(other, self.__class__):
return (other.v_lambda == self.v_lambda and
np.all(other.schedule == self.schedule))
else:
return False

def __ne__(self, other):
return not self.__eq__(other)


class Solution_Candidate():

def __init__(self, aid, configuration, rating):
"""Initializes a solution candidate.
Arguments:
aid -- agent identifier (str)
configuration -- system configuration (dict(str: schedule_selection))
rating -- rating of this solution candidate (float)
"""
self.aid = aid
self.configuration = dict(configuration)
self.rating = rating

def copy(self):
return Solution_Candidate(self.aid, self.configuration, self.rating)

def keys(self):
return self.configuration.keys()

def get(aid):
return self.configuration[aid]


class Working_Memory():

def __init__(self, objective, configuration, solution_candidate):
"""Initializes an agent's working memory.
Arguments:
objective -- one-argument objective function (obj --> float)
configuration -- system configuration (dict(str: schedule_selection))
solution_candidate -- initial solution candidate (Solution_Candidate)
"""
self.objective = objective
self.configuration = dict(configuration)
self.solution_candidate = solution_candidate.copy()

def copy(self):
return Working_Memory(self.objective, self.configuration,
self.solution_candidate.copy())
28 changes: 25 additions & 3 deletions src/scenarios.py
Expand Up @@ -2,6 +2,7 @@

from __future__ import division

import sys
import os
import csv
from datetime import datetime
Expand Down Expand Up @@ -113,8 +114,18 @@ def _bounds(opt_w, opt_m, objective, zerobound=True):
return sol_d_max, sol_d_min, sol_j_max


def _bounds_bruteforce(h, n, m):
d = np.load('../sc_data/bruteforce_h%03d.npy' % h)
def _bounds_bruteforce(h, n, m, w, obj):
path = os.path.dirname(os.path.realpath(__file__))
fn = '%s/../sc_data/bruteforce_h%03d.npy' % (path, h)
print fn
if os.path.exists(fn):
d = np.load(fn)
else:
print 'Bruteforce results file not found, calculating now.'
print '(You can store the results, see %s.%s)' % current_method()
results = bruteforce({KW_OPT_M: m, KW_OPT_N: n, KW_OPT_W: w,
KW_OBJECTIVE: obj})
# np.save(fn, results)
imin, imax = np.argmin(d), np.argmax(d)
vmin, vmax = d.min(), d.max()
conv = base10toN(imax, n)
Expand Down Expand Up @@ -189,7 +200,8 @@ def SC(rnd, seed,
raise RuntimeError(opt_p_refuse_type)

if opt_m == 10 and opt_n == 5 and opt_q == 5:
sol_d_max, sol_d_min, sol_j_max = _bounds_bruteforce(opt_h, opt_n, opt_m)
sol_d_max, sol_d_min, sol_j_max = _bounds_bruteforce(opt_h, opt_n,
opt_m, opt_w, objective)
else:
sol_d_max, sol_d_min, sol_j_max = _bounds(opt_w, opt_m, objective)

Expand Down Expand Up @@ -392,6 +404,16 @@ def ids(m, n):


def bruteforce(sc, progress=True, thres=None):
# Install signal handler for SIGINT
import signal

def sigint_detected(signal, frame):
print 'Stopping due to user request (SIGINT detected).'
sys.exit(1)

signal.signal(signal.SIGINT, sigint_detected)
print 'Cancel with Ctrl-C (i.e., SIGINT).'

# print
# print 'brute force'

Expand Down
45 changes: 28 additions & 17 deletions src/simulator.py
Expand Up @@ -7,11 +7,13 @@
from logger import *
from stigspace import Stigspace
from util import PBar
from model import Working_Memory, Solution_Candidate


# Install signal handler for SIGINT
SIGINT_DETECTED = False


def sigint_detected(signal, frame):
global SIGINT_DETECTED
SIGINT_DETECTED = True
Expand All @@ -26,21 +28,23 @@ def __init__(self, cfg, agents):
self.agents = agents
self.current_time = 0
self.messages = []
self.agent_delays = {aid: 0 for aid in self.agents}

def init(self):
for a in self.agents.values():
a.init(self)
speaker = (self.cfg.rnd.choice(self.agents.keys())
if self.cfg.random_speaker else 0)
INFO('Notifying speaker (a%s)' % speaker)
objective = self.sc[KW_OBJECTIVE]
kappa = Working_Memory(self.sc[KW_OBJECTIVE], dict(),
Solution_Candidate(None, dict(), float('inf')))
msg = {'sender': 'mas', 'kappa': kappa}
if Stigspace.active:
for a in self.agents.values():
a.notify(objective)
a.inbox.append(msg)
else:
self.agents[speaker].notify(objective)
objective._reset_call_counter()

speaker = (self.cfg.rnd.choice(self.agents.keys())
if self.cfg.random_speaker else 0)
INFO('Notifying speaker (a%s)' % speaker)
self.agents[speaker].inbox.append(msg)
# objective._reset_call_counter()

def step(self):
ts = dt.now()
Expand All @@ -52,7 +56,7 @@ def step(self):
delayed_messages = []
for delay, msg in self.messages:
if delay <= 1:
self.agents[msg[0]].update(msg[1])
self.agents[msg['receiver']].inbox.append(msg)
else:
delayed_messages.append((delay - 1, msg))
counter += 1
Expand All @@ -63,27 +67,34 @@ def step(self):
self.messages = delayed_messages

# activate agents
for a in self.agents.values():
a.step()
for aid in self.agents:
a = self.agents[aid]
delay = self.agent_delays[aid]
if delay <= 1:
a.step()
self.agent_delays[aid] = self.agentdelay()
else:
self.agent_delays[aid] = delay - 1
counter += 1
if progress is None and (dt.now() - ts).seconds >= 1:
progress = PBar(amount).start()
if progress is not None:
progress.update(counter)
self.current_time += 1


def msgdelay(self):
return self.cfg.rnd.randint(self.cfg.msg_delay_min,
self.cfg.msg_delay_max)

def agentdelay(self):
return self.cfg.rnd.randint(self.cfg.agent_delay_min,
self.cfg.agent_delay_max)

def msg(self, sender, receiver, msg):
def msg(self, msg):
msg_counter()
delay = self.msgdelay()
MSG('a%s --(%d)--> a%s' % (sender, delay, receiver))
self.messages.append((delay, (receiver, msg)))

MSG('a%s --(%d)--> a%s' % (msg['sender'], delay, msg['receiver']))
self.messages.append((delay, msg))

def is_active(self):
# Check maximal runtime
Expand All @@ -105,7 +116,7 @@ def is_active(self):

# Check agent activity
for a in self.agents.values():
if a.dirty:
if len(a.inbox) > 0:
return True

INFO('Stopping (no activity)')
Expand Down
32 changes: 16 additions & 16 deletions src/util.py
Expand Up @@ -126,28 +126,28 @@ def update(self, value=None):


def get_repo_revision():
# import filedbg
# filedbg.push()
from mercurial import hg, ui, commands
ui = ui.ui()
repo_path = get_repo_root()
repo = hg.repository(ui, repo_path)
ui.pushbuffer()
commands.identify(ui, repo, rev='.')
ret = ui.popbuffer().split()[0]
# files = filedbg.pop()
# for f in files:
# f.close()
return ret
repo_path, repo_type = get_repo_root()
if repo_type == 'hg':
from mercurial import hg, ui, commands
ui = ui.ui()
repo = hg.repository(ui, repo_path)
ui.pushbuffer()
commands.identify(ui, repo, rev='.')
return ui.popbuffer().split()[0]
elif repo_type == 'git':
import subprocess
return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()


def get_repo_root():
path = os.path.dirname(os.path.realpath(__file__))
while os.path.exists(path):
if '.hg' in os.listdir(path):
return path
if '.git' in os.listdir(path):
return path, 'git'
elif '.hg' in os.listdir(path):
return path, 'hg'
path = os.path.realpath(os.path.join(path, '..'))
raise RuntimeError('No .hg repository found!')
raise RuntimeError('No git/hg repository found!')


def current_method():
Expand Down

0 comments on commit 95f6a89

Please sign in to comment.