memga 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memga-1.1.0/LICENSE +21 -0
- memga-1.1.0/PKG-INFO +180 -0
- memga-1.1.0/README.md +157 -0
- memga-1.1.0/pyproject.toml +40 -0
- memga-1.1.0/setup.cfg +4 -0
- memga-1.1.0/src/memga/__init__.py +25 -0
- memga-1.1.0/src/memga/api.py +161 -0
- memga-1.1.0/src/memga/busca.py +280 -0
- memga-1.1.0/src/memga/data/recruiting-large/alternatives.csv +301 -0
- memga-1.1.0/src/memga/data/recruiting-large/criteria.csv +9 -0
- memga-1.1.0/src/memga/data/recruiting-small/alternatives.csv +9 -0
- memga-1.1.0/src/memga/data/recruiting-small/complementarity.csv +7 -0
- memga-1.1.0/src/memga/data/recruiting-small/criteria.csv +7 -0
- memga-1.1.0/src/memga/pesos.py +40 -0
- memga-1.1.0/src/memga/problema.py +81 -0
- memga-1.1.0/src/memga/radar.py +73 -0
- memga-1.1.0/src/memga/regras.py +126 -0
- memga-1.1.0/src/memga.egg-info/PKG-INFO +180 -0
- memga-1.1.0/src/memga.egg-info/SOURCES.txt +29 -0
- memga-1.1.0/src/memga.egg-info/dependency_links.txt +1 -0
- memga-1.1.0/src/memga.egg-info/requires.txt +5 -0
- memga-1.1.0/src/memga.egg-info/top_level.txt +1 -0
- memga-1.1.0/tests/test_api.py +132 -0
- memga-1.1.0/tests/test_busca.py +169 -0
- memga-1.1.0/tests/test_pesos.py +57 -0
- memga-1.1.0/tests/test_problema.py +66 -0
- memga-1.1.0/tests/test_radar.py +42 -0
- memga-1.1.0/tests/test_recrutamento_grande.py +78 -0
- memga-1.1.0/tests/test_recrutamento_pequeno.py +208 -0
- memga-1.1.0/tests/test_referencia.py +18 -0
- memga-1.1.0/tests/test_regras.py +121 -0
memga-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sérgio Augusto Faria Salles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
memga-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memga
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Multiple Choice Method (MEM): select a set of alternatives that complement each other, by exhaustive search or a genetic algorithm
|
|
5
|
+
Author: Henrique Rego Monteiro da Hora
|
|
6
|
+
Author-email: Sérgio Augusto Faria Salles <safsalles@hotmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/safsalles/portfolio-ds/tree/main/mem-ga
|
|
9
|
+
Project-URL: Issues, https://github.com/safsalles/portfolio-ds/issues
|
|
10
|
+
Keywords: multicriteria,decision-making,genetic-algorithm,selection,mem
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=2.0
|
|
19
|
+
Requires-Dist: matplotlib>=3.8
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# mem-ga
|
|
25
|
+
|
|
26
|
+
*Leia em: [English](README.en.md)*
|
|
27
|
+
|
|
28
|
+
O Método de Escolha Múltipla (MEM) escolhe um conjunto de alternativas que se completam, em vez de pegar as melhores uma a uma. Serve para montar equipes, carteiras, comitês, qualquer escolha em que o grupo importa mais que cada parte.
|
|
29
|
+
|
|
30
|
+
Este repositório traz o método como um pacote Python, `memga`, com:
|
|
31
|
+
|
|
32
|
+
- as duas regras de nota: a da tese que propôs o MEM (`media`) e a adaptação da minha dissertação (`minimo`), com a mesma interface;
|
|
33
|
+
- duas buscas: testar todas as combinações, para problemas pequenos, e um algoritmo genético (MEM-GA), para os grandes;
|
|
34
|
+
- vagas por grupo (por exemplo, 2 analistas e 1 gestor), complementação e pesos calculados dos dados, e um gráfico de radar.
|
|
35
|
+
|
|
36
|
+
Dois exemplos fictícios de recrutamento mostram o uso: um pequeno, para acompanhar a conta, e um grande, com 300 candidatos. A aplicação em futebol, com os dados do Brasileirão 2017, fica na pasta vizinha, [football-analytics](../football-analytics/README.md).
|
|
37
|
+
|
|
38
|
+
## Instalação
|
|
39
|
+
|
|
40
|
+
Do PyPI:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install memga
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Para mexer no código, na pasta `mem-ga`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install -e ".[test]"
|
|
50
|
+
python -m pytest
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Uso
|
|
54
|
+
|
|
55
|
+
A função `memga()` faz tudo: monta a regra, roda a busca e compara com a escolha tradicional. Os dois exemplos fictícios vêm dentro do pacote.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from memga import memga, load_example
|
|
59
|
+
|
|
60
|
+
# 2 de 8 candidatos, testando todas as duplas
|
|
61
|
+
print(memga(load_example("small"), size=2))
|
|
62
|
+
|
|
63
|
+
# Equipe de 4 entre 300, com vagas por grupo, pelo algoritmo genético
|
|
64
|
+
resultado = memga(
|
|
65
|
+
load_example("large"), method="ga", tau=0.54,
|
|
66
|
+
quotas={"analyst": 2, "engineer": 1, "manager": 1}, seed=1,
|
|
67
|
+
)
|
|
68
|
+
print(resultado.team, resultado.score)
|
|
69
|
+
# ('P007', 'P038', 'P192', 'P276') 0.6785714285714286
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Os argumentos principais são `method` (`"exhaustive"` ou `"ga"`), `rule` (`"mean"`, a da tese, ou `"minimum"`), `tau`, `quotas` e `config` (um `GAConfig`). Sem `rule`, a busca exaustiva usa `"mean"` e o algoritmo genético usa `"minimum"`. O resultado traz `team`, `score`, `ranking` (na busca exaustiva), `history` (no algoritmo genético) e `traditional`.
|
|
73
|
+
|
|
74
|
+
Para usar os módulos separadamente, a partir da pasta `mem-ga`:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from memga.busca import run_ga
|
|
78
|
+
from memga.problema import load_problem
|
|
79
|
+
from memga.regras import build_rule
|
|
80
|
+
|
|
81
|
+
problem = load_problem("exemplos/recrutamento-grande/data")
|
|
82
|
+
rule = build_rule(problem, "minimo", tau=0.54)
|
|
83
|
+
vagas = {"analista": 2, "engenheiro": 1, "gestor": 1}
|
|
84
|
+
result = run_ga(rule, problem.groups, vagas, size=4, seed=2026)
|
|
85
|
+
print(sorted(problem.alternatives[i] for i in result.team), result.score)
|
|
86
|
+
# ['P007', 'P038', 'P192', 'P276'] 0.6785714285714286
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Módulo | O que faz |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `memga.problema` | `Problem` (alternativas, critérios, direção, pesos, grupos), `load_problem` para ler os CSVs e `normalize` |
|
|
92
|
+
| `memga.pesos` | Complementação calculada dos dados (1 − \|r\|, como no CRITIC, dentro de cada grupo) e pesos por grupo |
|
|
93
|
+
| `memga.regras` | As duas regras, `mean_rule` e `minimum_rule`, e `build_rule` para montar uma delas a partir de um `Problem` |
|
|
94
|
+
| `memga.busca` | `exhaustive` (todas as combinações), `run_ga` (algoritmo genético) e `traditional_choice` (os melhores de cada grupo, para comparar) |
|
|
95
|
+
| `memga.radar` | Gráfico de radar com um painel por conjunto |
|
|
96
|
+
|
|
97
|
+
## O método
|
|
98
|
+
|
|
99
|
+
1. **Normaliza as avaliações.** Cada critério vai para a faixa de 0 a 1, com 1 para a melhor avaliação. Nos critérios em que menos é melhor, a escala se inverte.
|
|
100
|
+
2. **Avalia o conjunto em grupos de critérios.** Para cada grupo de critérios, junta o desempenho do conjunto, os pesos e a complementação entre esses critérios num valor.
|
|
101
|
+
3. **Aplica o tau de corte.** O grupo de critérios conta 1 se o valor alcançar o tau, e 0 se não alcançar. É esse corte que impede um resultado muito alto de pagar um resultado baixo em outro grupo: o método não é compensatório.
|
|
102
|
+
4. **Dá a nota.** A nota é a fração dos grupos de critérios que alcançam o tau.
|
|
103
|
+
|
|
104
|
+
Com grupos de alternativas (cargos, posições), a complementação é calculada dentro de cada grupo, e cada grupo pesa mais os critérios em que se destaca.
|
|
105
|
+
|
|
106
|
+
## As duas regras
|
|
107
|
+
|
|
108
|
+
As duas recebem um conjunto e devolvem `(nota, desempate)`, por isso qualquer busca funciona com qualquer regra.
|
|
109
|
+
|
|
110
|
+
| | `media` (tese) | `minimo` (adaptação) |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| Critérios | Combinados n a n, com n = tamanho do conjunto | Combinados 2 a 2 |
|
|
113
|
+
| Dentro do grupo de critérios | Média do desempenho (Equação 3.4 da tese) | Mínimo do desempenho |
|
|
114
|
+
| Nota | Fração dos grupos de critérios que alcançam o tau | Fração dos pares que alcançam o tau |
|
|
115
|
+
| Desempate | Média de `mínimo(valor, tau)` nos grupos | Média de `mínimo(valor, tau)` nos pares |
|
|
116
|
+
|
|
117
|
+
**Por que a adaptação.** Na tese, os critérios são combinados na mesma quantidade das alternativas escolhidas. Num conjunto grande, como 10 de 18 critérios, cada combinação fica perto da média geral do conjunto, e as diferenças entre conjuntos somem. Com os critérios 2 a 2, cada par ainda mostra o que o conjunto tem e o que falta. E, com o mínimo no lugar da média, um critério forte não cobre um fraco dentro do par, então o método fica não compensatório também ali.
|
|
118
|
+
|
|
119
|
+
**O desempate da regra `media` é novo.** Na tese, ela só rodava testando todas as combinações, e o empate ficava na mesma posição. O algoritmo genético precisa de um desempate para ter direção entre conjuntos com a mesma nota, então a regra `media` usa o mesmo desempate da `minimo`: quanto falta, em média, para os grupos alcançarem o tau.
|
|
120
|
+
|
|
121
|
+
**Limite da regra `media`.** O número de grupos de critérios é C(critérios, n). Com 18 critérios e um conjunto de 10, são 43.758 grupos por avaliação, lento demais para o algoritmo genético. Acima de 10.000 grupos (`MAX_MEAN_GROUPS`), a regra recusa o problema com uma mensagem que diz os números e sugere a regra `minimo`. Testar todas as combinações também tem limite: acima de 20 milhões de avaliações, a busca exaustiva recusa o problema.
|
|
122
|
+
|
|
123
|
+
## Exemplo pequeno: 2 de 8 candidatos
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python exemplos/recrutamento-pequeno/exemplo.py
|
|
127
|
+
python exemplos/recrutamento-pequeno/exemplo.py --regra minimo
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
São 8 candidatos com notas em 6 critérios, e a busca testa todas as 28 duplas. Os candidatos A1 e A2 têm as melhores médias, mas são parecidos: fortes em planejamento, logística e qualidade, e medianos em custos e pessoas. O A3 é o contrário.
|
|
131
|
+
|
|
132
|
+
| Ordem | Média ponderada | MEM, regra `media` | % dos grupos | MEM, regra `minimo` | % dos pares |
|
|
133
|
+
| --- | --- | --- | --- | --- | --- |
|
|
134
|
+
| 1.º | A1 | A1, A3 | 66,7% | A1, A3 | 46,7% |
|
|
135
|
+
| 1.º | | A2, A3 | 66,7% | A2, A3 | 46,7% |
|
|
136
|
+
| 3.º | A2 | A1, A2 | 60,0% | A1, A2 | 33,3% |
|
|
137
|
+
|
|
138
|
+
A escolha tradicional fica com A1 e A2, os dois primeiros pela média ponderada. Nas duas regras, essa dupla fica em 3.º lugar, e as melhores juntam um deles com o A3, que cobre o que falta. Com tau 0,24, as duas regras concordam no ranking do topo. A regra `minimo` dá notas menores, porque cada par vale o critério mais fraco.
|
|
139
|
+
|
|
140
|
+
No radar, cada linha colorida é um candidato e a área cinza é a média da dupla. À esquerda, A1 e A2 quase se sobrepõem. À direita, A1 e A3 se completam.
|
|
141
|
+
|
|
142
|
+

