blog

シフト最適化問題

Published:

By nob

Category: Posts

Tags: 数理最適化 Python-MIP Python

データの読み込み

OR-Library: Shift minimization personnel task scheduling でシフト最適化問題のデータが公開されている。

データは以下の書式である。

# Randomly generated data for apersonnel scheduling problem
# ./datagen tightness = 90 Multi-skilling level = 66
# Random number generator seed = 0
Type = 1
Jobs = 111
  17  419
...
Qualifications = 51
 76:  45 102 103   0   1   3   6   7   8  11  12  14  15  16  21  22  23  24  25  26  27  28  29  30  33  35  36  37  39  40  41  43  47  50  52  54  55  57  59  61  62  64  65  66  67  69  70  71  72  73  74  75  76  77  79  80  81  82  83  85  87  88  90  91  93  94  96  98  99 100 101 104 106 107 108 109
...
  • ヘッダ
  • 問題の種類(1: minimizing the number of shifts/workers)
  • Jobの数
  • 各Jobの開始・終了時刻
  • 担当者の数
  • 各担当者にアサインできるJobのリスト
from dataclasses import dataclass

import numpy as np


@dataclass
class Problem:
    file: str
    type: str
    jobs: list
    qualifications: dict


@dataclass
class Job:
    no: int
    start: int
    end: int

    def overlap(self, other):
        # job.end == job.startは許容する
        if other.start < self.start < other.end:
            return True
        if other.start < self.end < other.end:
            return True
        if self.start < other.start < self.end:
            return True
        if self.start < other.end < self.end:
            return True
        return False

    @property
    def delta(self):
        return self.end - self.start


def load_data(file):
    with open(file, "r") as f:
        problem_type = None
        jobs = []
        qualifications = {}
        while line := f.readline():
            if line.startswith("#"):
                continue
            if line.startswith("Type"):
                problem_type = line.split()[2]
            if line.startswith("Jobs"):
                num_jobs = int(line.split()[2])
                for i in range(num_jobs):
                    line = f.readline()
                    job_start, job_end = map(int, line.split())
                    jobs.append(Job(i, job_start, job_end))
            if line.startswith("Qualifications"):
                num_workers = int(line.split()[2])
                for i in range(num_workers):
                    line = f.readline()
                    qualifications[i] = set(map(int, line.split()[1:]))
        problem = Problem(file, problem_type, jobs, qualifications)
    return problem
problem = load_data("data/ptask/data_10_51_111_66.dat")
len(problem.jobs), len(problem.qualifications)
(111, 51)

同時にアサインしてはならないjobを調べる

各Jobをノードとして時間の重なりがあるJobを接続し、最大クリークを探す。

最大クリークが同時にアサインしてはならないJobの組み合わせとなる。

import networkx as nx

g = nx.Graph()

jobs = problem.jobs
for i in range(len(jobs)):
    for j in range(i + 1, len(jobs)):
        if jobs[i].overlap(jobs[j]):
            g.add_edge(i, j)

cliques = []
for c in nx.find_cliques(g):
    cliques.append(set(c))

print(len(cliques))
for clique in cliques:
    print(clique)
