# experiment met evolutionary programming voor de kanalenplanning
# het genetische zit er nu nauwelijks in, en wat er in zit is ongetwijfeld
# fout. wat er wel goed in zit is het parseren van de bestaande situatie en
# het scoren van (nu random) configuraties.
#
# de score functie is nu redelijk simplistisch:
#   - de score van een combinatie van twee interfaces op een node is 1 als er
#     twee of meer kanalen tussen zitten, en -1 als er 1 kanaal tussen zit, -5
#     als er geen kanaal tussen zit en -10 als de kanalen hetzelfde zijn
#   - de score van een node is de som van de scores van alle combinaties van
#     interfaces van die node, maal het aantal interfaces. dat laatste omdat
#     een positieve score hebben knapper is met meer interfaces dan met minder,
#     dus dat moet beloond. een negatieve score is erger met meer interfaces
#     (belangrijker node, waarschijnlijk) dan met minder, dus dat moet
#     bestraft.
#   - de totale score is de som van de scores van de nodes
#
# het stukje "genetisch" aan het einde is afgeraffeld. mutation en
# recombination gebeuren op de oplossing zelf ipv op een bitstring
# representatie. selection is dom en neemt gewoon de helft beste oplossingen.
#
# lvoge@cs.vu.nl
import re
import random
import os

# deze twee zijn nodig voor de evaluatie van een configuratie
nodes = {}		# nodename -> Node
groups = {}		# essid -> WIGroup

population = []

# gegeven een lijst, geef de lijst zonder dubbele elementen
def uniq(l):
	d = {}
	for e in l:
		d[e] = 1
	return d.keys()

class Node:
	def __init__(self, name, wis):
		self.name = name
		self.wis = wis
		self.special = {}
		for wi in wis:
			wi.setNode(self)
	def score(self):
		score = 0
		for i in range(len(self.wis)):
			wi_i = self.wis[i]
			if wi_i.group == None:
				# orphaned interface
				continue
			if wi_i.group.channel == None:
				print "huh?"
			for j in range(len(self.wis)):
				if i != j:
					wi_j = self.wis[j]
					if wi_j.group == None:
						continue
					diff = abs(wi_i.group.channel - wi_j.group.channel)
					if diff > 2:
						score = score + 1
					elif diff == 2:
						score = score - 1
					elif diff == 1:
						score = score - 5
					else:
						score = score - 10
			if self.special.has_key(wi_i.group.channel):
				score = score + self.special[wi_i.group.channel]
		score = score * len(self.wis)
		return score
	def setSpecialScoreForChannels(self, score, channels):
		for c in channels:
			self.special[c] = score

# een supernode is een verzameling subnodes, bv cetim1, cetim2 en cetim3.
class SuperNode(Node):
	def __init__(self, name, nodes):
		self.name = name
		self.nodes = nodes
		self.wis = []
		self.special = {}
		for n in nodes:
			self.wis.extend(n.wis)

class WI:
	def __init__(self, name):
		self.name = name
		self.node = None
		self.group = None
		self.omni = 0
	def setNode(self, node):
		self.node = node
	def setGroup(self, group):
		self.group = group

class WIGroup:
	def __init__(self, name):
		self.name = name
		self.wis = []
		self.channel = None
	def add_wi(self, wi):
		self.wis.append(wi)
		wi.setGroup(self)
	def uniq(self):
		self.wis = uniq(self.wis)

# een configuratie is een bepaalde bedeling van kanalen aan groepen. aangezien
# groepen keyed zijn op essid is de concrete configuratie hier een dict van
# essids naar kanalen.
class Configuration:
	def __init__(self):
		self.conf = None
		self.calculated_score = None
	def copy(self):
		c = Configuration()
		c.conf = self.conf.copy()
		c.calculated_score = None
		return c
	def randomize(self):
		self.conf = {}
		for essid in groups.keys():
			self.conf[essid] = random.randint(1, 13)
		self.calculated_score = None
	def score(self):
		assert len(self.conf) == len(groups)
		if self.calculated_score != None:
			return self.calculated_score
		for essid in groups.keys():
			groups[essid].channel = self.conf[essid]
		score = 0
		for node in nodes.values():
			score = score + node.score()
		self.calculated_score = score
		return score
	def mutate(self, rate):
		for essid in groups.keys():
			if random.random() <= rate:
				self.conf[essid] = random.randint(1, 13)
	def crossover(self, partner, numpoints):
		# pick the crossover points
		essids = groups.keys()
		points = [random.randint(0, len(essids) - 1) for i in range(numpoints)]
		points.sort()
		conf = {}
		who = 0
		lastpoint = 0
		while len(points):
			point = points.pop(0)
			if who == 0:
				d = self.conf
			else:
				d = partner.conf
			who = 1 - who
			for i in range(lastpoint, point + 1):
				conf[essids[i]] = d[essids[i]]
			lastpoint = point
		if who == 0:
			d = self.conf
		else:
			d = partner.conf
		for i in range(lastpoint, len(essids)):
			conf[essids[i]] = d[essids[i]]
		assert len(conf) == len(groups)
		self.conf = conf


