# UtilsNConstants.py
# Author: Juliette Zerick (jnzerick@math.ucdavis.edu)
# EAD 221 + PHY 250 (Spring 2009)
# Purpose: This is a collection of handy, generic utilities and global 
#	constants used throughout the simulation. The constants could be
#	defined elsewhere, but I found it more convenient to store them in
#	a single file; that way, I could keep this file up like a control
#	panel while testing the algorithm. 

import random
import math

# CONSTANTS AND OTHER PARAMETERS

NUM_GENERATIONS = 75 # maximum number of generations
DIMS = 2 # number of dimensions in Euclidean space that the Ag/Ab live in

NUM_ANTIBODIES = 15 # fixed number of Antibodies

PRECISION = 10 # number of bits used to represent one coordinate of an Antibody.
CHROM_LENGTH = DIMS*PRECISION # total chromosome length

AB_CLOUD_RAD = 0.75 # radius of an Antibody's "cloud" of (situational) awareness

NUM_ANTIGENS_PER_CLUMP = 10 # number of Antigens per cluster
CLUMP_RADIUS = 0.3 # radius of each cluster
NUM_AG_CLUMPS = 25 # number of Antigen clusters
NUM_ANTIGENS = NUM_ANTIGENS_PER_CLUMP * NUM_AG_CLUMPS # total number of Antigens

# for mapping coordinates
ALPHA = 6.0 # map is [0,ALPHA]^DIMS
SCALING = ALPHA/(2**PRECISION - 1.0) # scaling factor for mapping points

SIGMA_S = 0.25 # minimum distance between Antibodies
SIGMA_D = 25 # minimum affinity required to an Antibody to be 
		# selected for reproduction/survival

H_FRAC = 0.2 # fraction of high-affinity Antibodies selected for C
H_CULL = int(math.floor(H_FRAC*NUM_ANTIBODIES)) # number of Antibodies to select for C

# utility function for copying a list
def copylist(L):
	M = []
	for i in L:
		M.append(i)

	return M

# converts a string of numbers into a list
def istr2ilist(S):
	L = []
	for s in S:
		L.append(int(s))

	return L

# converts a list to a string
def list2str(L,delim):
	return reduce((lambda x,y: str(x)+delim+str(y)),L)

# converts a binary list into an integer
def blist2int(L):
	s = list2str(L,"")
	return int(s,2)

# checks if list is zero
def listis0(L):
	s = sum(L)
	return s == 0

# gets unique entries from L and returns it as a new list M
def get_uniques(L):
	M = []

	# for each element in the list...
	for i in xrange(len(L)):
		curr = L[i]
		numoccur = 0

		# count the duplicates
		for j in xrange(len(M)):
			if curr == M[j]:
				numoccur += 1

		# and remove duplicates
		if numoccur == 0:
			M.append(curr)

	return M

# dump a list of elements to a file--fh is assumed to be open and
# ready for writing; write the delimiter between each element
def list2file(fh,L,delim):
	for i in xrange(len(L)):
		fh.write(str(L[i]))
		
		if i < len(L)-1:
			fh.write(delim)

# generate binary string of length L
def gen_binstring(L):
	B = []

	for i in xrange(L):
		B.append(random.randint(0,1))

	return B

# generate a NONZERO binary string
def gen_nonzero_binstring(L):
	B = []

	# 'til a nonzero string is generated...
	while listis0(B):
		del B
		B = []

		# keep generating bit strings
		for i in xrange(L):
			B.append(random.randint(0,1))

	return B

# chops up a list into chunk_size pieces
def choplist(L,chunk_size):
	num_chunks = len(L) / chunk_size

	subL = L

	chunkedL = []

	# Python is awesome
	for k in xrange(num_chunks):
		chunk = subL[:chunk_size]
		chunkedL.append(chunk)
		subL = subL[chunk_size:]

	return chunkedL

# Euclidean distance metric; len(v1) = len(v2) assumed
def dist_metric(v1,v2):
	L = len(v1)

	s = 0

	for i in xrange(L):
		s += (v1[i]-v2[i])**2

	return math.sqrt(s)

# scales a number i by the scaling factor
def scale(i):
	return i*SCALING

# fourth-order Runge-Kutta DE solver
def RK4(x0,dfdx,h):
	k1 = dfdx*x0*h
	k2 = dfdx*(x0 + 0.5*k1)*h
	k3 = dfdx*(x0 + 0.5*k2)*h
	k4 = dfdx*(x0 + k3)*h

	x1 = x0 + (1.0/6)*(k1 + 2*k2 + 2*k3 + k4)

	return x1

# map number from the unit interval to [a,b] for a <= b
def mapUI2ab(x,a,b):
	return (b-a)*x + a

# a coin toss function--returns 0 or 1
def cointoss():
	return random.randint(0,1)

# an N-sided coin/die toss function--returns anything between 0 and N
def Ncointoss(N):
	return random.randint(0,N)
