Сообщество Engee

Распределение офисов между сотрудниками

Автор
avatar-artpgchartpgch
Notebook

Оптимизация распределения служебных помещений между сотрудниками

Введение

В данном примере представлено решение задачи оптимального распределения ограниченного ресурса — служебных помещений — между сотрудниками с учётом ряда организационных требований. Критериями оптимизации выступают персональные предпочтения работников, взвешенные по их стажу, а также обязательное условие смежности рабочих мест для двух пар сотрудников, занятых совместными проектами. Задача формализована в виде модели целочисленного линейного программирования с целью максимизации суммарного взвешенного удовлетворения предпочтений при соблюдении всех установленных ограничений.

Распределение помещений

Перед нами стоит задача распределить семь офисных помещений между шестью сотрудниками.

Имена сотрудников: Анна, Артём, Алёна, Антон, Андрей, Анастасия.

Каждый сотрудник получает ровно один кабинет, а в каждом кабинете может находиться не более одного человека. Таким образом, одно помещение неизбежно останется пустым.

Сотрудники могут указывать свои предпочтения по выбору рабочих мест, и эти пожелания учитываются в соответствии с их стажем работы в компании: чем дольше человек работает в компании, тем выше его приоритет.

Планировка помещений неоднородна: часть кабинетов имеет окна, другие — нет, причём одно из окон заметно меньше остальных. Дополнительная сложность заключается в том, что Алёна и Антон часто работают в паре, поэтому их стоит разместить в соседних помещениях. То же самое касается Андрея и Анастасии — их рабочие места также должны быть смежными.

Присоединим необходимые библиотеки и функцию построения планировки.

In [ ]:
using JuMP, HiGHS, LinearAlgebra
include("officeassign.jl")

Планировка помещений

Кабинеты 1, 2, 3 и 4 расположены внутри здания — это помещения без окон. Кабинеты 5, 6 и 7 имеют доступ к естественному освещению, однако важно отметить, что окно в кабинете 5 значительно меньше, чем в двух других.

Схема расположения кабинетов:

In [ ]:
кабинеты = ["Кабинет 1", "Кабинет 2", "Кабинет 3", "Кабинет 4", "Кабинет 5", "Кабинет 6", "Кабинет 7"];
officeassign(кабинеты)

Постановка задачи

Представим задачу в виде математической модели. Создадим бинарные переменные, которые будут указывать на принадлежность кабинета конкретному сотруднику.

Список имён сотрудников:

In [ ]:
имена = ["Анна", "Артём", "Алёна", "Антон", "Андрей", "Анастасия"];

Создадим бинарные переменные, индексированные по номерам кабинетов и именам сотрудников.

In [ ]:
модель = Model(HiGHS.Optimizer)
@variable(модель, занимает[i in имена, j in кабинеты], 
          Int, lower_bound=0, upper_bound=1);

Стаж сотрудников

Необходимо назначить весовые коэффициенты для предпочтений сотрудников в зависимости от их стажа. Таким образом, чем дольше человек работает в компании MathWorks, тем больший вес имеют его пожелания.

Стаж работы сотрудников распределяется следующим образом:

  • Анна — 9 лет

  • Артём — 2 года

  • Алёна — 5 лет

  • Антон — 4 года

  • Андрей — 1,5 года

  • Анастасия — 10 лет

На основе этих данных требуется построить нормированный вектор весовых коэффициентов.

In [ ]:
стаж = [9, 2, 5, 4, 1.5, 10];
весовой_вектор = стаж/sum(стаж);

Предпочтения сотрудников

Построим матрицу предпочтений, в которой строки соответствуют кабинетам, а столбцы — сотрудникам. Каждого человека нужно попросить оценить каждый кабинет по шкале таким образом, чтобы сумма всех его оценок (то есть сумма по столбцу) равнялась 100. Чем выше значение, тем более желательным считается данный кабинет для сотрудника. Предпочтения каждого человека представлены в виде вектор-столбца.

In [ ]:
Анна = [1, 3, 3, 3, 10, 40, 40];
Артём = [0, 0, 0, 0, 20, 40, 40];
Алёна = [0, 0, 0, 0, 30, 40, 30];
Антон = [0, 0, 0, 10, 10, 40, 50]
Андрей = [3, 4, 1, 2, 10, 40, 40];
Анастасия = [10, 10, 10, 10, 20, 20, 20];