# BEGIN UGLY PARSING CODE

to_resolve = []		# list of (essid, WI) tuples

def parse_wleiden_conf(lines):
	essid = None
	wi = None
	wis = []
	for l in lines:
		if wi != None:
			match = re.match("^MODE=master.*", l)
			if match != None:
				master = 1
			match = re.match("^ESSID=(.*)", l)
			if match != None:
				essid = match.group(1)
			match = re.match("^EW[0-9]*", l)
			if match != None:
				if master == 1:
					group = WIGroup(essid)
					groups[essid] = group
					group.add_wi(wi)
				else:
					# defer, may come way later
					to_resolve.append( (essid, wi) )
				master = 0
				wi = None
				essid = None
		else:
			match = re.match("^\$nodename='([^']*)';.*", l)
			if match != None:
				nodename = match.group(1).lower()
			match = re.match("^\$config{'wi[0-9]*:[0-9].*", l)
			if match != None:
				continue
			match = re.match("^\$config{'(wi[0-9]*).*", l)
			if match != None:
				wi = WI(match.group(1))
				wis.append(wi)
				master = 0
	node = Node(nodename, wis)
	nodes[nodename] = node

# gegeven een file met filenames van wleiden.conf's, parseer ze allemaal en
# vul de datastructuren
def parse_metafile(filename):
	for fname in open(filename).readlines():
		parse_wleiden_conf(open(fname[:-1]).readlines())
	for essid, wi in to_resolve:
		if not groups.has_key(essid):
			print "Warning: node %s, %s refers to unknown essid %s" % (wi.node.name, wi.name, essid)
			continue
		groups[essid].add_wi(wi)
	for group in groups.values():
		group.uniq()

def parse_coupled(filename):
	try:
		for line in open(filename).readlines():
			supername, rest = line[:-1].split(':')
			subnodes = []
			for nodename in rest.split(' '):
				if nodename == '':
					continue
				node = nodes[nodename]
				del nodes[nodename]
				subnodes.append(node)
			node = SuperNode(supername, subnodes)
			nodes[supername] = node
	except:
		pass

def parse_special(filename):
	for line in open(filename).readlines():
		fields = line[:-1].split(' ')
		nodename = fields[0]
		score = int(fields[1])
		channels = map(lambda c: int(c), fields[2:])
		nodes[nodename].setSpecialScoreForChannels(score, channels)

# END UGLY PARSING CODE

def plot_configuration(conf, out):
	out.write("digraph plot {\n")
	for essid in groups:
		out.write("\"%s\" [shape=box label=\"%s\\n(%d)\"]\n" % (essid, essid, conf[essid]))
	for nodename in nodes.keys():
		for wi in nodes[nodename].wis:
			if wi.group == None:
				continue
			out.write("\"%s\" -> \"%s\"\n" % (nodename, wi.group.name))
	out.write("}")

parse_metafile('l')
parse_coupled('coupled.conf')
parse_special('special.conf')

for essid in groups.keys():
	print essid, map(lambda wi: wi.node.name, groups[essid].wis)

for i in range(20):
	conf = Configuration()
	conf.randomize()
	population.append(conf)

last_high_score = -10000000
iterations_since_new_high_score = 0
while iterations_since_new_high_score < 1000:
	for i in range(0, 9):
		p = population[i].copy()
		#population[i].crossover(population[i + 10], random.randint(1, 4) * 2)
		population[i + 10] = p
		population[i + 10].mutate(0.05)
	population.sort(lambda a, b: cmp(b.score(), a.score()))
	high_score = population[0].score()
	if high_score > last_high_score:
		last_high_score = high_score
		iterations_since_new_high_score = 0
	iterations_since_new_high_score = iterations_since_new_high_score + 1
	print high_score
plot_configuration(population[0].conf, open("foo.dot", 'w'))
os.system('neato -Gstart=foo -Goverlap=false/scale -Gsplines=true -Gsep=2 -Gratio=fill -Gnslimit=50.0 -Grotate=90 -Gsize="11,7.5" -Tps -o channelga.ps foo.dot')

