O calendário de férias perfeito. Algoritmos naturais. Comportamento do enxame de abelhas





Algoritmos naturais (ou evolutivos) são um ramo da inteligência artificial que modela os processos de seleção natural, mutação e reprodução.



Um dos tipos de algoritmos naturais é o método do enxame de abelhas. Seu objetivo é concentrar mais abelhas nas áreas com maior densidade de flores.





As abelhas inicialmente não sabem nada sobre o campo, os obstáculos e o arranjo das flores. Eles começam sua busca em posições aleatórias, com velocidades e direções aleatórias.



Cada abelha lembra a posição onde encontrou mais flores e a área onde as outras abelhas encontraram mais flores. Ao escolher outra direção, a abelha irá passar entre esses dois pontos, dando preferência a um deles, dependendo do que for mais importante para ela: percepção pessoal ou reflexo social. Se, no processo de movimentação, for encontrado um local mais floral, no futuro poderá ser designado como o local de maior concentração de flores, marcado por todo o enxame.



A abelha pode voar além do alvo. Para evitar que isso aconteça, a velocidade da abelha diminui à medida que se aproxima do local de concentração. Assim, logo todo o enxame se reúne em locais de flores.







A tarefa era planejar as férias dos funcionários com as seguintes condições:



  1. Existem preferências por períodos de férias. Mudar e mudar esses períodos é indesejável. Algumas férias estão proibidas de se alterar.
  2. Os funcionários podem ter um número diferente de dias de férias
  3. Quantidade mínima de férias 7 dias
  4. Uma parte das férias deve ser de pelo menos 14 dias
  5. Quanto menos dias de férias você sair de férias, melhor
  6. Mais de 30% dos funcionários não devem estar ausentes em um departamento


Para a solução, usaremos um algoritmo genético e um algoritmo de enxame de abelhas. No papel das abelhas haverá períodos de férias (Aula Dia das Bruxas). Cada período pertence a um funcionário (Class Empl), cada funcionário está em um departamento (Class Dep).



//   
class Holiday
{
    public List<Penalty> penalties;
    public Empl empl;
    public DateTime start;
    public DateTime end;
...

    ///   -1  100. -1 -  . 
    /// 100 -    
    /// 100-50 -    
    /// 50-0 -     ,    ,   
    public sbyte score()    {        ...    }

}

//  
internal class Empl:IEquatable<Empl>
{
    private readonly int id;
    public int daysNorm;
    public string fio;
    public Dep dep;
    public readonly List<Penalty> penalties;

    public int cntPlannedDays { get {
            int result = 0;
            foreach (Holiday h in holidays)
                result += (h.end - h.start).Days + 1;
            return result;
        } }

    public List<Holiday> holidays; 
    public sbyte score()    {       ...    }
}

//  
class Dep
{
    ///  -   
    public int maxDepAbcenceCnt { get {...        } }

    ///      
    public List<Tuple<DateTime,DateTime>> GetFreeIntervals()    {...    }
    public string name;
    public List<Empl> empls;

    public List<Penalty> penalties;
    public sbyte score()    {        ...    }
}


Cada uma das classes contém a função score () - a pontuação para os critérios do algoritmo, que é calculada com base na lista de penalidades.



As abelhas (sair) podem ser criadas, movidas, removidas e transformadas (redimensionadas). Após carregar as preferências dos trabalhadores nos períodos livres, os dias de férias não alocados dos trabalhadores são atribuídos aleatoriamente. Se o funcionário designou mais dias, suas férias serão reduzidas até que sejam normalizadas.



O problema é considerado resolvido se todos os dias de férias não planejados forem distribuídos, as preferências forem atendidas e as outras condições do problema forem atendidas. Na vida real, raramente acontece para agradar a todos, mas o algoritmo pode tentar encontrar a solução ideal. Para isso, a cada iteração, as classes avaliam o cumprimento das condições do problema e preenchem a lista de penalidades. Outras mutações serão escolhidas dependendo do número individual de penalidades e penalidades de classes adjacentes. Ao final de cada movimento de todas as abelhas, o algoritmo é testado quanto à convergência e a solução de maior sucesso é lembrada. A qualidade da solução é calculada com base nas penalidades de todas as abelhas. Se uma solução ideal não for encontrada, o algoritmo retornará o resultado com a menor penalidade.



