![]() |
toulbar2An exact solver for cost function networks |
The problem consists in assigning N queens on a NxN chessboard with random weights in (1..N) associated to every cell such that each queen does not attack another queen and the sum of the weights of queen's selected cells is minimized.
A solution must have only one queen per column and per row. We create N variables for every column with domain size N to represent the selected row for each queen. A clique of binary constraints is used to express that two queens cannot be on the same row. Forbidden assignments have cost k=N**2+1. Two other cliques of binary constraints are used to express that two queens do not attack each other on a lower/upper diagonal.
{
problem: { "name": "4-queen", "mustbe": "<17" },
variables: {"Q0":["Row0", "Row1", "Row2", "Row3"], "Q1":["Row0", "Row1", "Row2", "Row3"],
"Q2":["Row0", "Row1", "Row2", "Row3"], "Q3":["Row0", "Row1", "Row2", "Row3"]},
functions: {
{scope: ["Q0", "Q1"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q0", "Q2"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q0", "Q3"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q1", "Q2"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q1", "Q3"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q2", "Q3"], "costs": [17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17]},
{scope: ["Q0", "Q1"], "costs": [0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0]},
{scope: ["Q0", "Q2"], "costs": [0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0]},
{scope: ["Q0", "Q3"], "costs": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0]},
{scope: ["Q1", "Q2"], "costs": [0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0]},
{scope: ["Q1", "Q3"], "costs": [0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0]},
{scope: ["Q2", "Q3"], "costs": [0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0]},
{scope: ["Q0", "Q1"], "costs": [0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0]},
{scope: ["Q0", "Q2"], "costs": [0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0]},
{scope: ["Q0", "Q3"], "costs": [0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]},
{scope: ["Q1", "Q2"], "costs": [0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0]},
{scope: ["Q1", "Q3"], "costs": [0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0]},
{scope: ["Q2", "Q3"], "costs": [0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0, 17, 0, 0, 0, 0]},
{scope: ["Q0"], "costs": [4, 4, 3, 4]},
{scope: ["Q1"], "costs": [4, 3, 4, 4]},
{scope: ["Q2"], "costs": [2, 1, 3, 2]},
{scope: ["Q3"], "costs": [1, 2, 3, 4]}
}
The following code using python3 interpreter will generate the previous example if called without argument. Otherwise the first argument is the number of queens N (e.g. "python3 queens.py 8").
Notice that the first lines of code (import and functions flatten and cfn) are needed by all the other tutorial examples.
import sys
from random import randint, seed
seed(123456789)
def flatten(x):
result = []
for el in x:
if hasattr(el, "__iter__") and not isinstance(el, str) and not isinstance(el, tuple) and not isinstance(el, dict):
result.extend(flatten(el))
else:
result.append(el)
return result
def cfn(problem, isMinimization, initPrimalBound, floatPrecision=0):
globals_key_order = ["metric", "cost", "bounds", "vars1", "vars2", "nb_states", "starts", "ends", "transitions", "nb_symbols", "nb_values", "start", "terminals", "non_terminals", "min", "max", "values", "defaultcost", "tuples", "comparator", "to"]
print('{')
print('\tproblem: { "name": "%s", "mustbe": "%s%.*f" },' % (problem["name"], "<" if (isMinimization) else ">", floatPrecision, initPrimalBound))
print('\tvariables: {', end='')
for i,e in enumerate(flatten(problem["variables"])):
if i > 0: print(', ', end='')
print('"%s":' % e[0], end='')
if isinstance(e[1], int):
print(' %s' % e[1], end='')
else:
print('[', end='')
for j,a in enumerate(e[1]):
if j > 0: print(', ', end='')
print('"%s"' % a, end='')
print(']', end='')
print('},')
print( '\tfunctions: {')
for i,e in enumerate(flatten(problem["functions"])):
if i > 0: print(',')
if e.get("name") is not None: print('\t\t"%s": {scope: [' % e.get("name"), end='')
else: print('\t\t{scope: [', end='')
scope = {}
for j,x in enumerate(e.get("scope")):
if (x in scope): sys.exit(str(e) + '\nError: scope of function ' + str(i) + ' with the same variable twice is forbidden!')
if j > 0: print(', ', end='')
print('"%s"' % x, end='')
scope[x]=j
print('], ', end='')
if e.get("type") is not None:
print('"type:" %s, ' % e.get("type"), end='')
if e.get("params") is not None:
if isinstance(e.get("params"), dict):
print('"params": {', end='')
first = True
for key in globals_key_order:
if key in e.get("params"):
if not first: print(', ', end='')
if isinstance(e.get("params")[key], str): print('"%s": "%s"' % (str(key),str(e.get("params")[key]).replace("'", '"')), end='')
else: print('"%s": %s' % (str(key),str(e.get("params")[key]).replace("'", '"')), end='')
first = False
print ('}', end='')
else: print('"params": %s, ' % str(e.get("params")).replace("'",'"'), end='')
if e.get("defaultcost") is not None:
print('"defaultcost:" %s, ' % e.get("defaultcost"), end='')
if e.get("costs") is not None:
print('"costs": ', end='')
if isinstance(e.get("costs"), str):
print('"%s"' % e.get("costs"), end='') # reuse future cost function by giving its name here
else:
print('[', end='')
for j,c in enumerate(e.get("costs")):
if j > 0: print(', ', end='')
if isinstance(c, str) and not c.isdigit():
print('"%s"' % c, end='')
else:
print('%s' % c, end='')
print(']', end='')
print('}', end='')
print('}\n}')
def model(N, k):
Var = ["Q" + str(i) for i in range(N)]
Queen = {
"name": str(N) + "-queen",
"variables": [(Var[i], ["Row" + str(j) for j in range(N)]) for i in range(N)],
"functions":
[
# permutation constraints expressed by a clique of binary constraints
[{"scope": [Var[i], Var[j]], "costs": [0 if a != b else k for a in range(N) for b in range(N)]} for i in range(N) for j in range(N) if (i < j)],
# upper diagonal constraints
[{"scope": [Var[i], Var[j]], "costs": [0 if a + i != b + j else k for a in range(N) for b in range(N)]} for i in range(N) for j in range(N) if (i < j)],
# lower diagonal constraints
[{"scope": [Var[i], Var[j]], "costs": [0 if a - i != b - j else k for a in range(N) for b in range(N)]} for i in range(N) for j in range(N) if (i < j)],
# random unary costs
[{"scope": [Var[i]], "costs": [randint(1,N) for a in range(N)]} for i in range(N)]
]
}
return Queen
if __name__ == '__main__':
# read parameters
N = int(sys.argv[1]) if len(sys.argv) > 1 else 4
# infinite cost
k = N**2+1
# dump problem into JSON .cfn format for minimization
cfn(model(N, k), True, k)
The problem consists in assigning a value from 0 to N-1 to every cell of a NxN chessboard. Each row and each column must be a permutation of N values. For each cell, a random weight in (1...N) is associated to every domain value. The objective is to find a complete assignment where the sum of the weights associated to the selected values for the cells is minimized.
We create NxN variables for all cells with domain size N. A hard AllDifferent global cost function is used to model a permutation for every row and every column. Random weights are generated for every cell and domain value. Forbidden assignments have cost k=N**3+1.
{
problem: { "name": "LatinSquare4", "mustbe": "<65" },
variables: {"X0_0": 4, "X0_1": 4, "X0_2": 4, "X0_3": 4, "X1_0": 4, "X1_1": 4, "X1_2": 4, "X1_3": 4, "X2_0": 4, "X2_1": 4, "X2_2": 4, "X2_3": 4, "X3_0": 4, "X3_1": 4, "X3_2": 4, "X3_3": 4},
functions: {
{scope: ["X0_0", "X0_1", "X0_2", "X0_3"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X1_0", "X1_1", "X1_2", "X1_3"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X2_0", "X2_1", "X2_2", "X2_3"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X3_0", "X3_1", "X3_2", "X3_3"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X0_0", "X1_0", "X2_0", "X3_0"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X0_1", "X1_1", "X2_1", "X3_1"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X0_2", "X1_2", "X2_2", "X3_2"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X0_3", "X1_3", "X2_3", "X3_3"], "type:" salldiff, "params": {"metric": "var", "cost": 65}},
{scope: ["X0_0"], "costs": [4, 4, 3, 4]},
{scope: ["X0_1"], "costs": [4, 3, 4, 4]},
{scope: ["X0_2"], "costs": [2, 1, 3, 2]},
{scope: ["X0_3"], "costs": [1, 2, 3, 4]},
{scope: ["X1_0"], "costs": [3, 1, 3, 3]},
{scope: ["X1_1"], "costs": [4, 1, 1, 1]},
{scope: ["X1_2"], "costs": [4, 1, 1, 3]},
{scope: ["X1_3"], "costs": [4, 4, 1, 4]},
{scope: ["X2_0"], "costs": [1, 3, 3, 2]},
{scope: ["X2_1"], "costs": [2, 1, 3, 1]},
{scope: ["X2_2"], "costs": [3, 4, 2, 2]},
{scope: ["X2_3"], "costs": [2, 3, 1, 3]},
{scope: ["X3_0"], "costs": [3, 4, 4, 2]},
{scope: ["X3_1"], "costs": [3, 2, 4, 4]},
{scope: ["X3_2"], "costs": [4, 1, 3, 4]},
{scope: ["X3_3"], "costs": [4, 4, 4, 3]}
}
The following code using python3 interpreter will generate the previous example if called without argument. Otherwise the first argument is the dimension N of the chessboard (e.g. "python3 latinsquare.py 6").
def model(N, k):
Var = {(i,j): "X" + str(i) + "_" + str(j) for i in range(N) for j in range(N)}
LatinSquare = {
"name": "LatinSquare" + str(N),
"variables": [(Var[(i,j)], N) for i in range(N) for j in range(N)],
"functions":
[# permutation constraints on rows
[{"scope": [Var[(i,j)] for j in range(N)], "type": "salldiff", "params": {"metric": "var", "cost": k}} for i in range(N)],
# permutation constraints on columns
[{"scope": [Var[(i,j)] for i in range(N)], "type": "salldiff", "params": {"metric": "var", "cost": k}} for j in range(N)],
# random unary costs on every cell
[{"scope": [Var[(i,j)]], "costs": [randint(1, N) for a in range(N)]} for i in range(N) for j in range(N)]
]
}
return LatinSquare
if __name__ == '__main__':
# read parameters
N = int(sys.argv[1]) if len(sys.argv) > 1 else 4
# infinite cost
k = N**3+1
# dump problem into JSON .cfn format for minimization
cfn(model(N, k), True, k)
The problem consists in assigning frequencies to radio communication links in such a way that no interferences occurs. Domains are set of integers (non necessarily consecutives). Two types of constraints occur: (I) the absolute difference between two frequencies should be greater than a given number d_i (|x-y|>d_i), or (II) the absolute difference between two frequencies should exactly be equal to a given number d_i (|x-y|=d_i). Different deviations d_i, i in 0..4, may exist for the same pair of links. d_0 corresponds to hard constraints while higher deviations are soft constraints that can be violated with an associated cost a_i. Moreover, pre-assigned frequencies may be known for some links which are either hard or soft preferences (mobility cost b_i, i in 0..4). The goal is to minimize the weighted sum of violated constraints. Cabon, B., de Givry, S., Lobjois, L., Schiex, T., Warners, J.P. Constraints (1999) 4: 79.
We create N variables for every radio link with a given integer domain. Hard and soft binary cost functions express interference constraints with possible deviations. Unary cost functions are used to model mobility costs.
Original data files can be download from the cost function library FullRLFAP. Their format is described here. You can try a small example CELAR6-SUB1 (var.txt, dom.txt, ctr.txt, cst.txt) with optimum value equal to 2669.
The following code using python3 interpreter will generate the corresponding cost function network (e.g. "python3 rlfap.py var.txt dom.txt ctr.txt cst.txt").
class Data:
def __init__(self, var, dom, ctr, cst):
self.var = list()
self.dom = {}
self.ctr = list()
self.cost = {}
self.nba = {}
self.nbb = {}
self.top = 0
stream = open(var)
for line in stream:
if len(line.split())>=4:
(varnum, vardom, value, mobility) = line.split()[:4]
self.var.append((int(varnum), int(vardom), int(value), int(mobility)))
self.nbb["b" + str(mobility)] = self.nbb.get("b" + str(mobility), 0) + 1
else:
(varnum, vardom) = line.split()[:2]
self.var.append((int(varnum), int(vardom)))
stream = open(dom)
for line in stream:
domain = line.split()[:]
self.dom[int(domain[0])] = [int(f) for f in domain[2:]]
stream = open(ctr)
for line in stream:
(var1, var2, dummy, operand, deviation, weight) = line.split()[:6]
self.ctr.append((int(var1), int(var2), operand, int(deviation), int(weight)))
self.nba["a" + str(weight)] = self.nba.get("a" + str(weight), 0) + 1
stream = open(cst)
for line in stream:
if len(line.split()) == 3:
(aorbi, eq, cost) = line.split()[:3]
if (eq == "="):
self.cost[aorbi] = int(cost)
self.top += int(cost) * self.nba.get(aorbi, self.nbb.get(aorbi, 0))
def model(data):
Var = {e[0]: "X" + str(e[0]) for e in data.var}
Domain = {e[0]: e[1] for e in data.var}
RLFAP = {
"name": "RLFAP",
"variables": [(Var[e[0]], ["f" + str(f) for f in data.dom[e[1]]]) for e in data.var],
"functions":
[# hard and soft interference
[{"scope": [Var[var1], Var[var2]], "costs": [0 if ((operand==">" and abs(a - b)>deviation) or (operand=="=" and abs(a - b)==deviation))
else data.cost.get("a"+str(weight),data.top)
for a in data.dom[Domain[var1]] for b in data.dom[Domain[var2]]]}
for (var1, var2, operand, deviation, weight) in data.ctr],
# mobility costs
[{"scope": [Var[e[0]]], "defaultcost": data.cost.get("b"+str(e[3]),data.top), "costs": ["f" + str(e[2]), 0] if e[2] in data.dom[e[1]] else []}
for e in data.var if len(e)==4]
]
}
return RLFAP
if __name__ == '__main__':
# read parameters
if len(sys.argv) < 5: exit('Command line arguments are filenames: var.txt dom.txt ctr.txt cst.txt')
data = Data(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
# dump problem into JSON .cfn format for minimization
cfn(model(data), True, data.top)
The previously-described radio link frequency assignment problem has been extended to take into account polarization constraints and user-defined relaxation of electromagnetic compatibility constraints. The problem is to assign a pair (frequency,polarization) to every radio communication link (also called a path). Frequencies are integer values taken in finite domains. Polarizations are in {-1,1}. Constraints are:
Constraints (I) to (III) are mandatory constraints, while constraints (IV) can be relaxed. The goal is to find a feasible assignment with the smallest relaxation level l and which minimizes the number of violations of (IV) at lower levels. See ROADEF Challenge 2001.
In order to benefit from soft local consistencies on binary cost functions, we create a single variable to represent a pair (frequency,polarization) for every radio link.
Original data files can be download from ROADEF or fapp. Their format is described here. You can try a small example exemple1.in (resp. exemple2.in) with optimum 523 at relaxation level 3 with 1 violation at level 2 and 3 below (resp. 13871 at level 7 with 1 violation at level 6 and 11 below). See ROADEF Challenge 2001 results.
The following code using python3 interpreter will generate the corresponding cost function network (e.g. "python3 fapp.py exemple1.in 3"). You can also compile fappeval.c using "gcc -o fappeval fappeval.c" and download sol2fapp.awk in order to evaluate the solutions (e.g., "python3 fapp.py exemple1.in 3 | toulbar2 --stdin=cfn -s=3 | awk -f ./sol2fapp.awk - exemple1").
class Data:
def __init__(self, filename, k):
self.var = list()
self.dom = {}
self.ctr = list()
self.softeq = list()
self.softne = list()
self.nbsoft = 0
self.top = 1
self.cst = 0
stream = open(filename)
for line in stream:
if len(line.split())==3 and line.split()[0]=="DM":
(DM, dom, freq) = line.split()[:3]
if self.dom.get(int(dom)) is None:
self.dom[int(dom)] = [int(freq)]
else:
self.dom[int(dom)].append(int(freq))
if len(line.split()) == 4 and line.split()[0]=="TR":
(TR, route, dom, polarisation) = line.split()[:4]
if int(polarisation) is 0:
self.var.append((int(route), [(f,0) for f in self.dom[int(dom)]] + [(f,1) for f in self.dom[int(dom)]]))
if int(polarisation) is -1:
self.var.append((int(route), [(f,0) for f in self.dom[int(dom)]]))
if int(polarisation) is 1:
self.var.append((int(route), [(f,1) for f in self.dom[int(dom)]]))
if len(line.split())==6 and line.split()[0]=="CI":
(CI, route1, route2, vartype, operator, deviation) = line.split()[:6]
self.ctr.append((int(route1), int(route2), vartype, operator, int(deviation)))
if len(line.split())==14 and line.split()[0]=="CE":
(CE, route1, route2, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) = line.split()[:14]
self.softeq.append((int(route1), int(route2), [int(s) for s in [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10]]))
self.nbsoft += 1
if len(line.split())==14 and line.split()[0]=="CD":
(CD, route1, route2, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10) = line.split()[:14]
self.softne.append((int(route1), int(route2), [int(s) for s in [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10]]))
self.cst = 10*k*self.nbsoft**2
self.top += self.cst
self.top += 10*self.nbsoft**2
def model(data, k):
Var = {e[0]: "X" + str(e[0]) for e in data.var}
Domain = {e[0]: e[1] for e in data.var}
FAPP = {
"name": "FAPP",
"variables": [(Var[e[0]], ["f" + str(f) + "p" + str(p) for (f,p) in e[1]]) for e in data.var],
"functions":
[# hard constraints (I) to (III)
[{"scope": [Var[route1], Var[route2]], "costs": [0 if ((operand=="I" and abs((f1 if vartype=="F" else p1) - (f2 if vartype=="F" else p2)) != deviation)
or (operand=="E" and abs((f1 if vartype=="F" else p1) - (f2 if vartype=="F" else p2)) == deviation))
else data.top
for (f1,p1) in Domain[route1] for (f2,p2) in Domain[route2]]}
for (route1, route2, vartype, operand, deviation) in data.ctr],
# soft equality constraints (IV)
[{"scope": [Var[route1], Var[route2]], "costs": [0 if p1!=p2 or abs(f1 - f2) >= deviations[i]
else (data.top if i>=k
else (1 if i < k-1
else 10*data.nbsoft))
for (f1, p1) in Domain[route1] for (f2, p2) in Domain[route2]]}
for i in range(11) for (route1, route2, deviations) in data.softeq],
# soft inequality constraints (IV)
[{"scope": [Var[route1], Var[route2]], "costs": [0 if p1==p2 or abs(f1 - f2) >= deviations[i]
else (data.top if i>=k
else (1 if i < k-1
else 10*data.nbsoft))
for (f1, p1) in Domain[route1] for (f2, p2) in Domain[route2]]}
for i in range(11) for (route1, route2, deviations) in data.softne],
# constant cost to be added corresponding to the relaxation level
{"scope": [], "defaultcost": data.cst, "costs": []}
]
}
return FAPP
if __name__ == '__main__':
# read parameters
if len(sys.argv) < 2: exit('Command line argument is problem-data-filename and relaxation-level')
k = int(sys.argv[2])
data = Data(sys.argv[1], k)
# dump problem into JSON .cfn format for minimization
cfn(model(data, k), True, data.top)
The problem is to detect marker genotyping incompatibilities (Mendelian errors only) in complex pedigrees. The input is a pedigree data with partial observed genotyping data at a single locus. The problem is to assign genotypes (unordered pairs of alleles) to all individuals such that they are compatible with the Mendelian law of heredity and with the maximum number of genotyping data. Sanchez, M., de Givry, S. and Schiex, T. Constraints (2008) 13:130.
We create N variables for every individual genotype with domain being all possible unordered pairs of existing alleles. Hard ternary cost functions express mendelian law of heredity. Unary cost functions are used to model potential genotyping errors.
Original data files can be download from the cost function library pedigree. Their format is described here. You can try a small example simple.pre (simple.pre) with optimum value equal to 1.
The following code using python3 interpreter will generate the corresponding cost function network (e.g. "python3 mendel.py simple.pre").
class Data:
def __init__(self, ped):
self.id = list()
self.father = {}
self.mother = {}
self.alleles = {}
self.freq = {}
self.obs = 0
stream = open(ped)
for line in stream:
(locus, id, father, mother, sex, allele1, allele2) = line.split()[:]
self.id.append(int(id))
self.father[int(id)] = int(father)
self.mother[int(id)] = int(mother)
self.alleles[int(id)] = (int(allele1), int(allele2)) if int(allele1) < int(allele2) else (int(allele2), int(allele1))
if int(allele1) != 0: self.freq[int(allele1)] = self.freq.get(int(allele1), 0) + 1
if int(allele2) != 0: self.freq[int(allele2)] = self.freq.get(int(allele2), 0) + 1
if int(allele1) != 0 or int(allele2) != 0: self.obs += 1
def model(data, k):
Var = {g: "g" + str(g) for g in data.id}
Domain = ["a" + str(a1) + "a" + str(a2) for a1 in data.freq for a2 in data.freq if a1 <= a2]
Mendel = {
"name": "Mendel",
"variables": [(Var[g], Domain) for g in data.id],
"functions":
[# mendelian law of heredity
[{"scope": [Var[data.father[g]], Var[data.mother[g]], Var[g]],
"costs": [0 if (a1 in (p1,p2) and a2 in (m1,m2)) or (a2 in (p1,p2) and a1 in (m1,m2)) else k
for p1 in data.freq for p2 in data.freq
for m1 in data.freq for m2 in data.freq
for a1 in data.freq for a2 in data.freq if p1 <= p2 and m1 <= m2 and a1 <= a2]}
for g in data.id if data.father.get(g, 0) != 0 and data.mother.get(g, 0) != 0],
# observation costs
[{"scope": [Var[g]],
"costs": [0 if (a1,a2) == data.alleles[g] else 1 for a1 in data.freq for a2 in data.freq if a1 <= a2]}
for g in data.id if data.alleles[g][0] != 0 and data.alleles[g][1] != 0]
]
}
return Mendel
if __name__ == '__main__':
# read parameters
if len(sys.argv) < 2: exit('Command line arguments are PEDFILE filename: simple.pre')
data = Data(sys.argv[1])
# dump problem into JSON .cfn format for minimization
cfn(model(data, data.obs + 1), True, data.obs + 1)
This is a clustering problem, occuring in social network analysis. The problem is to divide a given graph G into k clusters such that the interactions between clusters can be summarized by a k*k 0/1 matrix M: if M[i,j]=1 then all the nodes in cluster i should be connected to all the nodes in cluster j in G, else if M[i,j]=0 then there should be no edge between the nodes in G. The goal is to find a k-clustering and the associated matrix M minimizing the number of erroneous edges. A Mattenet, I Davidson, S Nijssen, P Schaus. Generic Constraint-Based Block Modeling Using Constraint Programming. CP 2019, pp656-673, Stamford, CT, USA.
We create N variables for every node of the graph with domain size k. We add k*k Boolean variables for representing M. For all triplets of two nodes u, v, and one matrix cell M[i,j], we have a ternary cost function which returns a cost of 1 if node u is assigned to cluster i, v to j, and M[i,j]=1 but (u,v) is not in G, or M[i,j]=0 and (u,v) in G. In order to break symmetries, we constrain the first k-1 node variables to be assigned to cluster index less than or equal to their index
You can try a small example simple.mat (simple.mat) with optimum value equal to 0 for 3 clusters. See other examples (*.mat) such as PoliticalActor in TUTORIALS.
The following code using python3 interpreter will generate the corresponding cost function network (e.g. "python3 simple.mat 3").
class Data:
def __init__(self, filename, k):
lines = open(filename).readlines()
self.n = len(lines)
self.matrix = [[int(e) for e in l.split(' ')] for l in lines]
self.top = 1 + self.n*self.n
def model(data, K):
Var = [(chr(65 + i) if data.n < 28 else "x" + str(i)) for i in range(data.n)] # Political actor or any instance
BlockModeling = {
"name": "BlockModel_N" + str(data.n) + "_K" + str(K),
"variables": [[("M_" + str(u) + "_" + str(v), 2) for u in range(K) for v in range(K)],
[(Var[i], K) for i in range(data.n)]],
"functions":
[
# objective function
[{"scope": ["M_" + str(u) + "_" + str(v), Var[i], Var[j]],
"costs": [1 if (u == k and v == l and data.matrix[i][j] != m)
else 0
for m in range(2)
for k in range(K)
for l in range(K)]}
for u in range(K) for v in range(K) for i in range(data.n) for j in range(data.n) if i != j],
# self-loops
[{"scope": ["M_" + str(u) + "_" + str(u), Var[i]],
"costs": [1 if (u == k and data.matrix[i][i] != m)
else 0
for m in range(2)
for k in range(K)]}
for u in range(K) for i in range(data.n)],
# breaking partial symmetries by fixing first (K-1) domain variables to be assigned to cluster less than or equal to their index
[{"scope": [Var[l]],
"costs": [data.top if k > l else 0 for k in range(K)]}
for l in range(K-1)]
]
}
return BlockModeling
if __name__ == '__main__':
# read parameters
if len(sys.argv) < 2: exit('Command line argument is problem data filename and number of blocks')
K = int(sys.argv[2])
data = Data(sys.argv[1], K)
# dump problem into JSON .cfn format for minimization
cfn(model(data, K), True, data.top)
We improve the previous model by sorting node variables by decreasing out degree and removing the lower triangular matrix of M if the input graph is symmetric.
def model(data, K):
symmetric = all([data.matrix[i][j] == data.matrix[j][i] for i in range(data.n) for j in range(data.n) if j>i])
Var = [(chr(65 + i) if data.n < 28 else "x" + str(i)) for i in range(data.n)]
# sort node variables by decreasing out degree
degree = [(i, sum(data.matrix[i])) for i in range(data.n)]
degree.sort(key=lambda tup: -tup[1])
indexes = [e[0] for e in degree]
BlockModeling = {
"name": "BlockModel_N" + str(data.n) + "_K" + str(K) + "_Sym" + str(symmetric),
# order node variables before matrix M variables
# order matrix M variables starting from the main diagonal and moving away progressively
# if input graph is symmetric then keep only the upper triangular matrix of M
"variables": [[("M_" + str(u) + "_" + str(u), 2) for u in range(K)],
[("M_" + str(u) + "_" + str(v), 2) for d in range(K) for u in range(K) for v in range(K)
if u != v and (not symmetric or u < v) and abs(u - v) == d],
[(Var[indexes[i]], K) for i in range(data.n)]],
"functions":
[
# objective function
# if input graph is symmetric then cost tables are also symmetric wrt node variables
[{"scope": ["M_" + str(u) + "_" + str(v), Var[indexes[i]], Var[indexes[j]]],
"costs": [1 if (((u == k and v == l) or (symmetric and u == l and v == k))
and data.matrix[indexes[i]][indexes[j]] != m)
else 0
for m in range(2)
for k in range(K)
for l in range(K)]}
for u in range(K) for v in range(K) for i in range(data.n) for j in range(data.n)
if i != j and (not symmetric or u <= v)],
# self-loops
[{"scope": ["M_" + str(u) + "_" + str(u), Var[indexes[i]]],
"costs": [1 if (u == k and data.matrix[indexes[i]][indexes[i]] != m)
else 0
for m in range(2)
for k in range(K)]}
for u in range(K) for i in range(data.n)],
# breaking partial symmetries by fixing first (K-1) domain variables to be assigned to cluster less than or equal to their index
[{"scope": [Var[indexes[l]]],
"costs": [data.top if k > l else 0 for k in range(K)]}
for l in range(K-1)]
]
}
return BlockModeling
Given a set of planes and runways, the objective is to minimize the total weighted deviation from the target landing time for each plane. We consider only a single runway. There are costs associated with landing either earlier or later than a target landing time for each plane. Each plane has to land within its predetermined time window such that separation times between all pairs of planes are satisfied. J.E. Beasley, M. Krishnamoorthy, Y.M. Sharaiha and D. Abramson. Scheduling aircraft landings - the static case. Transportation Science, vol.34, 2000.
We create N variables for every plane landing time. Binary cost functions express separation times between pairs of planes. Unary cost functions represent the weighted deviation for each plane.
Original data files can be download from the cost function library airland. Their format is described here. You can try a small example airland1.txt with optimum value equal to 700.
The following code uses the python3 module of toulbar2 and its CFN.py interface to generate the cost function network and solve it (e.g. "python3 airland.py airland1.txt"). Compile toulbar2 with "cmake -DPYTB2=ON . ; make" an copy the module in your current directory "cp lib/Linux/pytoulbar2.cpython* ."
import sys
import CFN
f = open(sys.argv[1], 'r').readlines()
tokens = []
for l in f:
tokens += l.split()
pos = 0
def token():
global pos, tokens
if (pos == len(tokens)):
return None
s = tokens[pos]
pos += 1
return int(float(s))
N = token()
token() # skip freeze time
LT = []
PC = []
ST = []
for i in range(N):
token() # skip appearance time
# Times per plane: {earliest landing time, target landing time, latest landing time}
LT.append([token(), token(), token()])
# Penalty cost per unit of time per plane:
# [for landing before target, after target]
PC.append([token(), token()])
# Separation time required after i lands before j can land
ST.append([token() for j in range(N)])
top = 99999
Problem = CFN.CFN(top)
for i in range(N):
Problem.AddVariable('x' + str(i), range(LT[i][0],LT[i][2]+1))
for i in range(N):
Problem.AddFunction([i], [PC[i][0]*abs(a-LT[i][1]) for a in range(LT[i][0], LT[i][2]+1)])
for i in range(N):
for j in range(i+1,N):
Problem.AddFunction([i, j], [top*(a+ST[i][j]>b and b+ST[j][i]>a) for a in range(LT[i][0], LT[i][2]+1) for b in range(LT[j][0], LT[j][2]+1)])
Problem.Dump('airplane.cfn')
Problem.NoPreprocessing()
Problem.Solve()