32
{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 17, 18, 19, 20, 21, 24, 25, 26, 27, 28, 31, 38, 39, 40, 43, 45, 49, 52, 54, 60, 65, 66, 67, 75, 84, 85}
{13, 34, 37, 42, 48, 53, 55, 56, 57, 58, 62, 64, 71, 72, 74, 76, 79, 80, 86, 87, 88, 90, 92, 93, 94, 96, 97, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{13, 34, 37, 42, 48, 53, 55, 56, 57, 58, 62, 71, 72, 73, 74, 76, 80, 82, 86, 87, 88, 90, 92, 93, 94, 95, 96, 97, 98, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{13, 34, 37, 42, 48, 53, 55, 57, 58, 62, 63, 71, 72, 73, 74, 76, 80, 82, 86, 87, 88, 90, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{13, 34, 37, 42, 48, 53, 55, 56, 57, 58, 62, 71, 72, 73, 74, 76, 79, 80, 82, 86, 87, 88, 90, 92, 93, 94, 95, 96, 97, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{4, 34, 37, 42, 48, 53, 55, 56, 57, 58, 59, 62, 64, 70, 71, 72, 74, 76, 77, 78, 79, 80, 87, 88, 92, 93, 94, 96, 97, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{4, 34, 37, 42, 48, 53, 55, 56, 57, 58, 59, 62, 64, 70, 71, 72, 74, 77, 78, 79, 80, 81, 87, 88, 92, 93, 94, 96, 103, 104, 105, 106, 109, 110}
{4, 22, 34, 37, 42, 47, 48, 50, 51, 53, 55, 56, 58, 59, 62, 64, 70, 71, 72, 74, 77, 78, 79, 80, 81, 87, 88, 89, 92, 94, 96, 103, 104, 105, 106, 109, 110}
{4, 34, 37, 42, 48, 53, 55, 56, 57, 58, 62, 64, 70, 71, 72, 74, 76, 77, 78, 79, 80, 86, 87, 88, 92, 93, 94, 96, 97, 101, 103, 104, 105, 106, 107, 108, 109, 110}
{0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 24, 25, 26, 28, 31, 35, 38, 39, 40, 41, 43, 45, 49, 52, 54, 60, 65, 66, 67, 75, 84, 85}
{0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 15, 17, 18, 19, 20, 21, 24, 25, 26, 27, 28, 31, 38, 39, 40, 41, 43, 45, 49, 52, 54, 60, 65, 66, 67, 75, 84, 85}
{0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 24, 25, 26, 28, 35, 38, 40, 41, 43, 45, 49, 50, 51, 52, 54, 60, 61, 65, 67, 75, 85, 91}
{0, 1, 3, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 22, 24, 25, 26, 28, 30, 35, 36, 38, 40, 41, 43, 45, 49, 50, 51, 52, 54, 61, 65, 67, 68, 75, 85, 91}
{1, 3, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 22, 24, 25, 29, 30, 35, 36, 38, 40, 41, 43, 45, 46, 47, 49, 50, 51, 52, 61, 65, 67, 68, 75, 85, 91}
{1, 3, 7, 8, 9, 10, 11, 14, 15, 19, 20, 21, 22, 24, 25, 29, 30, 33, 35, 36, 38, 40, 41, 43, 46, 47, 49, 50, 51, 59, 61, 67, 68, 75, 81, 83, 85, 91, 102}
{1, 3, 7, 8, 9, 11, 14, 15, 19, 20, 21, 22, 23, 24, 25, 29, 30, 33, 35, 36, 38, 40, 41, 43, 46, 47, 49, 50, 51, 59, 61, 67, 68, 75, 79, 81, 83, 85, 91, 102}
{1, 3, 7, 8, 9, 11, 14, 15, 19, 20, 22, 23, 24, 25, 29, 30, 33, 35, 36, 38, 40, 41, 43, 44, 46, 47, 50, 51, 59, 61, 67, 68, 69, 75, 79, 81, 83, 85, 91, 102}
{1, 3, 7, 14, 15, 19, 20, 22, 23, 25, 29, 30, 33, 35, 36, 38, 40, 41, 42, 43, 44, 46, 47, 50, 51, 59, 61, 67, 68, 69, 75, 78, 79, 81, 83, 85, 91, 102}
{1, 14, 15, 19, 20, 22, 23, 25, 29, 30, 32, 33, 35, 36, 38, 40, 41, 42, 43, 44, 46, 47, 50, 51, 59, 61, 67, 68, 69, 70, 75, 77, 78, 79, 81, 83, 85, 91, 102}
{1, 4, 15, 16, 19, 20, 22, 23, 29, 30, 32, 33, 35, 36, 38, 40, 41, 42, 44, 46, 47, 50, 51, 56, 59, 61, 64, 67, 68, 69, 70, 77, 78, 79, 81, 83, 89, 91, 102, 110}
{4, 15, 16, 19, 22, 23, 29, 30, 32, 33, 35, 36, 41, 42, 44, 46, 47, 48, 50, 51, 56, 59, 61, 64, 68, 69, 70, 77, 78, 79, 81, 83, 89, 91, 102, 110}
{4, 16, 22, 23, 29, 30, 32, 33, 36, 41, 42, 44, 46, 47, 48, 50, 51, 56, 59, 61, 64, 68, 69, 70, 77, 78, 79, 80, 81, 83, 89, 94, 96, 102, 106, 110}
{4, 16, 22, 30, 32, 36, 41, 42, 44, 47, 48, 50, 51, 56, 59, 61, 64, 69, 70, 77, 78, 79, 80, 81, 89, 94, 96, 104, 106, 109, 110}
{4, 16, 22, 32, 36, 41, 42, 44, 47, 48, 50, 51, 53, 56, 58, 59, 61, 62, 64, 69, 70, 71, 72, 74, 77, 78, 79, 80, 81, 88, 89, 92, 94, 96, 103, 104, 105, 106, 109, 110}
{4, 15, 16, 22, 23, 29, 30, 32, 33, 35, 36, 41, 42, 44, 46, 47, 48, 50, 51, 56, 59, 61, 64, 68, 69, 70, 77, 78, 79, 81, 83, 89, 91, 94, 96, 102, 106, 110}
{1, 4, 15, 16, 19, 20, 22, 23, 29, 30, 32, 33, 35, 36, 38, 40, 41, 42, 43, 44, 46, 47, 50, 51, 59, 61, 64, 67, 68, 69, 70, 77, 78, 79, 81, 83, 85, 91, 102}
{1, 7, 14, 15, 19, 20, 22, 23, 25, 29, 30, 33, 35, 36, 38, 40, 41, 42, 43, 44, 46, 47, 50, 51, 59, 61, 67, 68, 69, 75, 77, 78, 79, 81, 83, 85, 91, 102}
{1, 3, 7, 8, 9, 10, 11, 14, 15, 19, 20, 21, 22, 24, 25, 29, 30, 33, 35, 36, 38, 40, 41, 43, 46, 47, 49, 50, 51, 52, 59, 61, 67, 68, 75, 83, 85, 91, 102}
{1, 3, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 22, 24, 25, 29, 30, 35, 36, 38, 40, 41, 43, 46, 47, 49, 50, 51, 52, 61, 67, 68, 75, 85, 91, 102}
{1, 3, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 22, 24, 25, 26, 30, 35, 36, 38, 40, 41, 43, 45, 46, 49, 50, 51, 52, 54, 61, 65, 67, 68, 75, 85, 91}
{0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 15, 19, 20, 21, 24, 25, 26, 28, 35, 38, 39, 40, 41, 43, 45, 49, 51, 52, 54, 60, 61, 65, 67, 75, 85}
{4, 16, 22, 32, 36, 42, 44, 47, 48, 50, 51, 53, 55, 56, 58, 59, 62, 64, 69, 70, 71, 72, 74, 77, 78, 79, 80, 81, 87, 88, 89, 92, 94, 96, 103, 104, 105, 106, 109, 110}

モデルの作成

from mip import Model, OptimizationStatus, minimize, xsum

model = Model(solver_name="cbc")

"""
変数
"""
# 担当者をアサインするかどうか
assign = model.add_var_tensor(
    (len(problem.qualifications),), "assign", var_type="B"
)

# 担当者がどのjobにアサインされているか
job_assign = model.add_var_tensor(
    (len(problem.qualifications), len(problem.jobs)),
    "job_assign",
    var_type="B",
)

"""
目的関数
"""
model.objective = minimize(xsum(assign))

"""
制約条件
"""
for i, qualification in problem.qualifications.items():
    # 対応できないjobにアサインされていないこと
    for j in range(job_assign.shape[1]):
        value = 1 if j in qualification else 0
        model += job_assign[i][j] <= value

    # 複数のjobがアサインされている場合、時間が重なっていないこと
    for clique in cliques:
        model += (
            xsum(job_assign[i][j] for j in qualification.intersection(clique))
            <= assign[i]
        )

# 全てのjobに担当者が1人アサインされていること
for i in range(len(problem.jobs)):
    model += xsum(job_assign[:, i]) == 1

"""
求解
"""
model.optimize()

"""
結果の確認
"""
if model.status == OptimizationStatus.OPTIMAL:
    print("コスト", model.objective_value)

    print("アサインされた担当者")
    print(assign.astype(float, subok=False))

    assigned = job_assign.astype(float, subok=False)

    print("担当者にアサインされたjobの数")
    num_assigned_jobs = assigned.sum(axis=1)
    print(num_assigned_jobs)

    print("全てのjobに担当者が1人アサインされていること")
    print(assigned.sum(axis=0))

    print("対応できないjobにアサインされていないこと")
    total_qualified = 0
    total_assigned = 0
    for i, a in enumerate(assigned):
        qualified = True
        if not np.sum(a) > 0:
            print(i, "not assigned")
            continue
        total_assigned += 1
        for j in range(len(problem.jobs)):
            if a[j] == 1 and j not in problem.qualifications[i]:
                qualified = False
        if qualified:
            total_qualified += 1
        print(i, "qualified" if qualified else "not qualified")
        print(a)
    print(
        "worker / assigned / qualified",
        assigned.shape[0],
        total_assigned,
        total_qualified,
    )
else:
    print(model.status)
Welcome to the CBC MILP Solver 
Version: Trunk
Build Date: Oct 24 2021

Starting solution of the Linear programming relaxation problem using Primal Simplex

Coin0506I Presolve 1696 (-5708) rows, 3943 (-1769) columns and 46974 (-8707) elements
Clp1000I sum of infeasibilities 5.99312e-08 - average 3.53368e-11, 0 fixed columns
Coin0506I Presolve 1696 (0) rows, 3943 (0) columns and 46974 (0) elements
Clp0029I End of values pass after 3943 iterations
Clp0014I Perturbing problem by 0.001% of 1 - largest nonzero change 0 ( 0%) - largest zero change 2.9976734e-05
Clp0000I Optimal - objective value 40
Clp0000I Optimal - objective value 40
Clp0000I Optimal - objective value 40
Coin0511I After Postsolve, objective 40, infeasibilities - dual 0 (0), primal 0 (0)
Clp0032I Optimal objective 40 - 0 iterations time 0.432, Presolve 0.01, Idiot 0.43

Starting MIP optimization
コスト 40.0
アサインされた担当者
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 1. 1. 1.
 1. 1. 1. 1. 1. 0. 1. 1. 0. 0. 0. 0. 0. 1. 0. 1. 1. 0. 1. 1. 1. 1. 1. 1.
 1. 0. 0.]
担当者にアサインされたjobの数
[3. 3. 3. 3. 3. 3. 3. 2. 3. 3. 3. 2. 3. 3. 3. 3. 3. 3. 3. 3. 0. 3. 2. 3.
 2. 2. 3. 3. 3. 0. 2. 3. 0. 0. 0. 0. 0. 3. 0. 2. 3. 0. 2. 2. 3. 3. 3. 3.
 3. 0. 0.]
全てのjobに担当者が1人アサインされていること
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
対応できないjobにアサインされていないこと
0 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
1 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
2 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
3 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
4 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
5 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
6 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
7 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
8 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
9 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
10 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
11 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
12 qualified
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
13 qualified
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
14 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
15 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
16 qualified
[0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
17 qualified
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
18 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
19 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
20 not assigned
21 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
22 qualified
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
23 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
24 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
25 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
26 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
27 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
28 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
29 not assigned
30 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
31 qualified
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
32 not assigned
33 not assigned
34 not assigned
35 not assigned
36 not assigned
37 qualified
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
38 not assigned
39 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
40 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
41 not assigned
42 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
43 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.
 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
44 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
45 qualified
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
46 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
47 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
48 qualified
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
49 not assigned
50 not assigned
worker / assigned / qualified 51 40 40
import pandas as pd
import plotly.express as px

data = []
for i, a in enumerate(assigned):
    for assigned_job in np.where(a > 0):
        if assigned_job.shape[0] == 0:
            task = dict(task=str(i), start=0, end=0, delta=0)
            data.append(task)
            continue
        for j in assigned_job:
            task = dict(
                task=str(i),
                start=jobs[j].start,
                end=jobs[j].end,
                delta=jobs[j].delta,
            )
            data.append(task)

df = pd.DataFrame(data)
df["color"] = df["task"]
fig = px.timeline(
    df,
    x_start="start",
    x_end="end",
    y="task",
    color="color",
)
fig.layout.autosize = True
fig.layout.height = 1000
fig.layout.xaxis.type = "linear"
fig.layout.yaxis.autorange = "reversed"
# https://stackoverflow.com/a/68842350
for d in fig.data:
    data_filter = df["task"] == d.name
    d.x = df[data_filter]["delta"].tolist()
fig.show()

データの出典

参考

  • Python言語による実務で使える100+の最適化問題 81.シフトスケジューリング問題