//  
class Swarm
{
    public void toXlsx(string path){…}
    public List<Dep> deps;
    public List<Empl> empls;

    public List<Holiday> holidays;
    public sbyte _score = -127;
    // 
    public void findPenalties(){…}

    public void nextIteration()
    {
        resetScore();
        findPenalties();
        foreach (Empl e in empls)
        {
            foreach (Penalty p in e.penalties)
            {
                switch (p.name)
                {
                 case "PenaltyAboveNormalHolidayCnt": //  
                        break;
                 case "PenaltyNo14DaysPart"://       14 
                        break;
                 case "PenaltyBellowNormalHolidayCnt": //  
break;
                 default:
                        Log.WriteLine("     " + p.name);
                        break;
                }
            }
        }
    }

    //  
    public sbyte score(bool reCalculate=false)
    {
        if (_score != -127)
            return _score;
        if (reCalculate)
            resetScore();
        float resultH = 0,resultD=0,resultE=0;
        findPenalties();
        foreach (Holiday h in holidays)
        {
            resultH += (float)h.score();
        }
        resultH = resultH / (float)holidays.Count;
        foreach (Dep d in deps)
        {
            resultD += (float)d.score();
        }
        resultD = resultD / (float)deps.Count;
        foreach (Empl e in empls)
        {
            resultE += (float)e.score();
        }
        resultE = resultE / (float)empls.Count;

        _score = (sbyte)((resultH+resultD+resultE)/3);

        return _score;
    }

    public bool isConverged()
    {
        if (_score == -127)
            return false;
        findPenalties();
        foreach (Dep d in deps)
        {
            if (d.penaltyes.Count > 0)
                return false;
        }
        foreach(Empl e in empls)
        {
            if (e.penaltyes.Count > 0)
                return false;
        }
        foreach(Holiday h in holidays)
        {
            if (h.penalties.Count > 0)
                return false;
        }
        return true;
    }
}


A função findPenilities () é responsável por preencher a lista de penalidades para todos os objetos de enxame. A



classe de enxame também contém uma função de pontuação de qualidade que é calculada a partir das pontuações de todos os membros do enxame.



Após movimentar todas as abelhas, avalia-se a convergência do algoritmo, se o resultado desejado não for alcançado e o limite de iteração não for ultrapassado, a próxima iteração nextIteration () será lançada. No



nosso caso, muito depende da distribuição inicial de férias não planejadas, por isso decidiu-se iniciar o enxame em vários threads paralelos e escolher o melhor eles:



List<int> list = new List<int>();
for (int i = 1; i < CONST.SWAM_SIZE; i++)
    list.Add(i);
int bestScore = 0;
Parallel.ForEach(list, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, x => {
    Swarm iterSwarm = new Swarm(swarm);
    int currIter = 0;
    while (true)
    {
        if (iterSwarm.isConverged())
        {
            Console.WriteLine($"   {currIter}  score={iterSwarm.score()}");
            break;
        }
        if (currIter >= CONST.MAX_ITER_CNT)
        {
            Console.WriteLine("  ");
            break;
        }
        iterSwarm.nextIteration();
        currIter++;
        lock(isLock)
        {
            if (iterSwarm.score(true) > bestScore)
            {
                bestScore = iterSwarm.score();
                bestSwarm = new Swarm(iterSwarm);
            }
        }
    }


});
Console.WriteLine($"Source swarm score={swarm.score()}");
Console.WriteLine("");
            
Console.WriteLine($"Result bestSwarm={bestSwarm.score()}");
bestSwarm.toXlsx();






O algoritmo não é difícil de implementar e se resume principalmente a escrever uma função de mutação. O uso do algoritmo Bee Swarm é apropriado em problemas de otimização para os quais não há solução formalizada, e a enumeração de todas as opções e combinações não é apropriada devido ao seu grande número.



All Articles