|
|
143
|
+
|
|
144
|
+
O resultado depende do tau. Com a regra `media` e tau de 0,27 ou mais, o corte fica alto a ponto de só as notas muito altas de A1 e A2 passarem, e essa dupla volta ao 1.º lugar. Por isso o programa mostra o que muda com o tau um pouco acima e um pouco abaixo. Mais detalhes em [exemplos/recrutamento-pequeno](exemplos/recrutamento-pequeno/).
|
|
145
|
+
|
|
146
|
+
## Exemplo grande: equipe de 4 entre 300 candidatos
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
python exemplos/recrutamento-grande/exemplo.py
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
São 300 candidatos fictícios (150 analistas, 90 engenheiros e 60 gestores), com notas em 8 critérios, para uma equipe de 2 analistas, 1 engenheiro e 1 gestor. Seriam mais de 60 milhões de equipes possíveis, então a busca é feita pelo algoritmo genético. A execução leva cerca de 20 segundos.
|
|
153
|
+
|
|
154
|
+
| Equipe | Pessoas | Nota `media` | Nota `minimo` |
|
|
155
|
+
| --- | --- | --- | --- |
|
|
156
|
+
| Regra `media` | P050, P110 (analistas), P218 (engenheiro), P276 (gestor) | **50,0%** | 32,1% |
|
|
157
|
+
| Regra `minimo` | P007, P038 (analistas), P192 (engenheiro), P276 (gestor) | 24,3% | **67,9%** |
|
|
158
|
+
| Escolha tradicional | P038, P110 (analistas), P218 (engenheiro), P276 (gestor) | 48,6% | 53,6% |
|
|
159
|
+
|
|
160
|
+
A escolha tradicional pega os mais fortes de cada cargo. Cada regra acha uma equipe melhor que ela pela própria nota, e as duas regras escolhem equipes diferentes: a `media` troca um analista e fica perto da tradicional, e a `minimo` troca um analista e o engenheiro. A equipe da `minimo` perde um pouco em dados e programação e ganha em comunicação, negociação e organização: fica mais equilibrada, porque cada par de critérios vale o mais fraco dos dois. O gestor é o mesmo nas três.
|
|
161
|
+
|
|
162
|
+