Каждый i-й элемент вектора предпочтений каждого сотрудника показывает, насколько высоко он ценит i-й кабинет.

Таким образом, итоговая матрица предпочтений имеет следующий вид:

In [ ]:
матрица_предпочтений = [Анна Артём Алёна Антон Андрей Анастасия]';

Умножим матрицу предпочтений на весовой вектор, чтобы масштабировать столбцы с учетом стажа.

In [ ]:
ПМ = zeros(length(имена), length(кабинеты))
for i in 1:length(имена)
    for j in 1:length(кабинеты)
        ПМ[i, j] = весовой_вектор[i] * матрица_предпочтений[i, j]
    end
end

Целевая функция

Цель задачи — максимизировать удовлетворенность предпочтений сотрудников с учетом их стажа. Теперь необходимо создать задачу оптимизации и включить в нее данную целевую функцию.

In [ ]:
@objective(модель, Max, sum(занимает[i, j] * ПМ[findfirst(isequal(i), имена), findfirst(isequal(j), кабинеты)] 
                          for i in имена, j in кабинеты));

Ограничения

Первое условие гарантирует, что каждый сотрудник получает ровно один кабинет. Математически это означает, что для каждого человека сумма значений переменной занимает по всем кабинетам должна быть в точности равна единице.

In [ ]:
@constraint(модель, ограничение1[i in имена], sum(занимает[i, j] for j in кабинеты) == 1);

Вторая группа условий представляет собой неравенства. Эти ограничения гарантируют, что в каждом кабинете будет находиться не более одного человека.

In [ ]:
@constraint(модель, ограничение2[j in кабинеты], sum(занимает[i, j] for i in имена) <= 1);

Требуется, чтобы Алёна и Антон располагались в кабинетах, расстояние между которыми не превышает одного кабинета. Аналогичное условие применяется к Анастасии и Андрею.

Установим ограничения, гарантирующие, что Алёна и Антон будут находиться не дальше, чем через один кабинет друг от друга.

