def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. The Gale Ryser theorem asserts that if `p_1,p_2` are two partitions of `n` of respective lengths `k_1,k_2`, then there is a binary `k_1\times k_2` matrix `M` such that `p_1` is the vector of row sums and `p_2` is the vector of column sums of `M`, if and only if the conjugate of `p_2` dominates `p_1`. INPUT: - ``p1, p2``-- list of integers representing the vectors of row/column sums - ``algorithm`` -- two possible string values : - ``"ryser"`` implements the construction due to Ryser [Ryser63]_. - ``"gale"`` (default) implements the construction due to Gale [Gale57]_. OUTPUT: - A binary matrix if it exists, ``None`` otherwise. Gale's Algorithm: (Gale [Gale57]_): A matrix satisfying the constraints of its sums can be defined as the solution of the following Linear Program, which Sage knows how to solve. .. MATH:: \forall i&\sum_{j=1}^{k_2} b_{i,j}=p_{1,j}\\ \forall i&\sum_{j=1}^{k_1} b_{j,i}=p_{2,j}\\ &b_{i,j}\mbox{ is a binary variable} Ryser's Algorithm: (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining the `i`-th row of `B` to be the vector whose first `r_i` entries are `1`, and the remainder are 0's, `1\leq i\leq m`. This maximal matrix `B` with row sum `r` and ones left justified has column sum `r^{*}`. * Shift the last `1` in certain rows of `B` to column `n` in order to achieve the sum `s_n`. Call this `B` again. * The `1`'s in column n are to appear in those rows in which `A` has the largest row sums, giving preference to the bottom-most positions in case of ties. * Note: When this step automatically "fixes" other columns, one must skip ahead to the first column index with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. * Set `A = B`. Return `A`. EXAMPLES: Computing the matrix for `p_1=p_2=2+2+1` :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [2,2,1] sage: p2 = [2,2,1] sage: print gale_ryser_theorem(p1, p2) # not tested [1 1 0] [1 0 1] [0 1 0] sage: A = gale_ryser_theorem(p1, p2) sage: rs = [sum(x) for x in A.rows()] sage: cs = [sum(x) for x in A.columns()] sage: p1 == rs; p2 == cs True True Or for a non-square matrix with `p_1=3+3+2+1` and `p_2=3+2+2+1+1`, using Ryser's algorithm :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 0 1] [1 1 1 0] [0 1 0 0] [1 0 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1] [1 1 0 0] [1 1 0 0] sage: p1 = [4,2,2,0] sage: p2 = [3,3,1,1,0,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1 0 0] [1 1 0 0 0 0] [1 1 0 0 0 0] [0 0 0 0 0 0] sage: p1 = [3,3,2,1] sage: p2 = [3,2,2,1,1] sage: print gale_ryser_theorem(p1, p2, algorithm="gale") # not tested [1 1 1 0 0] [1 1 0 0 1] [1 0 1 0 0] [0 0 0 1 0] With `0` in the sequences, and with unordered inputs :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") [1 0 1 1 0] [1 1 1 0 0] [0 0 0 0 0] [0 0 1 0 0] [1 0 0 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] [0 0 1 0] [0 1 0 0] [1 0 0 0] [1 0 0 0] TESTS: This test created a random bipartite graph on `n+m` vertices. Its adjacency matrix is binary, and it is used to create some "random-looking" sequences which correspond to an existing matrix. The ``gale_ryser_theorem`` is then called on these sequences, and the output checked for correction.:: sage: def test_algorithm(algorithm, low = 10, high = 50): ... n,m = randint(low,high), randint(low,high) ... g = graphs.RandomBipartite(n, m, .3) ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) ... if ((ss1 == s1) and (ss2 == s2)): ... return True ... return False sage: for algorithm in ["gale", "ryser"]: # long time ... for i in range(50): # long time ... if not test_algorithm(algorithm, 3, 10): # long time ... print "Something wrong with algorithm ", algorithm # long time ... break # long time Null matrix:: sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="gale") [0 0 0 0] [0 0 0 0] [0 0 0 0] sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="ryser") [0 0 0 0] [0 0 0 0] [0 0 0 0] REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix if not(is_gale_ryser(p1,p2)): return False if algorithm=="ryser": # ryser's algorithm from sage.combinat.permutation import Permutation # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) r = [x[1] for x in tmp if x[1]>0] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) s = [x[1] for x in tmp if x[1]>0] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] n = len(s) A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)]) for k in range(1,n+1): goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))] if sum(A0.column(n-k))<>s[n-k]: A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols) # If we need to add empty rows/columns if len(p1)!=m: A0 = A0.stack(matrix([[0]*n]*(len(p1)-m))) if len(p2)!=n: A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input A0 = A0.matrix_from_rows_and_columns(r_permutation, s_permutation) return A0 elif algorithm == "gale": from sage.numerical.mip import MixedIntegerLinearProgram k1, k2=len(p1), len(p2) p = MixedIntegerLinearProgram() b = p.new_variable(dim=2) for (i,c) in enumerate(p1): p.add_constraint(p.sum([b[i][j] for j in xrange(k2)]),min=c,max=c) for (i,c) in enumerate(p2): p.add_constraint(p.sum([b[j][i] for j in xrange(k1)]),min=c,max=c) p.set_objective(None) p.set_binary(b) p.solve() b = p.get_values(b) M = [[0]*k2 for i in xrange(k1)] for i in xrange(k1): for j in xrange(k2): M[i][j] = int(b[i][j]) return matrix(M) else: raise ValueError("The only two algorithms available are \"gale\" and \"ryser\"")
def binpacking(items, maximum=1, k=None): r""" Solves the bin packing problem. The Bin Packing problem is the following : Given a list of items of weights `p_i` and a real value `K`, what is the least number of bins such that all the items can be put in the bins, while keeping sure that each bin contains a weight of at most `K` ? For more informations : http://en.wikipedia.org/wiki/Bin_packing_problem Two version of this problem are solved by this algorithm : * Is it possible to put the given items in `L` bins ? * What is the assignment of items using the least number of bins with the given list of items ? INPUT: - ``items`` -- A list of real values (the items' weight) - ``maximum`` -- The maximal size of a bin - ``k`` -- Number of bins - When set to an integer value, the function returns a partition of the items into `k` bins if possible, and raises an exception otherwise. - When set to ``None``, the function returns a partition of the items using the least number possible of bins. OUTPUT: A list of lists, each member corresponding to a box and containing the list of the weights inside it. If there is no solution, an exception is raised (this can only happen when ``k`` is specified or if ``maximum`` is less that the size of one item). EXAMPLES: Trying to find the minimum amount of boxes for 5 items of weights `1/5, 1/4, 2/3, 3/4, 5/7`:: sage: from sage.numerical.optimize import binpacking sage: values = [1/5, 1/3, 2/3, 3/4, 5/7] sage: bins = binpacking(values) sage: len(bins) 3 Checking the bins are of correct size :: sage: all([ sum(b)<= 1 for b in bins ]) True Checking every item is in a bin :: sage: b1, b2, b3 = bins sage: all([ (v in b1 or v in b2 or v in b3) for v in values ]) True One way to use only three boxes (which is best possible) is to put `1/5 + 3/4` together in a box, `1/3+2/3` in another, and `5/7` by itself in the third one. Of course, we can also check that there is no solution using only two boxes :: sage: from sage.numerical.optimize import binpacking sage: binpacking([0.2,0.3,0.8,0.9], k=2) Traceback (most recent call last): ... ValueError: This problem has no solution ! """ if max(items) > maximum: raise ValueError("This problem has no solution !") if k == None: from sage.functions.other import ceil k = ceil(sum(items) / maximum) while True: from sage.numerical.mip import MIPSolverException try: return binpacking(items, k=k, maximum=maximum) except MIPSolverException: k = k + 1 from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException p = MixedIntegerLinearProgram() # Boolean variable indicating whether # the i th element belongs to box b box = p.new_variable(dim=2) # Each bin contains at most max for b in range(k): p.add_constraint(p.sum( [items[i] * box[i][b] for i in range(len(items))]), max=maximum) # Each item is assigned exactly one bin for i in range(len(items)): p.add_constraint(p.sum([box[i][b] for b in range(k)]), min=1, max=1) p.set_objective(None) p.set_binary(box) try: p.solve() except MIPSolverException: raise ValueError("This problem has no solution !") box = p.get_values(box) boxes = [[] for i in range(k)] for b in range(k): boxes[b].extend( [items[i] for i in range(len(items)) if round(box[i][b]) == 1]) return boxes
def knapsack(seq, binary=True, max=1, value_only=False): r""" Solves the knapsack problem Knapsack problems: You have already had a knapsack problem, so you should know, but in case you do not, a knapsack problem is what happens when you have hundred of items to put into a bag which is too small for all of them. When you formally write it, here is your problem: * Your bag can contain a weight of at most `W`. * Each item `i` you have has a weight `w_i`. * Each item `i` has a usefulness of `u_i`. You then want to maximize the usefulness of the items you will store into your bag, while keeping sure the weight of the bag will not go over `W`. As a linear program, this problem can be represented this way (if you define `b_i` as the binary variable indicating whether the item `i` is to be included in your bag): .. MATH:: \mbox{Maximize: }\sum_i b_i u_i \\ \mbox{Such that: } \sum_i b_i w_i \leq W \\ \forall i, b_i \mbox{ binary variable} \\ (For more information, cf. http://en.wikipedia.org/wiki/Knapsack_problem.) EXAMPLE: If your knapsack problem is composed of three items (weight, value) defined by (1,2), (1.5,1), (0.5,3), and a bag of maximum weight 2, you can easily solve it this way:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2) [5.0, [(1, 2), (0.500000000000000, 3)]] sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True) 5.0 In the case where all the values (usefulness) of the items are equal to one, you do not need embarrass yourself with the second values, and you can just type for items `(1,1), (1.5,1), (0.5,1)` the command:: sage: from sage.numerical.knapsack import knapsack sage: knapsack([1,1.5,0.5], max=2, value_only=True) 2.0 INPUT: - ``seq`` -- Two different possible types: - A sequence of pairs (weight, value). - A sequence of reals (a value of 1 is assumed). - ``binary`` -- When set to True, an item can be taken 0 or 1 time. When set to False, an item can be taken any amount of times (while staying integer and positive). - ``max`` -- Maximum admissible weight. - ``value_only`` -- When set to True, only the maximum useful value is returned. When set to False, both the maximum useful value and an assignment are returned. OUTPUT: If ``value_only`` is set to True, only the maximum useful value is returned. Else (the default), the function returns a pair ``[value,list]``, where ``list`` can be of two types according to the type of ``seq``: - A list of pairs `(w_i, u_i)` for each object `i` occurring in the solution. - A list of reals where each real is repeated the number of times it is taken into the solution. """ reals = not isinstance(seq[0], tuple) if reals: seq = [(x, 1) for x in seq] from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True) present = p.new_variable() p.set_objective(p.sum([present[i] * seq[i][1] for i in range(len(seq))])) p.add_constraint(p.sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max) if binary: p.set_binary(present) else: p.set_integer(present) if value_only: return p.solve(objective_only=True) else: objective = p.solve() present = p.get_values(present) val = [] if reals: [val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq))] else: [val.extend([seq[i]] * int(present[i])) for i in range(len(seq))] return [objective, val]
def binpacking(items,maximum=1,k=None): r""" Solves the bin packing problem. The Bin Packing problem is the following : Given a list of items of weights `p_i` and a real value `K`, what is the least number of bins such that all the items can be put in the bins, while keeping sure that each bin contains a weight of at most `K` ? For more informations : http://en.wikipedia.org/wiki/Bin_packing_problem Two version of this problem are solved by this algorithm : * Is it possible to put the given items in `L` bins ? * What is the assignment of items using the least number of bins with the given list of items ? INPUT: - ``items`` -- A list of real values (the items' weight) - ``maximum`` -- The maximal size of a bin - ``k`` -- Number of bins - When set to an integer value, the function returns a partition of the items into `k` bins if possible, and raises an exception otherwise. - When set to ``None``, the function returns a partition of the items using the least number possible of bins. OUTPUT: A list of lists, each member corresponding to a box and containing the list of the weights inside it. If there is no solution, an exception is raised (this can only happen when ``k`` is specified or if ``maximum`` is less that the size of one item). EXAMPLES: Trying to find the minimum amount of boxes for 5 items of weights `1/5, 1/4, 2/3, 3/4, 5/7`:: sage: from sage.numerical.optimize import binpacking sage: values = [1/5, 1/3, 2/3, 3/4, 5/7] sage: bins = binpacking(values) sage: len(bins) 3 Checking the bins are of correct size :: sage: all([ sum(b)<= 1 for b in bins ]) True Checking every item is in a bin :: sage: b1, b2, b3 = bins sage: all([ (v in b1 or v in b2 or v in b3) for v in values ]) True One way to use only three boxes (which is best possible) is to put `1/5 + 3/4` together in a box, `1/3+2/3` in another, and `5/7` by itself in the third one. Of course, we can also check that there is no solution using only two boxes :: sage: from sage.numerical.optimize import binpacking sage: binpacking([0.2,0.3,0.8,0.9], k=2) Traceback (most recent call last): ... ValueError: This problem has no solution ! """ if max(items) > maximum: raise ValueError("This problem has no solution !") if k==None: from sage.functions.other import ceil k=ceil(sum(items)/maximum) while True: from sage.numerical.mip import MIPSolverException try: return binpacking(items,k=k,maximum=maximum) except MIPSolverException: k = k + 1 from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException p=MixedIntegerLinearProgram() # Boolean variable indicating whether # the i th element belongs to box b box=p.new_variable(dim=2) # Each bin contains at most max for b in range(k): p.add_constraint(p.sum([items[i]*box[i][b] for i in range(len(items))]),max=maximum) # Each item is assigned exactly one bin for i in range(len(items)): p.add_constraint(p.sum([box[i][b] for b in range(k)]),min=1,max=1) p.set_objective(None) p.set_binary(box) try: p.solve() except MIPSolverException: raise ValueError("This problem has no solution !") box=p.get_values(box) boxes=[[] for i in range(k)] for b in range(k): boxes[b].extend([items[i] for i in range(len(items)) if round(box[i][b])==1]) return boxes
def gale_ryser_theorem(p1, p2, algorithm="gale"): r""" Returns the binary matrix given by the Gale-Ryser theorem. The Gale Ryser theorem asserts that if `p_1,p_2` are two partitions of `n` of respective lengths `k_1,k_2`, then there is a binary `k_1\times k_2` matrix `M` such that `p_1` is the vector of row sums and `p_2` is the vector of column sums of `M`, if and only if the conjugate of `p_2` dominates `p_1`. INPUT: - ``p1, p2``-- list of integers representing the vectors of row/column sums - ``algorithm`` -- two possible string values : - ``"ryser"`` implements the construction due to Ryser [Ryser63]_. - ``"gale"`` (default) implements the construction due to Gale [Gale57]_. OUTPUT: - A binary matrix if it exists, ``None`` otherwise. Gale's Algorithm: (Gale [Gale57]_): A matrix satisfying the constraints of its sums can be defined as the solution of the following Linear Program, which Sage knows how to solve. .. MATH:: \forall i&\sum_{j=1}^{k_2} b_{i,j}=p_{1,j}\\ \forall i&\sum_{j=1}^{k_1} b_{j,i}=p_{2,j}\\ &b_{i,j}\mbox{ is a binary variable} Ryser's Algorithm: (Ryser [Ryser63]_): The construction of an `m\times n` matrix `A=A_{r,s}`, due to Ryser, is described as follows. The construction works if and only if have `s\preceq r^*`. * Construct the `m\times n` matrix `B` from `r` by defining the `i`-th row of `B` to be the vector whose first `r_i` entries are `1`, and the remainder are 0's, `1\leq i\leq m`. This maximal matrix `B` with row sum `r` and ones left justified has column sum `r^{*}`. * Shift the last `1` in certain rows of `B` to column `n` in order to achieve the sum `s_n`. Call this `B` again. * The `1`'s in column n are to appear in those rows in which `A` has the largest row sums, giving preference to the bottom-most positions in case of ties. * Note: When this step automatically "fixes" other columns, one must skip ahead to the first column index with a wrong sum in the step below. * Proceed inductively to construct columns `n-1`, ..., `2`, `1`. * Set `A = B`. Return `A`. EXAMPLES: Computing the matrix for `p_1=p_2=2+2+1` :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [2,2,1] sage: p2 = [2,2,1] sage: print gale_ryser_theorem(p1, p2) # not tested [1 1 0] [1 0 1] [0 1 0] sage: A = gale_ryser_theorem(p1, p2) sage: rs = [sum(x) for x in A.rows()] sage: cs = [sum(x) for x in A.columns()] sage: p1 == rs; p2 == cs True True Or for a non-square matrix with `p_1=3+3+2+1` and `p_2=3+2+2+1+1`, using Ryser's algorithm :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: p1 = [3,3,1,1] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 0 1] [1 1 1 0] [0 1 0 0] [1 0 0 0] sage: p1 = [4,2,2] sage: p2 = [3,3,1,1] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1] [1 1 0 0] [1 1 0 0] sage: p1 = [4,2,2,0] sage: p2 = [3,3,1,1,0,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 1 0 0] [1 1 0 0 0 0] [1 1 0 0 0 0] [0 0 0 0 0 0] sage: p1 = [3,3,2,1] sage: p2 = [3,2,2,1,1] sage: print gale_ryser_theorem(p1, p2, algorithm="gale") # not tested [1 1 1 0 0] [1 1 0 0 1] [1 0 1 0 0] [0 0 0 1 0] With `0` in the sequences, and with unordered inputs :: sage: from sage.combinat.integer_vector import gale_ryser_theorem sage: gale_ryser_theorem([3,3,0,1,1,0], [3,1,3,1,0], algorithm = "ryser") [1 0 1 1 0] [1 1 1 0 0] [0 0 0 0 0] [0 0 1 0 0] [1 0 0 0 0] [0 0 0 0 0] sage: p1 = [3,1,1,1,1]; p2 = [3,2,2,0] sage: gale_ryser_theorem(p1, p2, algorithm = "ryser") [1 1 1 0] [0 0 1 0] [0 1 0 0] [1 0 0 0] [1 0 0 0] TESTS: This test created a random bipartite graph on `n+m` vertices. Its adjacency matrix is binary, and it is used to create some "random-looking" sequences which correspond to an existing matrix. The ``gale_ryser_theorem`` is then called on these sequences, and the output checked for correction.:: sage: def test_algorithm(algorithm, low = 10, high = 50): ... n,m = randint(low,high), randint(low,high) ... g = graphs.RandomBipartite(n, m, .3) ... s1 = sorted(g.degree([(0,i) for i in range(n)]), reverse = True) ... s2 = sorted(g.degree([(1,i) for i in range(m)]), reverse = True) ... m = gale_ryser_theorem(s1, s2, algorithm = algorithm) ... ss1 = sorted(map(lambda x : sum(x) , m.rows()), reverse = True) ... ss2 = sorted(map(lambda x : sum(x) , m.columns()), reverse = True) ... if ((ss1 == s1) and (ss2 == s2)): ... return True ... return False sage: for algorithm in ["gale", "ryser"]: # long time ... for i in range(50): # long time ... if not test_algorithm(algorithm, 3, 10): # long time ... print "Something wrong with algorithm ", algorithm # long time ... break # long time Null matrix:: sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="gale") [0 0 0 0] [0 0 0 0] [0 0 0 0] sage: gale_ryser_theorem([0,0,0],[0,0,0,0], algorithm="ryser") [0 0 0 0] [0 0 0 0] [0 0 0 0] REFERENCES: .. [Ryser63] H. J. Ryser, Combinatorial Mathematics, Carus Monographs, MAA, 1963. .. [Gale57] D. Gale, A theorem on flows in networks, Pacific J. Math. 7(1957)1073-1082. """ from sage.combinat.partition import Partition from sage.matrix.constructor import matrix if not(is_gale_ryser(p1,p2)): return False if algorithm=="ryser": # ryser's algorithm from sage.combinat.permutation import Permutation # Sorts the sequences if they are not, and remembers the permutation # applied tmp = sorted(enumerate(p1), reverse=True, key=lambda x:x[1]) r = [x[1] for x in tmp if x[1]>0] r_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] m = len(r) tmp = sorted(enumerate(p2), reverse=True, key=lambda x:x[1]) s = [x[1] for x in tmp if x[1]>0] s_permutation = [x-1 for x in Permutation([x[0]+1 for x in tmp]).inverse()] n = len(s) A0 = matrix([[1]*r[j]+[0]*(n-r[j]) for j in range(m)]) for k in range(1,n+1): goodcols = [i for i in range(n) if s[i]==sum(A0.column(i))] if sum(A0.column(n-k))<>s[n-k]: A0 = _slider01(A0,s[n-k],n-k, p1, p2, goodcols) # If we need to add empty rows/columns if len(p1)!=m: A0 = A0.stack(matrix([[0]*n]*(len(p1)-m))) if len(p2)!=n: A0 = A0.transpose().stack(matrix([[0]*len(p1)]*(len(p2)-n))).transpose() # Applying the permutations to get a matrix satisfying the # order given by the input A0 = A0.matrix_from_rows_and_columns(r_permutation, s_permutation) return A0 elif algorithm == "gale": from sage.numerical.mip import MixedIntegerLinearProgram k1, k2=len(p1), len(p2) p = MixedIntegerLinearProgram() b = p.new_variable(dim=2) for (i,c) in enumerate(p1): p.add_constraint(p.sum([b[i][j] for j in xrange(k2)]),min=c,max=c) for (i,c) in enumerate(p2): p.add_constraint(p.sum([b[j][i] for j in xrange(k1)]),min=c,max=c) p.set_objective(None) p.set_binary(b) p.solve() b = p.get_values(b) M = [[0]*k2 for i in xrange(k1)] for i in xrange(k1): for j in xrange(k2): M[i][j] = int(b[i][j]) return matrix(M) else: raise ValueError("The only two algorithms available are \"gale\" and \"ryser\"")
def knapsack(seq, binary=True, max=1, value_only=False, solver=None, verbose=0): r""" Solves the knapsack problem For more information on the knapsack problem, see the documentation of the :mod:`knapsack module <sage.numerical.knapsack>` or the :wikipedia:`Knapsack_problem`. INPUT: - ``seq`` -- Two different possible types: - A sequence of tuples ``(weight, value, something1, something2, ...)``. Note that only the first two coordinates (``weight`` and ``values``) will be taken into account. The rest (if any) will be ignored. This can be useful if you need to attach some information to the items. - A sequence of reals (a value of 1 is assumed). - ``binary`` -- When set to ``True``, an item can be taken 0 or 1 time. When set to ``False``, an item can be taken any amount of times (while staying integer and positive). - ``max`` -- Maximum admissible weight. - ``value_only`` -- When set to ``True``, only the maximum useful value is returned. When set to ``False``, both the maximum useful value and an assignment are returned. - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to be used. If set to ``None``, the default one is used. For more information on LP solvers and which default solver is used, see the documentation of class :class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. OUTPUT: If ``value_only`` is set to ``True``, only the maximum useful value is returned. Else (the default), the function returns a pair ``[value,list]``, where ``list`` can be of two types according to the type of ``seq``: - The list of tuples `(w_i, u_i, ...)` occurring in the solution. - A list of reals where each real is repeated the number of times it is taken into the solution. EXAMPLES: If your knapsack problem is composed of three items ``(weight, value)`` defined by ``(1,2), (1.5,1), (0.5,3)``, and a bag of maximum weight `2`, you can easily solve it this way:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2) [5.0, [(1, 2), (0.500000000000000, 3)]] sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True) 5.0 Besides weight and value, you may attach any data to the items:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1, 2, 'spam'), (0.5, 3, 'a', 'lot')]) [3.0, [(0.500000000000000, 3, 'a', 'lot')]] In the case where all the values (usefulness) of the items are equal to one, you do not need embarrass yourself with the second values, and you can just type for items `(1,1), (1.5,1), (0.5,1)` the command:: sage: from sage.numerical.knapsack import knapsack sage: knapsack([1,1.5,0.5], max=2, value_only=True) 2.0 """ reals = not isinstance(seq[0], tuple) if reals: seq = [(x,1) for x in seq] from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver=solver) present = p.new_variable() p.set_objective(p.sum([present[i] * seq[i][1] for i in range(len(seq))])) p.add_constraint(p.sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max) if binary: p.set_binary(present) else: p.set_integer(present) if value_only: return p.solve(objective_only=True, log=verbose) else: objective = p.solve(log=verbose) present = p.get_values(present) val = [] if reals: [val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq))] else: [val.extend([seq[i]] * int(present[i])) for i in range(len(seq))] return [objective,val]
def knapsack(seq, binary=True, max=1, value_only=False, solver=None, verbose=0): r""" Solves the knapsack problem For more information on the knapsack problem, see the documentation of the :mod:`knapsack module <sage.numerical.knapsack>` or the :wikipedia:`Knapsack_problem`. INPUT: - ``seq`` -- Two different possible types: - A sequence of tuples ``(weight, value, something1, something2, ...)``. Note that only the first two coordinates (``weight`` and ``values``) will be taken into account. The rest (if any) will be ignored. This can be useful if you need to attach some information to the items. - A sequence of reals (a value of 1 is assumed). - ``binary`` -- When set to ``True``, an item can be taken 0 or 1 time. When set to ``False``, an item can be taken any amount of times (while staying integer and positive). - ``max`` -- Maximum admissible weight. - ``value_only`` -- When set to ``True``, only the maximum useful value is returned. When set to ``False``, both the maximum useful value and an assignment are returned. - ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to be used. If set to ``None``, the default one is used. For more information on LP solvers and which default solver is used, see the documentation of class :class:`MixedIntegerLinearProgram <sage.numerical.mip.MixedIntegerLinearProgram>`. - ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set to 0 by default, which means quiet. OUTPUT: If ``value_only`` is set to ``True``, only the maximum useful value is returned. Else (the default), the function returns a pair ``[value,list]``, where ``list`` can be of two types according to the type of ``seq``: - The list of tuples `(w_i, u_i, ...)` occurring in the solution. - A list of reals where each real is repeated the number of times it is taken into the solution. EXAMPLES: If your knapsack problem is composed of three items ``(weight, value)`` defined by ``(1,2), (1.5,1), (0.5,3)``, and a bag of maximum weight `2`, you can easily solve it this way:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2) [5.0, [(1, 2), (0.500000000000000, 3)]] sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True) 5.0 Besides weight and value, you may attach any data to the items:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1, 2, 'spam'), (0.5, 3, 'a', 'lot')]) [3.0, [(0.500000000000000, 3, 'a', 'lot')]] In the case where all the values (usefulness) of the items are equal to one, you do not need embarrass yourself with the second values, and you can just type for items `(1,1), (1.5,1), (0.5,1)` the command:: sage: from sage.numerical.knapsack import knapsack sage: knapsack([1,1.5,0.5], max=2, value_only=True) 2.0 """ reals = not isinstance(seq[0], tuple) if reals: seq = [(x, 1) for x in seq] from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True, solver=solver) present = p.new_variable() p.set_objective(p.sum([present[i] * seq[i][1] for i in range(len(seq))])) p.add_constraint(p.sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max) if binary: p.set_binary(present) else: p.set_integer(present) if value_only: return p.solve(objective_only=True, log=verbose) else: objective = p.solve(log=verbose) present = p.get_values(present) val = [] if reals: [ val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq)) ] else: [val.extend([seq[i]] * int(present[i])) for i in range(len(seq))] return [objective, val]
def steiner_tree(self,G, weighted = False): """ Returns a tree of minimum weight connecting the given set of vertices. + + Definition : + + Computing a minimum spanning tree in a graph can be done in `n + \log(n)` time (and in linear time if all weights are + equal). On the other hand, if one is given a large (possibly + weighted) graph and a subset of its vertices, it is NP-Hard to + find a tree of minimum weight connecting the given set of + vertices, which is then called a Steiner Tree. + + `Wikipedia article on Steiner Trees + <http://en.wikipedia.org/wiki/Steiner_tree_problem>`_. + + INPUT: + + - ``vertices`` -- the vertices to be connected by the Steiner + Tree. + + - ``weighted`` (boolean) -- Whether to consider the graph as + weighted, and use each edge's label as a weight, considering + ``None`` as a weight of `1`. If ``weighted=False`` (default) + all edges are considered to have a weight of `1`. + + .. NOTE:: + + * This problem being defined on undirected graphs, the + orientation is not considered if the current graph is + actually a digraph. + + * The graph is assumed not to have multiple edges. + + ALGORITHM: + + Solved through Linear Programming. + + COMPLEXITY: + + NP-Hard. + + Note that this algorithm first checks whether the given + set of vertices induces a connected graph, returning one of its + spanning trees if ``weighted`` is set to ``False``, and thus + answering very quickly in some cases + + EXAMPLES: + + The Steiner Tree of the first 5 vertices in a random graph is, + of course, always a tree :: + + sage: g = graphs.RandomGNP(30,.5) + sage: st = g.steiner_tree(g.vertices()[:5]) # optional - requires GLPK, CBC or CPLEX + sage: st.is_tree() # optional - requires GLPK, CBC or CPLEX + True + + And all the 5 vertices are contained in this tree :: + + sage: all([v in st for v in g.vertices()[:5] ]) # optional - requires GLPK, CBC or CPLEX + True + + An exception is raised when the problem is impossible, i.e. + if the given vertices are not all included in the same + connected component :: + + sage: g = 2 * graphs.PetersenGraph() + sage: st = g.steiner_tree([5,15]) + Traceback (most recent call last): + ... + ValueError: The given vertices do not all belong to the same connected component. This problem has no solution ! + """ if G.is_directed(): g = nx.Graph(G) else: g = G vertices=G.nodes() if g.has_multiple_edges(): raise ValueError("The graph is expected not to have multiple edges.") # Can the problem be solved ? Are all the vertices in the same # connected component ? cc = g.connected_component_containing_vertex(vertices[0]) if not all([v in cc for v in vertices]): raise ValueError("The given vertices do not all belong to the same connected component. This problem has no solution !") # Can it be solved using the min spanning tree algorithm ? if not weighted: gg = g.subgraph(vertices) if gg.is_connected(): st = g.subgraph(edges = gg.min_spanning_tree()) st.delete_vertices([v for v in g if st.degree(v) == 0]) return st # Then, LP formulation p = MixedIntegerLinearProgram(maximization = False) # Reorder an edge R = lambda (x,y) : (x,y) if x<y else (y,x) # edges used in the Steiner Tree edges = p.new_variable() # relaxed edges to test for acyclicity r_edges = p.new_variable() # Whether a vertex is in the Steiner Tree vertex = p.new_variable() for v in g: for e in g.edges_incident(v, labels=False): p.add_constraint(vertex[v] - edges[R(e)], min = 0) # We must have the given vertices in our tree for v in vertices: p.add_constraint(sum([edges[R(e)] for e in g.edges_incident(v,labels=False)]), min=1) # The number of edges is equal to the number of vertices in our tree minus 1 p.add_constraint(sum([vertex[v] for v in g]) - sum([edges[R(e)] for e in g.edges(labels=None)]), max = 1, min = 1) # There are no cycles in our graph for u,v in g.edges(labels = False): p.add_constraint( r_edges[(u,v)]+ r_edges[(v,u)] - edges[R((u,v))] , min = 0 ) eps = 1/(5*Integer(g.order())) for v in g: p.add_constraint(sum([r_edges[(u,v)] for u in g.neighbors(v)]), max = 1-eps) # Objective if weighted: w = lambda (x,y) : g.edge_label(x,y) if g.edge_label(x,y) is not None else 1 else: w = lambda (x,y) : 1 p.set_objective(sum([w(e)*edges[R(e)] for e in g.edges(labels = False)])) p.set_binary(edges) p.solve() edges = p.get_values(edges) st = g.subgraph(edges=[e for e in g.edges(labels = False) if edges[R(e)] == 1]) st.delete_vertices([v for v in g if st.degree(v) == 0]) return st
def knapsack(seq, binary=True, max=1, value_only=False): r""" Solves the knapsack problem Knapsack problems: You have already had a knapsack problem, so you should know, but in case you do not, a knapsack problem is what happens when you have hundred of items to put into a bag which is too small for all of them. When you formally write it, here is your problem: * Your bag can contain a weight of at most `W`. * Each item `i` you have has a weight `w_i`. * Each item `i` has a usefulness of `u_i`. You then want to maximize the usefulness of the items you will store into your bag, while keeping sure the weight of the bag will not go over `W`. As a linear program, this problem can be represented this way (if you define `b_i` as the binary variable indicating whether the item `i` is to be included in your bag): .. MATH:: \mbox{Maximize: }\sum_i b_i u_i \\ \mbox{Such that: } \sum_i b_i w_i \leq W \\ \forall i, b_i \mbox{ binary variable} \\ (For more information, cf. http://en.wikipedia.org/wiki/Knapsack_problem.) EXAMPLE: If your knapsack problem is composed of three items (weight, value) defined by (1,2), (1.5,1), (0.5,3), and a bag of maximum weight 2, you can easily solve it this way:: sage: from sage.numerical.knapsack import knapsack sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2) [5.0, [(1, 2), (0.500000000000000, 3)]] sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True) 5.0 In the case where all the values (usefulness) of the items are equal to one, you do not need embarrass yourself with the second values, and you can just type for items `(1,1), (1.5,1), (0.5,1)` the command:: sage: from sage.numerical.knapsack import knapsack sage: knapsack([1,1.5,0.5], max=2, value_only=True) 2.0 INPUT: - ``seq`` -- Two different possible types: - A sequence of pairs (weight, value). - A sequence of reals (a value of 1 is assumed). - ``binary`` -- When set to True, an item can be taken 0 or 1 time. When set to False, an item can be taken any amount of times (while staying integer and positive). - ``max`` -- Maximum admissible weight. - ``value_only`` -- When set to True, only the maximum useful value is returned. When set to False, both the maximum useful value and an assignment are returned. OUTPUT: If ``value_only`` is set to True, only the maximum useful value is returned. Else (the default), the function returns a pair ``[value,list]``, where ``list`` can be of two types according to the type of ``seq``: - A list of pairs `(w_i, u_i)` for each object `i` occurring in the solution. - A list of reals where each real is repeated the number of times it is taken into the solution. """ reals = not isinstance(seq[0], tuple) if reals: seq = [(x, 1) for x in seq] from sage.numerical.mip import MixedIntegerLinearProgram p = MixedIntegerLinearProgram(maximization=True) present = p.new_variable() p.set_objective(p.sum([present[i] * seq[i][1] for i in range(len(seq))])) p.add_constraint(p.sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max) if binary: p.set_binary(present) else: p.set_integer(present) if value_only: return p.solve(objective_only=True) else: objective = p.solve() present = p.get_values(present) val = [] if reals: [ val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq)) ] else: [val.extend([seq[i]] * int(present[i])) for i in range(len(seq))] return [objective, val]