|
|
163
|
+
|
|
164
|
+
O tau de cada regra foi escolhido para a nota não saturar: com o tau baixo demais, toda equipe passa em tudo; alto demais, ninguém passa. Testando de 0,01 a 1,00, ficou o valor em que a escolha tradicional chega mais perto de 50% (0,63 na `media` e 0,54 na `minimo`). Mais detalhes, e como os dados foram gerados, em [exemplos/recrutamento-grande](exemplos/recrutamento-grande/).
|
|
165
|
+
|
|
166
|
+
## Formato dos dados
|
|
167
|
+
|
|
168
|
+
`load_problem` lê uma pasta com estes arquivos:
|
|
169
|
+
|
|
170
|
+
- `criteria.csv`: `criterion`, `direction` (1 quando mais é melhor e 0 quando mais é pior) e `weight`.
|
|
171
|
+
- `alternatives.csv`: a coluna `alternative`, uma coluna por critério e, se houver vagas por grupo, a coluna `group`.
|
|
172
|
+
- `complementarity.csv` (opcional): a matriz de complementação entre critérios, com a diagonal vazia. Sem ela, a complementação é calculada dos dados.
|
|
173
|
+
|
|
174
|
+
## Conferência com a tese
|
|
175
|
+
|
|
176
|
+
A regra `media`, com a busca exaustiva, foi conferida com a aplicação numérica da tese, cujos dados não estão neste repositório. Na escolha de 2, com tau de 0,20, as 45 duplas do Quadro 3.9 bateram. Com tau de 0,19 e 0,21, 44 de 45 bateram nos Quadros 3.10 e 3.11, e a dupla diferente (A1, A8) tem um grupo de critérios a mais ou a menos. Na escolha de 3, os 40 conjuntos legíveis do Quadro 3.15 bateram. Os testes guardam o ranking do exemplo pequeno para que isso não mude sem aviso.
|
|
177
|
+
|
|
178
|
+
## Fonte
|
|
179
|
+
|
|
180
|
+
O MEM foi proposto na tese *Método de Escolha Múltipla: uma proposta multicritério para seleção em conjunto de mais de uma alternativa*, de Henrique Rego Monteiro da Hora (Universidade Federal Fluminense, 2013). A regra `minimo` e o algoritmo genético vêm da dissertação *MEM-GA: Método de Escolha Múltipla com Algoritmo Genético aplicado à formação de equipes de futebol*, de Sérgio Augusto Faria Salles (Instituto Federal Fluminense, 2018), escrita em MATLAB e trazida aqui para Python.
|
memga-1.1.0/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# mem-ga
|
|
2
|
+
|
|
3
|
+
*Leia em: [English](README.en.md)*
|
|
4
|
+
|
|
5
|
+
O Método de Escolha Múltipla (MEM) escolhe um conjunto de alternativas que se completam, em vez de pegar as melhores uma a uma. Serve para montar equipes, carteiras, comitês, qualquer escolha em que o grupo importa mais que cada parte.
|
|
6
|
+
|
|
7
|
+
Este repositório traz o método como um pacote Python, `memga`, com:
|
|
8
|
+
|
|
9
|
+
- as duas regras de nota: a da tese que propôs o MEM (`media`) e a adaptação da minha dissertação (`minimo`), com a mesma interface;
|
|
10
|
+
- duas buscas: testar todas as combinações, para problemas pequenos, e um algoritmo genético (MEM-GA), para os grandes;
|
|
11
|
+
- vagas por grupo (por exemplo, 2 analistas e 1 gestor), complementação e pesos calculados dos dados, e um gráfico de radar.
|
|
12
|
+
|
|
13
|
+
Dois exemplos fictícios de recrutamento mostram o uso: um pequeno, para acompanhar a conta, e um grande, com 300 candidatos. A aplicação em futebol, com os dados do Brasileirão 2017, fica na pasta vizinha, [football-analytics](../football-analytics/README.md).
|
|
14
|
+
|
|
15
|
+
## Instalação
|
|
16
|
+
|
|
17
|
+
Do PyPI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install memga
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Para mexer no código, na pasta `mem-ga`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -e ".[test]"
|
|
27
|
+
python -m pytest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Uso
|
|
31
|
+
|
|
32
|
+
A função `memga()` faz tudo: monta a regra, roda a busca e compara com a escolha tradicional. Os dois exemplos fictícios vêm dentro do pacote.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from memga import memga, load_example
|
|
36
|
+
|
|
37
|
+
# 2 de 8 candidatos, testando todas as duplas
|
|
38
|
+
print(memga(load_example("small"), size=2))
|
|
39
|
+
|
|
40
|
+
# Equipe de 4 entre 300, com vagas por grupo, pelo algoritmo genético
|
|
41
|
+
resultado = memga(
|
|
42
|
+
load_example("large"), method="ga", tau=0.54,
|
|
43
|
+
quotas={"analyst": 2, "engineer": 1, "manager": 1}, seed=1,
|
|
44
|
+
)
|
|
45
|
+
print(resultado.team, resultado.score)
|
|
46
|
+
# ('P007', 'P038', 'P192', 'P276') 0.6785714285714286
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Os argumentos principais são `method` (`"exhaustive"` ou `"ga"`), `rule` (`"mean"`, a da tese, ou `"minimum"`), `tau`, `quotas` e `config` (um `GAConfig`). Sem `rule`, a busca exaustiva usa `"mean"` e o algoritmo genético usa `"minimum"`. O resultado traz `team`, `score`, `ranking` (na busca exaustiva), `history` (no algoritmo genético) e `traditional`.
|
|
50
|
+
|
|
51
|
+
Para usar os módulos separadamente, a partir da pasta `mem-ga`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from memga.busca import run_ga
|
|
55
|
+
from memga.problema import load_problem
|
|
56
|
+
from memga.regras import build_rule
|
|
57
|
+
|
|
58
|
+
problem = load_problem("exemplos/recrutamento-grande/data")
|
|
59
|
+
rule = build_rule(problem, "minimo", tau=0.54)
|
|
60
|
+
vagas = {"analista": 2, "engenheiro": 1, "gestor": 1}
|
|
61
|
+
result = run_ga(rule, problem.groups, vagas, size=4, seed=2026)
|
|
62
|
+
print(sorted(problem.alternatives[i] for i in result.team), result.score)
|
|
63
|
+
# ['P007', 'P038', 'P192', 'P276'] 0.6785714285714286
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Módulo | O que faz |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `memga.problema` | `Problem` (alternativas, critérios, direção, pesos, grupos), `load_problem` para ler os CSVs e `normalize` |
|
|
69
|
+
| `memga.pesos` | Complementação calculada dos dados (1 − \|r\|, como no CRITIC, dentro de cada grupo) e pesos por grupo |
|
|
70
|
+
| `memga.regras` | As duas regras, `mean_rule` e `minimum_rule`, e `build_rule` para montar uma delas a partir de um `Problem` |
|
|
71
|
+
| `memga.busca` | `exhaustive` (todas as combinações), `run_ga` (algoritmo genético) e `traditional_choice` (os melhores de cada grupo, para comparar) |
|
|
72
|
+
| `memga.radar` | Gráfico de radar com um painel por conjunto |
|
|
73
|
+
|
|
74
|
+
## O método
|
|
75
|
+
|
|
76
|
+
1. **Normaliza as avaliações.** Cada critério vai para a faixa de 0 a 1, com 1 para a melhor avaliação. Nos critérios em que menos é melhor, a escala se inverte.
|
|
77
|
+
2. **Avalia o conjunto em grupos de critérios.** Para cada grupo de critérios, junta o desempenho do conjunto, os pesos e a complementação entre esses critérios num valor.
|
|
78
|
+
3. **Aplica o tau de corte.** O grupo de critérios conta 1 se o valor alcançar o tau, e 0 se não alcançar. É esse corte que impede um resultado muito alto de pagar um resultado baixo em outro grupo: o método não é compensatório.
|
|
79
|
+
4. **Dá a nota.** A nota é a fração dos grupos de critérios que alcançam o tau.
|
|
80
|
+
|
|
81
|
+
Com grupos de alternativas (cargos, posições), a complementação é calculada dentro de cada grupo, e cada grupo pesa mais os critérios em que se destaca.
|
|
82
|
+
|
|
83
|
+
## As duas regras
|
|
84
|
+
|
|
85
|
+
As duas recebem um conjunto e devolvem `(nota, desempate)`, por isso qualquer busca funciona com qualquer regra.
|
|
86
|
+
|
|
87
|
+
| | `media` (tese) | `minimo` (adaptação) |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| Critérios | Combinados n a n, com n = tamanho do conjunto | Combinados 2 a 2 |
|
|
90
|
+
| Dentro do grupo de critérios | Média do desempenho (Equação 3.4 da tese) | Mínimo do desempenho |
|
|
91
|
+
| Nota | Fração dos grupos de critérios que alcançam o tau | Fração dos pares que alcançam o tau |
|
|
92
|
+
| Desempate | Média de `mínimo(valor, tau)` nos grupos | Média de `mínimo(valor, tau)` nos pares |
|
|
93
|
+
|
|
94
|
+
**Por que a adaptação.** Na tese, os critérios são combinados na mesma quantidade das alternativas escolhidas. Num conjunto grande, como 10 de 18 critérios, cada combinação fica perto da média geral do conjunto, e as diferenças entre conjuntos somem. Com os critérios 2 a 2, cada par ainda mostra o que o conjunto tem e o que falta. E, com o mínimo no lugar da média, um critério forte não cobre um fraco dentro do par, então o método fica não compensatório também ali.
|
|
95
|
+
|
|
96
|
+
**O desempate da regra `media` é novo.** Na tese, ela só rodava testando todas as combinações, e o empate ficava na mesma posição. O algoritmo genético precisa de um desempate para ter direção entre conjuntos com a mesma nota, então a regra `media` usa o mesmo desempate da `minimo`: quanto falta, em média, para os grupos alcançarem o tau.
|
|
97
|
+
|
|
98
|
+
**Limite da regra `media`.** O número de grupos de critérios é C(critérios, n). Com 18 critérios e um conjunto de 10, são 43.758 grupos por avaliação, lento demais para o algoritmo genético. Acima de 10.000 grupos (`MAX_MEAN_GROUPS`), a regra recusa o problema com uma mensagem que diz os números e sugere a regra `minimo`. Testar todas as combinações também tem limite: acima de 20 milhões de avaliações, a busca exaustiva recusa o problema.
|
|
99
|
+
|
|
100
|
+
## Exemplo pequeno: 2 de 8 candidatos
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python exemplos/recrutamento-pequeno/exemplo.py
|
|
104
|
+
python exemplos/recrutamento-pequeno/exemplo.py --regra minimo
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
São 8 candidatos com notas em 6 critérios, e a busca testa todas as 28 duplas. Os candidatos A1 e A2 têm as melhores médias, mas são parecidos: fortes em planejamento, logística e qualidade, e medianos em custos e pessoas. O A3 é o contrário.
|
|
108
|
+
|
|
109
|
+
| Ordem | Média ponderada | MEM, regra `media` | % dos grupos | MEM, regra `minimo` | % dos pares |
|
|
110
|
+
| --- | --- | --- | --- | --- | --- |
|
|
111
|
+
| 1.º | A1 | A1, A3 | 66,7% | A1, A3 | 46,7% |
|
|
112
|
+
| 1.º | | A2, A3 | 66,7% | A2, A3 | 46,7% |
|
|
113
|
+
| 3.º | A2 | A1, A2 | 60,0% | A1, A2 | 33,3% |
|
|
114
|
+
|
|
115
|
+
A escolha tradicional fica com A1 e A2, os dois primeiros pela média ponderada. Nas duas regras, essa dupla fica em 3.º lugar, e as melhores juntam um deles com o A3, que cobre o que falta. Com tau 0,24, as duas regras concordam no ranking do topo. A regra `minimo` dá notas menores, porque cada par vale o critério mais fraco.
|
|
116
|
+
|
|
117
|
+
No radar, cada linha colorida é um candidato e a área cinza é a média da dupla. À esquerda, A1 e A2 quase se sobrepõem. À direita, A1 e A3 se completam.
|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
O resultado depende do tau. Com a regra `media` e tau de 0,27 ou mais, o corte fica alto a ponto de só as notas muito altas de A1 e A2 passarem, e essa dupla volta ao 1.º lugar. Por isso o programa mostra o que muda com o tau um pouco acima e um pouco abaixo. Mais detalhes em [exemplos/recrutamento-pequeno](exemplos/recrutamento-pequeno/).
|
|
122
|
+
|
|
123
|
+
## Exemplo grande: equipe de 4 entre 300 candidatos
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python exemplos/recrutamento-grande/exemplo.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
São 300 candidatos fictícios (150 analistas, 90 engenheiros e 60 gestores), com notas em 8 critérios, para uma equipe de 2 analistas, 1 engenheiro e 1 gestor. Seriam mais de 60 milhões de equipes possíveis, então a busca é feita pelo algoritmo genético. A execução leva cerca de 20 segundos.
|
|
130
|
+
|
|
131
|
+
| Equipe | Pessoas | Nota `media` | Nota `minimo` |
|
|
132
|
+
| --- | --- | --- | --- |
|
|
133
|
+
| Regra `media` | P050, P110 (analistas), P218 (engenheiro), P276 (gestor) | **50,0%** | 32,1% |
|
|
134
|
+
| Regra `minimo` | P007, P038 (analistas), P192 (engenheiro), P276 (gestor) | 24,3% | **67,9%** |
|
|
135
|
+
| Escolha tradicional | P038, P110 (analistas), P218 (engenheiro), P276 (gestor) | 48,6% | 53,6% |
|
|
136
|
+
|
|
137
|
+
A escolha tradicional pega os mais fortes de cada cargo. Cada regra acha uma equipe melhor que ela pela própria nota, e as duas regras escolhem equipes diferentes: a `media` troca um analista e fica perto da tradicional, e a `minimo` troca um analista e o engenheiro. A equipe da `minimo` perde um pouco em dados e programação e ganha em comunicação, negociação e organização: fica mais equilibrada, porque cada par de critérios vale o mais fraco dos dois. O gestor é o mesmo nas três.
|
|
138
|
+
|
|
139
|
+