In [ ]:
@constraint(модель, ограничение_Алёна_Антон_1, занимает["Алёна","Кабинет 1"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 2"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_2, занимает["Алёна","Кабинет 2"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 1"] - занимает["Антон","Кабинет 3"] - занимает["Антон","Кабинет 5"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_3, занимает["Алёна","Кабинет 3"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 2"] - занимает["Антон","Кабинет 4"] - занимает["Антон","Кабинет 6"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_4, занимает["Алёна","Кабинет 4"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 3"] - занимает["Антон","Кабинет 7"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_5, занимает["Алёна","Кабинет 5"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 2"] - занимает["Антон","Кабинет 6"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_6, занимает["Алёна","Кабинет 6"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 3"] - занимает["Антон","Кабинет 5"] - занимает["Антон","Кабинет 7"] <= 1);
@constraint(модель, ограничение_Алёна_Антон_7, занимает["Алёна","Кабинет 7"] + sum(занимает["Антон", j] for j in кабинеты) - занимает["Антон","Кабинет 4"] - занимает["Антон","Кабинет 6"] <= 1);

Теперь установим ограничения, гарантирующие, что Анастасия и Андрей будут находиться не дальше, чем через один кабинет друг от друга.

In [ ]:
@constraint(модель, ограничение_Андрей_Анастасия_1, занимает["Андрей","Кабинет 1"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 2"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_2, занимает["Андрей","Кабинет 2"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 1"] - занимает["Анастасия","Кабинет 3"] - занимает["Анастасия","Кабинет 5"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_3, занимает["Андрей","Кабинет 3"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 2"] - занимает["Анастасия","Кабинет 4"] - занимает["Анастасия","Кабинет 6"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_4, занимает["Андрей","Кабинет 4"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 3"] - занимает["Анастасия","Кабинет 7"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_5, занимает["Андрей","Кабинет 5"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 2"] - занимает["Анастасия","Кабинет 6"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_6, занимает["Андрей","Кабинет 6"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 3"] - занимает["Анастасия","Кабинет 5"] - занимает["Анастасия","Кабинет 7"] <= 1);
@constraint(модель, ограничение_Андрей_Анастасия_7, занимает["Андрей","Кабинет 7"] + sum(занимает["Анастасия", j] for j in кабинеты) - занимает["Анастасия","Кабинет 4"] - занимает["Анастасия","Кабинет 6"] <= 1);

Решение

Выполним решение задачи оптимизации.

In [ ]:
optimize!(модель);
Running HiGHS 1.11.0 (git hash: 364c83a51e): Copyright (c) 2025 HiGHS under MIT licence terms
MIP  has 27 rows; 42 cols; 164 nonzeros; 42 integer variables (42 binary)
Coefficient ranges:
  Matrix [1e+00, 1e+00]
  Cost   [5e-02, 1e+01]
  Bound  [1e+00, 1e+00]
  RHS    [1e+00, 1e+00]
Presolving model
27 rows, 42 cols, 164 nonzeros  0s
27 rows, 42 cols, 174 nonzeros  0s
Objective function is integral with scale 441

Solving MIP model with:
   27 rows
   42 cols (42 binary, 0 integer, 0 implied int., 0 continuous, 0 domain fixed)
   174 nonzeros

Src: B => Branching; C => Central rounding; F => Feasibility pump; J => Feasibility jump;
     H => Heuristic; L => Sub-MIP; P => Empty MIP; R => Randomized rounding; Z => ZI Round;
     I => Shifting; S => Solve LP; T => Evaluate node; U => Unbounded; X => User solution;
     z => Trivial zero; l => Trivial lower; u => Trivial upper; p => Trivial point

        Nodes      |    B&B Tree     |            Objective Bounds              |  Dynamic Constraints |       Work      
Src  Proc. InQueue |  Leaves   Expl. | BestBound       BestSol              Gap |   Cuts   InLp Confl. | LpIters     Time

 J       0       0         0   0.00%   inf             19.0952381         Large        0      0      0         0     0.1s
 R       0       0         0   0.00%   25.06349206     19.52380952       28.37%        0      0      0        21     0.1s
 C       0       0         0   0.00%   24.63492063     20.0952381        22.59%       31      1      0        23     0.1s
 T       0       0         0 100.00%   24.63492063     24.63492063        0.00%       31      1      0        23     0.1s
         1       0         1 100.00%   24.63492063     24.63492063        0.00%       31      1      0        23     0.1s

Solving report
  Status            Optimal
  Primal bound      24.6349206349
  Dual bound        24.6349206349
  Gap               0% (tolerance: 0.01%)
  P-D integral      0.00197485656513
  Solution status   feasible
                    24.6349206349 (objective)
                    0 (bound viol.)
                    0 (int. viol.)
                    0 (row viol.)
  Timing            0.08 (total)
                    0.00 (presolve)
                    0.00 (solve)
                    0.00 (postsolve)
  Max sub-MIP depth 0
  Nodes             1
  Repair LPs        0 (0 feasible; 0 iterations)
  LP iterations     23 (total)
                    0 (strong br.)
                    2 (separation)
                    0 (heuristics)

In [ ]:
решение = value.(занимает);
значение_функции = objective_value(модель);
статус_выхода = termination_status(модель);
выходные_данные = raw_status(модель);

Визуализация решения

Визуализируем оптимальное распределение служебных помещений между сотрудниками.

In [ ]:
число_кабинетов = length(кабинеты);
кабинет = Vector{Vector{String}}(undef, число_кабинетов);
for i=1:число_кабинетов
    кабинет[i] = имена[findall(x -> x > 0.5, [решение[имя, кабинеты[i]] for имя in имена])]
end

кто_в_кабинете = copy(кабинеты);
for i=1:число_кабинетов
    if isempty(кабинет[i])
        кто_в_кабинете[i] = " пусто  ";
    else
        кто_в_кабинете[i] = кабинет[i][1];
    end
end

officeassign(кто_в_кабинете);
значение_функции,статус_выхода,выходные_данные
Out[0]:
(24.634920634920633, MathOptInterface.OPTIMAL, "kHighsModelStatusOptimal")

Вывод

В результате решения оптимизационной задачи было получено распределение кабинетов, обеспечивающее максимальное значение целевой функции при полном соблюдении заданных условий. Решение демонстрирует эффективность применения методов математического моделирования для принятия объективных управленческих решений в условиях конфликтующих критериев и ограниченных ресурсов. Использованный подход позволяет формализовать и балансировать как субъективные человеческие предпочтения, так и строгие операционные требования.