|
|
140
|
+
|
|
141
|
+
O tau de cada regra foi escolhido para a nota não saturar: com o tau baixo demais, toda equipe passa em tudo; alto demais, ninguém passa. Testando de 0,01 a 1,00, ficou o valor em que a escolha tradicional chega mais perto de 50% (0,63 na `media` e 0,54 na `minimo`). Mais detalhes, e como os dados foram gerados, em [exemplos/recrutamento-grande](exemplos/recrutamento-grande/).
|
|
142
|
+
|
|
143
|
+
## Formato dos dados
|
|
144
|
+
|
|
145
|
+
`load_problem` lê uma pasta com estes arquivos:
|
|
146
|
+
|
|
147
|
+
- `criteria.csv`: `criterion`, `direction` (1 quando mais é melhor e 0 quando mais é pior) e `weight`.
|
|
148
|
+
- `alternatives.csv`: a coluna `alternative`, uma coluna por critério e, se houver vagas por grupo, a coluna `group`.
|
|
149
|
+
- `complementarity.csv` (opcional): a matriz de complementação entre critérios, com a diagonal vazia. Sem ela, a complementação é calculada dos dados.
|
|
150
|
+
|
|
151
|
+
## Conferência com a tese
|
|
152
|
+
|
|
153
|
+
A regra `media`, com a busca exaustiva, foi conferida com a aplicação numérica da tese, cujos dados não estão neste repositório. Na escolha de 2, com tau de 0,20, as 45 duplas do Quadro 3.9 bateram. Com tau de 0,19 e 0,21, 44 de 45 bateram nos Quadros 3.10 e 3.11, e a dupla diferente (A1, A8) tem um grupo de critérios a mais ou a menos. Na escolha de 3, os 40 conjuntos legíveis do Quadro 3.15 bateram. Os testes guardam o ranking do exemplo pequeno para que isso não mude sem aviso.
|
|
154
|
+
|
|
155
|
+
## Fonte
|
|
156
|
+
|
|
157
|
+
O MEM foi proposto na tese *Método de Escolha Múltipla: uma proposta multicritério para seleção em conjunto de mais de uma alternativa*, de Henrique Rego Monteiro da Hora (Universidade Federal Fluminense, 2013). A regra `minimo` e o algoritmo genético vêm da dissertação *MEM-GA: Método de Escolha Múltipla com Algoritmo Genético aplicado à formação de equipes de futebol*, de Sérgio Augusto Faria Salles (Instituto Federal Fluminense, 2018), escrita em MATLAB e trazida aqui para Python.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "memga"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Multiple Choice Method (MEM): select a set of alternatives that complement each other, by exhaustive search or a genetic algorithm"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Sérgio Augusto Faria Salles", email = "safsalles@hotmail.com" },
|
|
15
|
+
{ name = "Henrique Rego Monteiro da Hora" },
|
|
16
|
+
]
|
|
17
|
+
keywords = ["multicriteria", "decision-making", "genetic-algorithm", "selection", "mem"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["numpy>=2.0", "matplotlib>=3.8"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/safsalles/portfolio-ds/tree/main/mem-ga"
|
|
28
|
+
Issues = "https://github.com/safsalles/portfolio-ds/issues"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
test = ["pytest>=8.0"]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["src"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.package-data]
|
|
37
|
+
memga = ["data/*/*.csv"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
memga-1.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""MEM: Método de Escolha Múltipla, com busca exaustiva ou por algoritmo genético.
|
|
2
|
+
|
|
3
|
+
Uso mais simples:
|
|
4
|
+
|
|
5
|
+
from memga import memga, load_example
|
|
6
|
+
print(memga(load_example("small"), size=2))
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from memga.api import MemgaResult, Ranked, load_example, memga
|
|
10
|
+
from memga.busca import GAConfig, traditional_choice
|
|
11
|
+
from memga.problema import Problem, load_problem
|
|
12
|
+
|
|
13
|
+
__version__ = "1.1.0"
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"GAConfig",
|
|
17
|
+
"MemgaResult",
|
|
18
|
+
"Problem",
|
|
19
|
+
"Ranked",
|
|
20
|
+
"__version__",
|
|
21
|
+
"load_example",
|
|
22
|
+
"load_problem",
|
|
23
|
+
"memga",
|
|
24
|
+
"traditional_choice",
|
|
25
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""A função de entrada do pacote: `memga()` escolhe um conjunto de alternativas.
|
|
2
|
+
|
|
3
|
+
Ela junta o que está nos outros módulos: monta a regra de nota, roda a busca exaustiva ou
|
|
4
|
+
o algoritmo genético e compara o resultado com a escolha tradicional.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from memga.busca import GAConfig, exhaustive, run_ga, traditional_choice
|
|
13
|
+
from memga.problema import Problem, load_problem, normalize
|
|
14
|
+
from memga.regras import build_rule
|
|
15
|
+
|
|
16
|
+
DEFAULT_TAU = 0.24
|
|
17
|
+
METHODS = ("exhaustive", "ga")
|
|
18
|
+
RULES = ("mean", "minimum")
|
|
19
|
+
_RULE_NAMES = {"mean": "media", "minimum": "minimo"}
|
|
20
|
+
DATA_DIR = Path(__file__).resolve().parent / "data"
|
|
21
|
+
EXAMPLES = {"small": "recruiting-small", "large": "recruiting-large"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class Ranked:
|
|
26
|
+
position: int
|
|
27
|
+
alternatives: tuple[str, ...]
|
|
28
|
+
score: float
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MemgaResult:
|
|
33
|
+
"""O resultado de `memga()`.
|
|
34
|
+
|
|
35
|
+
`team` e `score` valem para os dois métodos: com a busca exaustiva, é o primeiro
|
|
36
|
+
conjunto da ordem. `ranking` só existe na busca exaustiva e `history` só no algoritmo
|
|
37
|
+
genético. `traditional` é a escolha tradicional e a nota dela na mesma regra.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
team: tuple[str, ...]
|
|
41
|
+
score: float
|
|
42
|
+
method: str
|
|
43
|
+
rule: str
|
|
44
|
+
tau: float
|
|
45
|
+
size: int
|
|
46
|
+
problem: Problem
|
|
47
|
+
traditional: tuple[tuple[str, ...], float]
|
|
48
|
+
quotas: dict | None = None
|
|
49
|
+
seed: int | None = None
|
|
50
|
+
ranking: list[Ranked] = field(default_factory=list)
|
|
51
|
+
history: list[float] = field(default_factory=list)
|
|
52
|
+
payment: np.ndarray | None = None
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
search = "exhaustive search" if self.method == "exhaustive" else "genetic algorithm"
|
|
56
|
+
lines = [
|
|
57
|
+
f"Multiple Choice Method: {search}, {self.rule} rule, tau {self.tau:g}",
|
|
58
|
+
f"{len(self.problem.alternatives)} alternatives, {len(self.problem.criteria)} criteria, "
|
|
59
|
+
f"selecting {self.size}",
|
|
60
|
+
"",
|
|
61
|
+
]
|
|
62
|
+
if self.method == "exhaustive":
|
|
63
|
+
lines.append(f"Best sets ({len(self.ranking)} tested):")
|
|
64
|
+
for item in self.ranking[:10]:
|
|
65
|
+
lines.append(f" {item.position:>3}. {', '.join(item.alternatives):<24} {item.score:6.1%}")
|
|
66
|
+
if len(self.ranking) > 10:
|
|
67
|
+
lines.append(f" ... {len(self.ranking) - 10} more")
|
|
68
|
+
else:
|
|
69
|
+
lines.append(f"Selected: {', '.join(self.team)} ({self.score:.1%})")
|
|
70
|
+
if self.seed is not None:
|
|
71
|
+
lines.append(f"Seed: {self.seed}")
|
|
72
|
+
names, score = self.traditional
|
|
73
|
+
lines += ["", f"Traditional choice (best one by one): {', '.join(names)} ({score:.1%})"]
|
|
74
|
+
if self.ranking and len(names) == self.size:
|
|
75
|
+
place = next((r.position for r in self.ranking if set(r.alternatives) == set(names)), None)
|
|
76
|
+
if place:
|
|
77
|
+
lines.append(f"It ranks {place} in the Multiple Choice Method.")
|
|
78
|
+
return "\n".join(lines)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_example(name="small"):
|
|
82
|
+
"""Carrega um dos exemplos que vêm com o pacote: "small" (8 candidatos) ou "large" (300)."""
|
|
83
|
+
if name not in EXAMPLES:
|
|
84
|
+
raise ValueError(f"Exemplo desconhecido: {name!r}. Use 'small' ou 'large'.")
|
|
85
|
+
return load_problem(DATA_DIR / EXAMPLES[name])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _check_quotas(quotas, problem):
|
|
89
|
+
if quotas is None:
|
|
90
|
+
return None
|
|
91
|
+
if problem.groups is None:
|
|
92
|
+
raise ValueError("`quotas` precisa de `groups` no problema.")
|
|
93
|
+
unknown = sorted(set(quotas) - set(problem.groups.tolist()))
|
|
94
|
+
if unknown:
|
|
95
|
+
raise ValueError(f"`quotas` cita grupos que não estão no problema: {', '.join(unknown)}.")
|
|
96
|
+
if any(int(q) != q or q < 0 for q in quotas.values()):
|
|
97
|
+
raise ValueError("`quotas` precisa ter números inteiros, de zero para cima.")
|
|
98
|
+
quotas = {group: int(q) for group, q in quotas.items() if q > 0}
|
|
99
|
+
if not quotas:
|
|
100
|
+
raise ValueError("`quotas` precisa pedir pelo menos 1 alternativa.")
|
|
101
|
+
return quotas
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def memga(problem, size=None, method="exhaustive", rule=None, tau=DEFAULT_TAU, quotas=None,
|
|
105
|
+
config=None, seed=None, on_progress=None):
|
|
106
|
+
"""Escolhe o conjunto de alternativas que mais se complementam.
|
|
107
|
+
|
|
108
|
+
Parâmetros:
|
|
109
|
+
problem: um `Problem`, de `load_problem` ou `load_example`.
|
|
110
|
+
size: quantas alternativas escolher. Pode faltar quando há `quotas`.
|
|
111
|
+
method: "exhaustive" (testa todas as combinações e devolve a ordem completa) ou
|
|
112
|
+
"ga" (algoritmo genético, para problemas grandes demais para testar tudo).
|
|
113
|
+
rule: "mean" (a regra da tese) ou "minimum" (critérios 2 a 2, com o menor dos dois).
|
|
114
|
+
Por padrão, "mean" na busca exaustiva e "minimum" no algoritmo genético.
|
|
115
|
+
tau: o corte; uma combinação de critérios conta quando o valor chega nele.
|
|
116
|
+
quotas: vagas por grupo, como {"analyst": 2, "manager": 1}. Precisa de `groups`.
|
|
117
|
+
config: um `GAConfig`, para ajustar o algoritmo genético.
|
|
118
|
+
seed: semente, para repetir um resultado do algoritmo genético.
|
|
119
|
+
on_progress: função (iteração, total, melhor nota) chamada durante o algoritmo genético.
|
|
120
|
+
"""
|
|
121
|
+
if method not in METHODS:
|
|
122
|
+
raise ValueError(f"Método desconhecido: {method!r}. Use 'exhaustive' ou 'ga'.")
|
|
123
|
+
if rule is None:
|
|
124
|
+
rule = "mean" if method == "exhaustive" else "minimum"
|
|
125
|
+
if rule not in RULES:
|
|
126
|
+
raise ValueError(f"Regra desconhecida: {rule!r}. Use 'mean' ou 'minimum'.")
|
|
127
|
+
|
|
128
|
+
quotas = _check_quotas(quotas, problem)
|
|
129
|
+
if size is None:
|
|
130
|
+
if quotas is None:
|
|
131
|
+
raise ValueError("Informe `size`, quantas alternativas escolher.")
|
|
132
|
+
size = sum(quotas.values())
|
|
133
|
+
elif int(size) != size or size < 1:
|
|
134
|
+
raise ValueError(f"`size` precisa ser um número inteiro de pelo menos 1, e veio {size}.")
|
|
135
|
+
elif quotas is not None and size != sum(quotas.values()):
|
|
136
|
+
raise ValueError(f"`size` é {size}, mas `quotas` soma {sum(quotas.values())}.")
|
|
137
|
+
size = int(size)
|
|
138
|
+
if size > len(problem.alternatives):
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"O conjunto pede {size} alternativas, mas os dados têm só {len(problem.alternatives)}."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
fitted = build_rule(problem, _RULE_NAMES[rule], tau, size=size)
|
|
144
|
+
names = problem.alternatives
|
|
145
|
+
normalized = normalize(problem.values, problem.direction)
|
|
146
|
+
weights = problem.weights if problem.weights.sum() > 0 else np.ones_like(problem.weights)
|
|
147
|
+
chosen = traditional_choice(normalized * weights, size, problem.groups, quotas)
|
|
148
|
+
traditional = (tuple(names[i] for i in chosen), fitted.rate(tuple(sorted(chosen)))[0])
|
|
149
|
+
|
|
150
|
+
result = dict(method=method, rule=rule, tau=tau, size=size, problem=problem,
|
|
151
|
+
traditional=traditional, quotas=quotas, seed=seed, payment=fitted.payment)
|
|
152
|
+
if method == "exhaustive":
|
|
153
|
+
ranking = [
|
|
154
|
+
Ranked(c.position, tuple(names[i] for i in c.alternatives), c.score)
|
|
155
|
+
for c in exhaustive(fitted, len(names), size, problem.groups, quotas)
|
|
156
|
+
]
|
|
157
|
+
return MemgaResult(team=ranking[0].alternatives, score=ranking[0].score, ranking=ranking, **result)
|
|
158
|
+
|
|
159
|
+
found = run_ga(fitted, problem.groups, quotas, size, config or GAConfig(), seed, on_progress)
|
|
160
|
+
return MemgaResult(team=tuple(names[i] for i in found.team), score=found.score,
|
|
161
|
+
history=found.history, **result)
|