dega2 0.1.0__py3-none-any.whl
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.
- dega2/BuildpopGA.py +51 -0
- dega2/ConsChek.py +13 -0
- dega2/ConsChekGA4.py +19 -0
- dega2/DomChek.py +19 -0
- dega2/DomChekGA4.py +78 -0
- dega2/GA4.py +64 -0
- dega2/GenPop.py +32 -0
- dega2/__init__.py +9 -0
- dega2/__main__.py +4 -0
- dega2/cli.py +139 -0
- dega2/entree.py +180 -0
- dega2/example.py +29 -0
- dega2/influ_cof.py +29 -0
- dega2/plotting.py +32 -0
- dega2/problems.py +15 -0
- dega2/solver.py +120 -0
- dega2/upload_cofGA.py +72 -0
- dega2-0.1.0.dist-info/METADATA +122 -0
- dega2-0.1.0.dist-info/RECORD +23 -0
- dega2-0.1.0.dist-info/WHEEL +5 -0
- dega2-0.1.0.dist-info/entry_points.txt +2 -0
- dega2-0.1.0.dist-info/licenses/LICENSE +21 -0
- dega2-0.1.0.dist-info/top_level.txt +1 -0
dega2/BuildpopGA.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from . import ConsChekGA4, DomChekGA4
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def explorer(population1, n_population, explor, p_cof1, index_p, p_cofphi1, index_phi,
|
|
7
|
+
n_pop, n_pop_phi):
|
|
8
|
+
"""Phase d'exploration, vectorisée : pour chaque variable j, on ajoute +1 ou -1
|
|
9
|
+
sur toute la ligne concernée (au lieu de boucler colonne par colonne)."""
|
|
10
|
+
blocs_phi, blocs_p = [], []
|
|
11
|
+
tab_ind1, tab_ind1_ph = [], []
|
|
12
|
+
n_p = n_pop - n_pop_phi # nombre de colonnes modifiées dans la 2e moitié
|
|
13
|
+
|
|
14
|
+
for j in range(explor):
|
|
15
|
+
a1 = np.array(population1[:, 0:n_pop_phi])
|
|
16
|
+
a2 = np.array(population1[:, n_pop_phi:n_population])
|
|
17
|
+
|
|
18
|
+
r_phi = index_phi[0, j]
|
|
19
|
+
r_p = index_p[0, j]
|
|
20
|
+
a1[r_phi, :] += 1 if p_cofphi1[2, j] < 0 else -1
|
|
21
|
+
a2[r_p, :n_p] += 1 if p_cof1[2, j] < 0 else -1
|
|
22
|
+
|
|
23
|
+
tab_ind1_ph.extend([r_phi] * n_pop_phi)
|
|
24
|
+
tab_ind1.extend([r_p] * n_p)
|
|
25
|
+
blocs_phi.append(a1)
|
|
26
|
+
blocs_p.append(a2)
|
|
27
|
+
|
|
28
|
+
a3 = np.concatenate(blocs_phi, axis=1) # une seule concaténation
|
|
29
|
+
a4 = np.concatenate(blocs_p, axis=1)
|
|
30
|
+
return a3, a4, tab_ind1, tab_ind1_ph
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def Build_PT(population1, n_population, explor, p_cof1, index_p, p_cofphi1, index_phi,
|
|
34
|
+
phi, alpha, a, b, c, n_pop, n_pop_phi, n):
|
|
35
|
+
|
|
36
|
+
###################### Variables exploration ##############################
|
|
37
|
+
a3, a4, tab_ind1, tab_ind1_ph = explorer(
|
|
38
|
+
population1, n_population, explor, p_cof1, index_p, p_cofphi1, index_phi,
|
|
39
|
+
n_pop, n_pop_phi)
|
|
40
|
+
|
|
41
|
+
############ Check the constraints and dominance of new populations ########
|
|
42
|
+
tab_ind11 = tab_ind1.copy()
|
|
43
|
+
tab_ind11_phi = tab_ind1_ph.copy()
|
|
44
|
+
|
|
45
|
+
[pop1, pop1_phi, tab_ind11, tab_ind11_phi] = ConsChekGA4.chek(
|
|
46
|
+
a3, a4, a, b, tab_ind11_phi, tab_ind11)
|
|
47
|
+
|
|
48
|
+
[pop2, tab_ind11_phi, tab_ind11] = DomChekGA4.nd_update(
|
|
49
|
+
c, population1, pop1, pop1_phi, tab_ind11, tab_ind11_phi)
|
|
50
|
+
|
|
51
|
+
return pop2, tab_ind1, tab_ind11, tab_ind1_ph, tab_ind11_phi
|
dega2/ConsChek.py
ADDED
dega2/ConsChekGA4.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _realisables(a, b, pop):
|
|
5
|
+
"""Indices des colonnes de `pop` qui vérifient a @ x <= b (test fait pour toutes
|
|
6
|
+
les colonnes en un seul produit matriciel)."""
|
|
7
|
+
if pop.shape[1] == 0:
|
|
8
|
+
return np.arange(0)
|
|
9
|
+
return np.flatnonzero((a @ pop <= b).all(axis=0))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def chek(pop_phi, pop, a, b, tab_ind11_phi, tab_ind11):
|
|
13
|
+
idx = _realisables(a, b, pop)
|
|
14
|
+
idx_phi = _realisables(a, b, pop_phi)
|
|
15
|
+
|
|
16
|
+
return (pop[:, idx],
|
|
17
|
+
pop_phi[:, idx_phi],
|
|
18
|
+
[tab_ind11[k] for k in idx],
|
|
19
|
+
[tab_ind11_phi[k] for k in idx_phi])
|
dega2/DomChek.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def non_dominance(c, pop):
|
|
5
|
+
obj_val = np.dot(c, pop)
|
|
6
|
+
idx = np.arange(obj_val.shape[1])
|
|
7
|
+
i = 0
|
|
8
|
+
while i < len(idx) - 1:
|
|
9
|
+
oi = obj_val[:, idx[i]][:, None]
|
|
10
|
+
reste = obj_val[:, idx[i + 1:]]
|
|
11
|
+
le = (oi <= reste).all(axis=0) # i domine (faiblement) j -> j supprimé
|
|
12
|
+
gt = (oi > reste).all(axis=0) # j domine strictement i -> i supprimé
|
|
13
|
+
if gt.any():
|
|
14
|
+
jg = int(gt.argmax()) # premier j qui domine i
|
|
15
|
+
idx = np.delete(idx, np.append(np.flatnonzero(le[:jg]) + i + 1, i))
|
|
16
|
+
else:
|
|
17
|
+
idx = np.delete(idx, np.flatnonzero(le) + i + 1)
|
|
18
|
+
i += 1
|
|
19
|
+
return pop[:, idx]
|
dega2/DomChekGA4.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _indices_non_domines(obj):
|
|
5
|
+
"""Équivalent exact de la boucle de `non_dominance`, mais sans np.delete répétés
|
|
6
|
+
et avec la comparaison de i contre tous les j restants faite en une seule fois.
|
|
7
|
+
Renvoie les indices (dans l'ordre d'origine) des colonnes conservées."""
|
|
8
|
+
idx = np.arange(obj.shape[1])
|
|
9
|
+
i = 0
|
|
10
|
+
while i < len(idx) - 1:
|
|
11
|
+
oi = obj[:, idx[i]][:, None]
|
|
12
|
+
reste = obj[:, idx[i + 1:]]
|
|
13
|
+
le = (oi <= reste).all(axis=0) # i domine (faiblement) j -> j supprimé
|
|
14
|
+
gt = (oi > reste).all(axis=0) # j domine strictement i -> i supprimé
|
|
15
|
+
if gt.any():
|
|
16
|
+
jg = int(gt.argmax()) # premier j qui domine i
|
|
17
|
+
a_supprimer = np.append(np.flatnonzero(le[:jg]) + i + 1, i)
|
|
18
|
+
idx = np.delete(idx, a_supprimer) # i reste à la même position
|
|
19
|
+
else:
|
|
20
|
+
idx = np.delete(idx, np.flatnonzero(le) + i + 1)
|
|
21
|
+
i += 1
|
|
22
|
+
return idx
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def non_dominance(c, pop, tab_ind3):
|
|
26
|
+
obj_val = np.dot(c, pop)
|
|
27
|
+
idx = _indices_non_domines(obj_val)
|
|
28
|
+
return pop[:, idx], [tab_ind3[k] for k in idx], obj_val[:, idx]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _filtrer_contre(obj_p, idx_p, obj_ref, idx_ref, limite_fixe=None):
|
|
32
|
+
"""Compare chaque individu de la population (idx_p) à l'ensemble de référence
|
|
33
|
+
(idx_ref) : un individu de référence dominé est supprimé, et l'individu de la
|
|
34
|
+
population est supprimé dès qu'un élément de référence le domine strictement.
|
|
35
|
+
`limite_fixe` reproduit la borne de balayage de la 2e boucle d'origine."""
|
|
36
|
+
i = 0
|
|
37
|
+
while i < len(idx_p):
|
|
38
|
+
oi = obj_p[:, idx_p[i]][:, None]
|
|
39
|
+
L = len(idx_ref) if limite_fixe is None else min(limite_fixe, len(idx_ref))
|
|
40
|
+
ref = obj_ref[:, idx_ref[:L]]
|
|
41
|
+
le = (oi <= ref).all(axis=0)
|
|
42
|
+
gt = (oi > ref).all(axis=0)
|
|
43
|
+
if gt.any():
|
|
44
|
+
jg = int(gt.argmax())
|
|
45
|
+
idx_ref = np.delete(idx_ref, np.flatnonzero(le[:jg]))
|
|
46
|
+
idx_p = np.delete(idx_p, i) # i reste à la même position
|
|
47
|
+
else:
|
|
48
|
+
idx_ref = np.delete(idx_ref, np.flatnonzero(le))
|
|
49
|
+
i += 1
|
|
50
|
+
return idx_p, idx_ref
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def nd_update(c, population, pop1, pop1_phi, tab_ind11, tab_ind11_phi):
|
|
54
|
+
|
|
55
|
+
# non-dominance interne de pop1_phi puis de pop1
|
|
56
|
+
pop1_phi, tab_ind11_phi, obj_val_phi = non_dominance(c, pop1_phi, tab_ind11_phi)
|
|
57
|
+
pop1, tab_ind11, obj_val1 = non_dominance(c, pop1, tab_ind11)
|
|
58
|
+
|
|
59
|
+
obj_val_p = np.dot(c, population)
|
|
60
|
+
idx_p = np.arange(population.shape[1])
|
|
61
|
+
|
|
62
|
+
# population contre pop1
|
|
63
|
+
idx_p, idx1 = _filtrer_contre(obj_val_p, idx_p, obj_val1,
|
|
64
|
+
np.arange(pop1.shape[1]))
|
|
65
|
+
|
|
66
|
+
# population contre pop1_phi
|
|
67
|
+
# NB : comportement d'origine conservé. Le balayage de pop1_phi est borné par la
|
|
68
|
+
# taille de la population (size_popkD), pas par celle de pop1_phi : si pop1_phi
|
|
69
|
+
# est plus grande, ses derniers éléments ne sont pas comparés.
|
|
70
|
+
D = len(idx_p)
|
|
71
|
+
idx_p, idx_phi = _filtrer_contre(obj_val_p, idx_p, obj_val_phi,
|
|
72
|
+
np.arange(pop1_phi.shape[1]), limite_fixe=D)
|
|
73
|
+
|
|
74
|
+
pop2 = np.concatenate((population[:, idx_p], pop1_phi[:, idx_phi], pop1[:, idx1]),
|
|
75
|
+
axis=1)
|
|
76
|
+
return (pop2,
|
|
77
|
+
[tab_ind11_phi[k] for k in idx_phi],
|
|
78
|
+
[tab_ind11[k] for k in idx1])
|
dega2/GA4.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import BuildpopGA, GenPop, upload_cofGA
|
|
3
|
+
from tqdm import tqdm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def NewPop(population1, n_population, explor, p_cof1, index_p, p_cofphi1, index_phi,
|
|
7
|
+
phi, alpha, a, b, c, T, n, verbose=True):
|
|
8
|
+
|
|
9
|
+
n_pop = len(population1.T)
|
|
10
|
+
n_pop_phi = int(n_pop / alpha)
|
|
11
|
+
|
|
12
|
+
min_phi = []
|
|
13
|
+
|
|
14
|
+
barre = tqdm(
|
|
15
|
+
range(T),
|
|
16
|
+
desc="DEGA-2",
|
|
17
|
+
unit="it",
|
|
18
|
+
colour="green",
|
|
19
|
+
disable=not verbose,
|
|
20
|
+
bar_format="{desc} {percentage:3.0f}%|{bar:35}| {n_fmt}/{total_fmt} "
|
|
21
|
+
"[{elapsed}<{remaining}, {rate_fmt}] {postfix}",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
for _ in barre:
|
|
25
|
+
[pop2, tab_ind1, tab_ind11, tab_ind1_phi, tab_ind11_phi] = BuildpopGA.Build_PT(
|
|
26
|
+
population1, n_population, explor, p_cof1, index_p, p_cofphi1, index_phi,
|
|
27
|
+
phi, alpha, a, b, c, n_pop, n_pop_phi, n)
|
|
28
|
+
|
|
29
|
+
s = pop2.shape[1]
|
|
30
|
+
|
|
31
|
+
if s > n_population:
|
|
32
|
+
# sélection des n_pop meilleurs individus (tri croissant de phi)
|
|
33
|
+
xphi1 = np.dot(phi, pop2)
|
|
34
|
+
ind_phi1 = np.argsort(xphi1, axis=1)
|
|
35
|
+
pop3 = pop2[:, ind_phi1[0, :n_pop]].astype(int)
|
|
36
|
+
elif s < n_population:
|
|
37
|
+
pop3 = GenPop.Gen(a, b, c, n, n_population - s)
|
|
38
|
+
pop3 = np.concatenate((pop2, pop3), axis=1)
|
|
39
|
+
else:
|
|
40
|
+
pop3 = pop2
|
|
41
|
+
|
|
42
|
+
population1 = pop3
|
|
43
|
+
xphi1 = np.dot(phi, pop3)
|
|
44
|
+
|
|
45
|
+
xphi2 = xphi1.min()
|
|
46
|
+
min_phi.append(xphi2)
|
|
47
|
+
|
|
48
|
+
##################### Candidats ####################################
|
|
49
|
+
condidat_phi = list(index_phi[0, :explor])
|
|
50
|
+
condidat = list(index_p[0, :explor])
|
|
51
|
+
|
|
52
|
+
##################### Mise à jour des coefficients #################
|
|
53
|
+
[p_cof1, p_cofphi1, index_p, index_phi] = upload_cofGA.upload(
|
|
54
|
+
p_cof1, index_p, p_cofphi1, index_phi,
|
|
55
|
+
tab_ind1, tab_ind1_phi, tab_ind11, tab_ind11_phi,
|
|
56
|
+
explor, condidat_phi, condidat, n_pop, n_pop_phi)
|
|
57
|
+
|
|
58
|
+
barre.set_postfix_str(f"min_phi={xphi2:.4e}", refresh=False)
|
|
59
|
+
|
|
60
|
+
# Correction : l'indice vient de pop3 (= population1), pas de pop2
|
|
61
|
+
min_index = xphi1.argmin()
|
|
62
|
+
xopt = population1[:, min_index]
|
|
63
|
+
|
|
64
|
+
return pop2, xphi2, min_phi, xopt
|
dega2/GenPop.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from . import ConsChek, DomChek
|
|
3
|
+
|
|
4
|
+
# Nombre maximal de tirages consécutifs sans progression avant d'abandonner.
|
|
5
|
+
_ESSAIS_MAX = 5000
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def Gen(a,b,c,n,n_population):
|
|
9
|
+
population=np.random.randint(0,3,(n,n_population),dtype=int);
|
|
10
|
+
pop2=ConsChek.chek(population, a, b)
|
|
11
|
+
pop3=DomChek.non_dominance(c, pop2)
|
|
12
|
+
np1=n_population-len(pop3.T)
|
|
13
|
+
sans_progres=0
|
|
14
|
+
while np1!=0:
|
|
15
|
+
population=np.random.randint(0,3,(n,np1),dtype=int);
|
|
16
|
+
pop2=ConsChek.chek(population, a, b)
|
|
17
|
+
pop3=np.concatenate((pop3,pop2),axis=1);
|
|
18
|
+
pop3=DomChek.non_dominance(c, pop3)
|
|
19
|
+
np1_nouveau=n_population-len(pop3.T)
|
|
20
|
+
# Garde-fou : sans lui, un problème où il n'existe pas assez de solutions
|
|
21
|
+
# réalisables et deux à deux non dominées (peu d'objectifs, peu de variables,
|
|
22
|
+
# contraintes très serrées...) faisait tourner la boucle indéfiniment.
|
|
23
|
+
sans_progres = 0 if np1_nouveau < np1 else sans_progres + 1
|
|
24
|
+
if sans_progres > _ESSAIS_MAX:
|
|
25
|
+
raise RuntimeError(
|
|
26
|
+
"Impossible de construire une population de %d solutions réalisables "
|
|
27
|
+
"et non dominées entre elles (%d trouvées). Réduis la taille de la "
|
|
28
|
+
"population, ajoute des objectifs (lignes de c) ou relâche les "
|
|
29
|
+
"contraintes." % (n_population, n_population - np1_nouveau))
|
|
30
|
+
np1=np1_nouveau
|
|
31
|
+
|
|
32
|
+
return(pop3)
|
dega2/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""DEGA-2 : algorithme génétique multi-objectifs à variables entières."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from .solver import Result, solve
|
|
6
|
+
from .entree import charger_probleme, ecrire_exemple
|
|
7
|
+
from .example import demo
|
|
8
|
+
|
|
9
|
+
__all__ = ["solve", "charger_probleme", "ecrire_exemple", "demo", "Result", "__version__"]
|
dega2/__main__.py
ADDED
dega2/cli.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Ligne de commande :
|
|
2
|
+
|
|
3
|
+
dega2 assistant interactif (l'utilisateur entre son problème)
|
|
4
|
+
dega2 -f probleme.json problème et paramètres lus dans un fichier
|
|
5
|
+
dega2 --exemple modele.json écrit un fichier d'exemple à modifier
|
|
6
|
+
dega2 --demo problème aléatoire de démonstration
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from . import __version__, problems
|
|
13
|
+
from .entree import (PARAMETRES_DEFAUT, charger_probleme, demander,
|
|
14
|
+
demander_oui_non, ecrire_exemple, saisir_parametres,
|
|
15
|
+
saisir_probleme)
|
|
16
|
+
from .example import demo
|
|
17
|
+
from .solver import solve
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _construire_parseur():
|
|
21
|
+
p = argparse.ArgumentParser(
|
|
22
|
+
prog="dega2",
|
|
23
|
+
description="DEGA-2. Sans option, lance un assistant qui demande le problème "
|
|
24
|
+
"et les paramètres.")
|
|
25
|
+
p.add_argument("-f", "--probleme", metavar="FICHIER",
|
|
26
|
+
help="fichier .json ou .npz contenant a, b, c, phi "
|
|
27
|
+
"(et éventuellement \"parametres\")")
|
|
28
|
+
p.add_argument("--exemple", metavar="FICHIER",
|
|
29
|
+
help="écrit un fichier .json d'exemple, puis quitte")
|
|
30
|
+
p.add_argument("--demo", action="store_true",
|
|
31
|
+
help="problème aléatoire de démonstration (voir -n, -p, -m)")
|
|
32
|
+
p.add_argument("-n", type=int, default=None, help="[--demo] nombre de variables (défaut 30)")
|
|
33
|
+
p.add_argument("-p", "--objectifs", type=int, default=None,
|
|
34
|
+
help="[--demo] nombre d'objectifs (défaut 10)")
|
|
35
|
+
p.add_argument("-m", "--contraintes", type=int, default=None,
|
|
36
|
+
help="[--demo] nombre de contraintes (défaut 15)")
|
|
37
|
+
p.add_argument("-T", "--iterations", type=int, default=None,
|
|
38
|
+
help="nombre d'itérations (défaut 2n)")
|
|
39
|
+
p.add_argument("--population", type=int, default=None, help="taille de la population (défaut 80)")
|
|
40
|
+
p.add_argument("--alpha", type=int, default=None, help="paramètre alpha (défaut 4)")
|
|
41
|
+
p.add_argument("--explor", type=int, default=None,
|
|
42
|
+
help="variables explorées par itération (défaut 3)")
|
|
43
|
+
p.add_argument("--sans-positivite", action="store_true",
|
|
44
|
+
help="ne pas ajouter les contraintes x >= 0")
|
|
45
|
+
p.add_argument("--seed", type=int, default=None, help="graine aléatoire")
|
|
46
|
+
p.add_argument("--no-plot", action="store_true", help="ne pas afficher la courbe")
|
|
47
|
+
p.add_argument("--version", action="version", version=f"dega2 {__version__}")
|
|
48
|
+
return p
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _lancer(a, b, c, phi, parametres, seed=None, tracer=True):
|
|
52
|
+
a, c = np.atleast_2d(a), np.atleast_2d(c)
|
|
53
|
+
print(f"\n🧬 DEGA-2 — démarrage (variables={a.shape[1]}, objectifs={c.shape[0]}, "
|
|
54
|
+
f"contraintes={a.shape[0]})\n")
|
|
55
|
+
res = solve(a, b, c, phi, seed=seed, **parametres)
|
|
56
|
+
print("\n" + res.summary() + "\n")
|
|
57
|
+
if tracer:
|
|
58
|
+
try:
|
|
59
|
+
res.plot()
|
|
60
|
+
except ImportError as exc:
|
|
61
|
+
print(exc)
|
|
62
|
+
return res
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _assistant():
|
|
66
|
+
print("\n🧬 DEGA-2 — assistant\n")
|
|
67
|
+
print("Comment fournir le problème ?")
|
|
68
|
+
print(" 1) Charger un fichier (.json ou .npz)")
|
|
69
|
+
print(" 2) Saisir au clavier")
|
|
70
|
+
print(" 3) Problème aléatoire (démonstration)")
|
|
71
|
+
choix = demander("Choix", 1, minimum=1, maximum=3)
|
|
72
|
+
|
|
73
|
+
defauts = {}
|
|
74
|
+
if choix == 1:
|
|
75
|
+
while True:
|
|
76
|
+
chemin = input("Chemin du fichier : ").strip().strip('"').strip("'")
|
|
77
|
+
try:
|
|
78
|
+
a, b, c, phi, defauts = charger_probleme(chemin)
|
|
79
|
+
break
|
|
80
|
+
except (OSError, ValueError) as exc:
|
|
81
|
+
print(f" Lecture impossible : {exc}")
|
|
82
|
+
elif choix == 2:
|
|
83
|
+
a, b, c, phi = saisir_probleme()
|
|
84
|
+
else:
|
|
85
|
+
n = demander("Nombre de variables n", 30, minimum=1)
|
|
86
|
+
m1 = demander("Nombre de contraintes", 15, minimum=1)
|
|
87
|
+
p = demander("Nombre d'objectifs", 10, minimum=1)
|
|
88
|
+
a, b, c, phi = problems.Genr(n, m1, p)
|
|
89
|
+
|
|
90
|
+
n = np.atleast_2d(a).shape[1]
|
|
91
|
+
parametres = saisir_parametres(n, defauts)
|
|
92
|
+
tracer = demander_oui_non("Afficher la courbe de convergence à la fin ?", True)
|
|
93
|
+
_lancer(a, b, c, phi, parametres, tracer=tracer)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv=None):
|
|
97
|
+
args = _construire_parseur().parse_args(argv)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
if args.exemple:
|
|
101
|
+
ecrire_exemple(args.exemple)
|
|
102
|
+
print(f"Fichier d'exemple écrit : {args.exemple}\n"
|
|
103
|
+
f"Modifie-le puis lance : dega2 -f {args.exemple}")
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
if args.demo:
|
|
107
|
+
demo(n=30 if args.n is None else args.n,
|
|
108
|
+
p=10 if args.objectifs is None else args.objectifs,
|
|
109
|
+
m1=15 if args.contraintes is None else args.contraintes,
|
|
110
|
+
n_population=80 if args.population is None else args.population,
|
|
111
|
+
alpha=4 if args.alpha is None else args.alpha,
|
|
112
|
+
explor=3 if args.explor is None else args.explor,
|
|
113
|
+
iterations=args.iterations, seed=args.seed, plot=not args.no_plot)
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
if args.probleme:
|
|
117
|
+
a, b, c, phi, du_fichier = charger_probleme(args.probleme)
|
|
118
|
+
params = dict(PARAMETRES_DEFAUT)
|
|
119
|
+
params.update(du_fichier) # valeurs du fichier
|
|
120
|
+
for cle, valeur in (("n_population", args.population), ("alpha", args.alpha),
|
|
121
|
+
("explor", args.explor), ("iterations", args.iterations)):
|
|
122
|
+
if valeur is not None: # options de la ligne de commande
|
|
123
|
+
params[cle] = valeur
|
|
124
|
+
if args.explor is None and "explor" not in du_fichier:
|
|
125
|
+
params["explor"] = min(params["explor"], np.atleast_2d(a).shape[1])
|
|
126
|
+
if args.sans_positivite:
|
|
127
|
+
params["positivity"] = False
|
|
128
|
+
_lancer(a, b, c, phi, params, seed=args.seed, tracer=not args.no_plot)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
_assistant()
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
except (OSError, ValueError, RuntimeError) as exc:
|
|
135
|
+
print(f"Erreur : {exc}")
|
|
136
|
+
return 1
|
|
137
|
+
except (EOFError, KeyboardInterrupt):
|
|
138
|
+
print("\nInterrompu.")
|
|
139
|
+
return 1
|
dega2/entree.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Saisie du problème et des paramètres par l'utilisateur.
|
|
2
|
+
|
|
3
|
+
Trois façons de fournir un problème :
|
|
4
|
+
- un fichier .json ou .npz -> charger_probleme(chemin)
|
|
5
|
+
- la saisie au clavier -> saisir_probleme()
|
|
6
|
+
- des tableaux numpy dans son code -> dega2.solve(a, b, c, phi)
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
CLES_PROBLEME = ("a", "b", "c", "phi")
|
|
13
|
+
CLES_PARAMETRES = ("n_population", "alpha", "explor", "iterations", "positivity")
|
|
14
|
+
|
|
15
|
+
PARAMETRES_DEFAUT = {
|
|
16
|
+
"n_population": 80,
|
|
17
|
+
"alpha": 4,
|
|
18
|
+
"explor": 3,
|
|
19
|
+
"iterations": None, # None -> 2 * n
|
|
20
|
+
"positivity": True,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ----------------------------------------------------------------------------
|
|
25
|
+
# Fichiers
|
|
26
|
+
# ----------------------------------------------------------------------------
|
|
27
|
+
def charger_probleme(chemin):
|
|
28
|
+
"""Lit un problème dans un fichier .json ou .npz.
|
|
29
|
+
|
|
30
|
+
Le fichier contient les clés a, b, c, phi, et éventuellement une clé
|
|
31
|
+
"parametres" (n_population, alpha, explor, iterations, positivity).
|
|
32
|
+
Renvoie (a, b, c, phi, parametres) ; `parametres` est un dict, éventuellement vide.
|
|
33
|
+
"""
|
|
34
|
+
chemin = str(chemin)
|
|
35
|
+
bas = chemin.lower()
|
|
36
|
+
if bas.endswith(".json"):
|
|
37
|
+
with open(chemin, encoding="utf-8") as f:
|
|
38
|
+
donnees = json.load(f)
|
|
39
|
+
elif bas.endswith(".npz"):
|
|
40
|
+
with np.load(chemin, allow_pickle=False) as f:
|
|
41
|
+
donnees = {k: f[k] for k in f.files}
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError("Format non pris en charge : utilise un fichier .json ou .npz")
|
|
44
|
+
|
|
45
|
+
manquantes = [k for k in CLES_PROBLEME if k not in donnees]
|
|
46
|
+
if manquantes:
|
|
47
|
+
raise ValueError("Clés manquantes dans le fichier : " + ", ".join(manquantes))
|
|
48
|
+
|
|
49
|
+
parametres = donnees.get("parametres", {})
|
|
50
|
+
if not isinstance(parametres, dict):
|
|
51
|
+
raise ValueError('"parametres" doit être un dictionnaire.')
|
|
52
|
+
inconnus = [k for k in parametres if k not in CLES_PARAMETRES]
|
|
53
|
+
if inconnus:
|
|
54
|
+
raise ValueError("Paramètres inconnus : " + ", ".join(inconnus)
|
|
55
|
+
+ " (attendus : " + ", ".join(CLES_PARAMETRES) + ")")
|
|
56
|
+
|
|
57
|
+
a, b, c, phi = (np.asarray(donnees[k]) for k in CLES_PROBLEME)
|
|
58
|
+
return a, b, c, phi, dict(parametres)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ecrire_exemple(chemin):
|
|
62
|
+
"""Écrit un fichier .json d'exemple que l'utilisateur peut modifier."""
|
|
63
|
+
exemple = {
|
|
64
|
+
"a": [[2, 3, 1, 4, 2, 1, 3, 2],
|
|
65
|
+
[1, 1, 2, 3, 1, 4, 2, 1],
|
|
66
|
+
[3, 1, 1, 2, 4, 1, 1, 3]],
|
|
67
|
+
"b": [40, 36, 42],
|
|
68
|
+
"c": [[3, -1, 2, 0, 4, -2, 1, 1],
|
|
69
|
+
[-2, 4, 1, -1, 0, 3, -3, 2],
|
|
70
|
+
[1, 2, -4, 3, -1, 1, 2, -2],
|
|
71
|
+
[0, -3, 2, 2, 1, -1, 4, -1],
|
|
72
|
+
[2, 1, 1, -3, 2, 2, -2, 3]],
|
|
73
|
+
"phi": [-3, -2, -4, -1, -2, -3, -1, -2],
|
|
74
|
+
"parametres": {
|
|
75
|
+
"n_population": 20,
|
|
76
|
+
"alpha": 4,
|
|
77
|
+
"explor": 3,
|
|
78
|
+
"iterations": 16,
|
|
79
|
+
"positivity": True,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
with open(chemin, "w", encoding="utf-8") as f:
|
|
83
|
+
json.dump(exemple, f, indent=2, ensure_ascii=False)
|
|
84
|
+
return exemple
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ----------------------------------------------------------------------------
|
|
88
|
+
# Saisie interactive
|
|
89
|
+
# ----------------------------------------------------------------------------
|
|
90
|
+
def _nombre(brut):
|
|
91
|
+
"""Convertit un texte en int si possible, sinon en float."""
|
|
92
|
+
try:
|
|
93
|
+
return int(brut)
|
|
94
|
+
except ValueError:
|
|
95
|
+
return float(brut)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def demander(invite, defaut=None, type_=int, minimum=None, maximum=None):
|
|
99
|
+
"""Pose une question, répète tant que la réponse est invalide.
|
|
100
|
+
Entrée vide = valeur par défaut."""
|
|
101
|
+
while True:
|
|
102
|
+
suffixe = "" if defaut is None else f" [{defaut}]"
|
|
103
|
+
brut = input(f"{invite}{suffixe} : ").strip()
|
|
104
|
+
if brut == "":
|
|
105
|
+
if defaut is not None:
|
|
106
|
+
return defaut
|
|
107
|
+
print(" Une valeur est requise.")
|
|
108
|
+
continue
|
|
109
|
+
try:
|
|
110
|
+
valeur = type_(brut)
|
|
111
|
+
except ValueError:
|
|
112
|
+
print(" Valeur invalide.")
|
|
113
|
+
continue
|
|
114
|
+
if (minimum is not None and valeur < minimum) or \
|
|
115
|
+
(maximum is not None and valeur > maximum):
|
|
116
|
+
borne = f"entre {minimum} et {maximum}" if None not in (minimum, maximum) \
|
|
117
|
+
else (f">= {minimum}" if minimum is not None else f"<= {maximum}")
|
|
118
|
+
print(f" La valeur doit être {borne}.")
|
|
119
|
+
continue
|
|
120
|
+
return valeur
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def demander_oui_non(invite, defaut=True):
|
|
124
|
+
indice = "O/n" if defaut else "o/N"
|
|
125
|
+
while True:
|
|
126
|
+
brut = input(f"{invite} [{indice}] : ").strip().lower()
|
|
127
|
+
if brut == "":
|
|
128
|
+
return defaut
|
|
129
|
+
if brut in ("o", "oui", "y", "yes"):
|
|
130
|
+
return True
|
|
131
|
+
if brut in ("n", "non", "no"):
|
|
132
|
+
return False
|
|
133
|
+
print(" Réponds par o ou n.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def demander_vecteur(invite, longueur):
|
|
137
|
+
"""Lit `longueur` nombres séparés par des espaces ou des virgules."""
|
|
138
|
+
while True:
|
|
139
|
+
brut = input(f"{invite} : ").replace(",", " ").split()
|
|
140
|
+
try:
|
|
141
|
+
valeurs = [_nombre(t) for t in brut]
|
|
142
|
+
except ValueError:
|
|
143
|
+
print(" Il faut uniquement des nombres.")
|
|
144
|
+
continue
|
|
145
|
+
if len(valeurs) != longueur:
|
|
146
|
+
print(f" {longueur} nombres attendus, {len(valeurs)} reçus.")
|
|
147
|
+
continue
|
|
148
|
+
return valeurs
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def saisir_probleme():
|
|
152
|
+
"""Saisie au clavier de a, b, c, phi. Renvoie (a, b, c, phi)."""
|
|
153
|
+
n = demander("Nombre de variables n", minimum=1)
|
|
154
|
+
m = demander("Nombre de contraintes m", minimum=1)
|
|
155
|
+
p = demander("Nombre d'objectifs p (lignes de c)", minimum=1)
|
|
156
|
+
|
|
157
|
+
print(f"\nMatrice a : {m} lignes de {n} nombres (contraintes a·x <= b)")
|
|
158
|
+
a = [demander_vecteur(f" a, ligne {i + 1}/{m}", n) for i in range(m)]
|
|
159
|
+
b = demander_vecteur(f"Second membre b : {m} nombres", m)
|
|
160
|
+
print(f"\nMatrice c : {p} lignes de {n} nombres (objectifs)")
|
|
161
|
+
c = [demander_vecteur(f" c, ligne {i + 1}/{p}", n) for i in range(p)]
|
|
162
|
+
phi = demander_vecteur(f"Fonction à minimiser phi : {n} nombres", n)
|
|
163
|
+
return np.array(a), np.array(b), np.array(c), np.array(phi)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def saisir_parametres(n, defauts=None):
|
|
167
|
+
"""Demande les paramètres de l'algorithme (n = nombre de variables, pour proposer
|
|
168
|
+
2·n itérations). Renvoie un dict utilisable par `solve`."""
|
|
169
|
+
d = dict(PARAMETRES_DEFAUT)
|
|
170
|
+
d.update(defauts or {})
|
|
171
|
+
print("\nParamètres de l'algorithme (Entrée = valeur proposée)")
|
|
172
|
+
n_population = demander(" Taille de la population", d["n_population"], minimum=1)
|
|
173
|
+
alpha = demander(" alpha", d["alpha"], minimum=1)
|
|
174
|
+
explor = demander(" Variables explorées par itération", min(d["explor"], n),
|
|
175
|
+
minimum=1, maximum=n)
|
|
176
|
+
iterations = demander(" Nombre d'itérations",
|
|
177
|
+
2 * n if d["iterations"] is None else d["iterations"], minimum=1)
|
|
178
|
+
positivity = demander_oui_non(" Ajouter les contraintes x >= 0 ?", d["positivity"])
|
|
179
|
+
return {"n_population": n_population, "alpha": alpha, "explor": explor,
|
|
180
|
+
"iterations": iterations, "positivity": positivity}
|
dega2/example.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Démonstration : reproduit le script DEGA2.py d'origine sur un problème aléatoire."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from . import problems
|
|
5
|
+
from .solver import solve
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def demo(n=30, p=10, m1=15, n_population=80, alpha=4, explor=3,
|
|
9
|
+
iterations=None, seed=None, plot=True, verbose=True):
|
|
10
|
+
"""Génère un problème aléatoire (n variables, p objectifs, m1 contraintes),
|
|
11
|
+
le résout avec DEGA-2 et renvoie le `Result`."""
|
|
12
|
+
if seed is not None:
|
|
13
|
+
np.random.seed(seed)
|
|
14
|
+
|
|
15
|
+
a, b, c, phi = problems.Genr(n, m1, p)
|
|
16
|
+
T = 2 * n if iterations is None else iterations
|
|
17
|
+
|
|
18
|
+
if verbose:
|
|
19
|
+
print("\n🧬 DEGA-2 — démarrage de l'optimisation "
|
|
20
|
+
f"(n={n}, objectifs={p}, contraintes={m1}, itérations={T})\n")
|
|
21
|
+
|
|
22
|
+
res = solve(a, b, c, phi, n_population=n_population, alpha=alpha,
|
|
23
|
+
explor=explor, iterations=T, verbose=verbose)
|
|
24
|
+
|
|
25
|
+
if verbose:
|
|
26
|
+
print("\n" + res.summary(max_vars=n) + "\n")
|
|
27
|
+
if plot:
|
|
28
|
+
res.plot()
|
|
29
|
+
return res
|
dega2/influ_cof.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def selct(c, n, p, phi):
|
|
5
|
+
# coefficients normalisés (version vectorisée des doubles boucles d'origine)
|
|
6
|
+
sum_c = np.sum(np.abs(c), axis=1)[:, np.newaxis]
|
|
7
|
+
sum_phi = np.sum(np.abs(phi))
|
|
8
|
+
cof_c = np.sum(c / sum_c, axis=0) # influence cumulée de chaque variable
|
|
9
|
+
cof_phi = phi[0, :] / sum_phi
|
|
10
|
+
|
|
11
|
+
p_cof1 = np.empty((3, n))
|
|
12
|
+
p_cofphi1 = np.empty((3, n))
|
|
13
|
+
|
|
14
|
+
p_cof1[0, :] = np.abs(cof_c)
|
|
15
|
+
p_cofphi1[0, :] = np.abs(cof_phi)
|
|
16
|
+
p_cof1[1, :] = np.arange(n)
|
|
17
|
+
p_cofphi1[1, :] = np.arange(n)
|
|
18
|
+
p_cof1[2, :] = np.where(cof_c < 0, -1, 1)
|
|
19
|
+
# Comportement d'origine conservé : le signe de p_cofphi vaut toujours -1
|
|
20
|
+
# (les deux branches du `if` d'origine donnaient -1). Voir remarque.
|
|
21
|
+
p_cofphi1[2, :] = -1
|
|
22
|
+
|
|
23
|
+
# tri par influence décroissante (même expression que l'original)
|
|
24
|
+
p_cof1 = p_cof1[::, p_cof1[0, ].argsort()[::-1]]
|
|
25
|
+
p_cofphi1 = p_cofphi1[::, p_cofphi1[0, ].argsort()[::-1]]
|
|
26
|
+
index_p = (p_cof1[1, :])[np.newaxis, :].astype(int)
|
|
27
|
+
index_phi = (p_cofphi1[1, :])[np.newaxis, :].astype(int)
|
|
28
|
+
|
|
29
|
+
return [p_cof1, index_p, p_cofphi1, index_phi]
|
dega2/plotting.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Courbe de convergence (matplotlib est une dépendance optionnelle)."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def plot_convergence(result, show=True):
|
|
6
|
+
try:
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
except ImportError as exc: # pragma: no cover
|
|
9
|
+
raise ImportError(
|
|
10
|
+
"matplotlib est requis pour tracer la courbe : "
|
|
11
|
+
"pip install \"dega2[plot]\""
|
|
12
|
+
) from exc
|
|
13
|
+
|
|
14
|
+
y = np.ravel(result.history)
|
|
15
|
+
t = np.arange(len(y))
|
|
16
|
+
|
|
17
|
+
fig, ax = plt.subplots(figsize=(9, 5))
|
|
18
|
+
ax.plot(t, y, color="#d62728", linewidth=2, label="min φ")
|
|
19
|
+
ax.fill_between(t, y, y.max(), color="#d62728", alpha=0.08)
|
|
20
|
+
ax.scatter([t[-1]], [y[-1]], color="#d62728", zorder=3)
|
|
21
|
+
ax.annotate(f"φ* = {y[-1]:.4g}", xy=(t[-1], y[-1]),
|
|
22
|
+
xytext=(-90, 25), textcoords="offset points",
|
|
23
|
+
arrowprops=dict(arrowstyle="->", color="gray"))
|
|
24
|
+
ax.set_xlabel("Iterations")
|
|
25
|
+
ax.set_ylabel("min of phi")
|
|
26
|
+
ax.set_title("Convergence of DEGA-2")
|
|
27
|
+
ax.grid(True, linestyle="--", alpha=0.4)
|
|
28
|
+
ax.legend()
|
|
29
|
+
fig.tight_layout()
|
|
30
|
+
if show:
|
|
31
|
+
plt.show()
|
|
32
|
+
return fig
|
dega2/problems.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def Genr(n,m1,p):
|
|
4
|
+
a=np.random.randint(1,10,(m1,n),dtype=int)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
b=np.random.randint(8*n,10*n,m1,dtype=int)[:,np.newaxis]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
c=np.random.randint(-10,10,(p,n),dtype=int)
|
|
12
|
+
phi=np.random.randint(-10,10,n,dtype=int)[np.newaxis,:]
|
|
13
|
+
return a,b,c,phi
|
|
14
|
+
|
|
15
|
+
|
dega2/solver.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Interface publique de DEGA-2.
|
|
2
|
+
|
|
3
|
+
>>> import dega2
|
|
4
|
+
>>> res = dega2.solve(a, b, c, phi)
|
|
5
|
+
>>> res.x, res.phi
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from . import GA4, GenPop, influ_cof
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Result:
|
|
17
|
+
"""Résultat d'une optimisation DEGA-2."""
|
|
18
|
+
|
|
19
|
+
x: np.ndarray # meilleure solution trouvée (vecteur de longueur n)
|
|
20
|
+
phi: float # valeur de phi pour cette solution
|
|
21
|
+
history: np.ndarray # minimum de phi à chaque itération
|
|
22
|
+
population: np.ndarray # population de la dernière itération, avant sélection (une solution par colonne)
|
|
23
|
+
time: float # temps de calcul en secondes
|
|
24
|
+
iterations: int # nombre d'itérations effectuées
|
|
25
|
+
|
|
26
|
+
def summary(self, max_vars=None):
|
|
27
|
+
"""Encadré texte récapitulatif (comme dans le script d'origine)."""
|
|
28
|
+
x = np.ravel(self.x)
|
|
29
|
+
shown = x if max_vars is None else x[:max_vars]
|
|
30
|
+
apercu = ", ".join(f"{v:.4g}" for v in shown)
|
|
31
|
+
lignes = [
|
|
32
|
+
"RÉSULTATS DEGA-2",
|
|
33
|
+
f"Phi optimum : {self.phi:.6g}",
|
|
34
|
+
f"Temps CPU : {self.time:.2f} s",
|
|
35
|
+
f"Itérations : {self.iterations}",
|
|
36
|
+
f"x* : {apercu}",
|
|
37
|
+
]
|
|
38
|
+
largeur = max(len(ligne) for ligne in lignes) + 2
|
|
39
|
+
out = ["┏" + "━" * largeur + "┓"]
|
|
40
|
+
for i, ligne in enumerate(lignes):
|
|
41
|
+
out.append("┃ " + ligne.ljust(largeur - 1) + "┃")
|
|
42
|
+
if i == 0:
|
|
43
|
+
out.append("┣" + "━" * largeur + "┫")
|
|
44
|
+
out.append("┗" + "━" * largeur + "┛")
|
|
45
|
+
return "\n".join(out)
|
|
46
|
+
|
|
47
|
+
def plot(self, show=True):
|
|
48
|
+
"""Trace la courbe de convergence (nécessite matplotlib)."""
|
|
49
|
+
from .plotting import plot_convergence
|
|
50
|
+
return plot_convergence(self, show=show)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def solve(a, b, c, phi, *, n_population=80, alpha=4, explor=3,
|
|
54
|
+
iterations=None, positivity=True, seed=None, verbose=True):
|
|
55
|
+
"""Lance DEGA-2 sur le problème : min phi·x sous a·x <= b, x entier.
|
|
56
|
+
|
|
57
|
+
Paramètres
|
|
58
|
+
----------
|
|
59
|
+
a : (m, n) matrice des contraintes.
|
|
60
|
+
b : (m,) ou (m, 1) second membre.
|
|
61
|
+
c : (p, n) matrice des p objectifs (sert au test de non-dominance).
|
|
62
|
+
phi : (n,) ou (1, n) fonction objectif scalaire à minimiser.
|
|
63
|
+
n_population : taille de la population.
|
|
64
|
+
alpha : la population d'exploration « phi » est de taille n_population / alpha.
|
|
65
|
+
explor : nombre de variables explorées à chaque itération.
|
|
66
|
+
iterations : nombre d'itérations (par défaut 2 * n).
|
|
67
|
+
positivity : si True, ajoute les contraintes x >= 0 (comme le script d'origine).
|
|
68
|
+
seed : graine du générateur aléatoire (résultats reproductibles).
|
|
69
|
+
verbose : affiche la barre de progression.
|
|
70
|
+
|
|
71
|
+
Renvoie un objet `Result`.
|
|
72
|
+
"""
|
|
73
|
+
a = np.atleast_2d(np.asarray(a))
|
|
74
|
+
b = np.asarray(b).reshape(-1, 1)
|
|
75
|
+
c = np.atleast_2d(np.asarray(c))
|
|
76
|
+
phi = np.asarray(phi).reshape(1, -1)
|
|
77
|
+
|
|
78
|
+
n = a.shape[1]
|
|
79
|
+
p = c.shape[0]
|
|
80
|
+
if b.shape[0] != a.shape[0]:
|
|
81
|
+
raise ValueError("b doit avoir autant de lignes que a.")
|
|
82
|
+
if c.shape[1] != n or phi.shape[1] != n:
|
|
83
|
+
raise ValueError("a, c et phi doivent avoir le même nombre de variables n.")
|
|
84
|
+
if not 1 <= explor <= n:
|
|
85
|
+
raise ValueError("explor doit être compris entre 1 et n.")
|
|
86
|
+
if n_population < 1 or alpha < 1:
|
|
87
|
+
raise ValueError("n_population et alpha doivent être >= 1.")
|
|
88
|
+
|
|
89
|
+
T = 2 * n if iterations is None else int(iterations)
|
|
90
|
+
|
|
91
|
+
if seed is not None:
|
|
92
|
+
np.random.seed(seed)
|
|
93
|
+
|
|
94
|
+
if positivity:
|
|
95
|
+
a = np.concatenate((a, -np.eye(n)), axis=0)
|
|
96
|
+
b = np.concatenate((b, np.zeros((n, 1))), axis=0)
|
|
97
|
+
|
|
98
|
+
# Population initiale
|
|
99
|
+
population = GenPop.Gen(a, b, c, n, n_population)
|
|
100
|
+
|
|
101
|
+
# Influence des coefficients
|
|
102
|
+
p_cof1, index_p, p_cofphi1, index_phi = influ_cof.selct(c, n, p, phi)
|
|
103
|
+
|
|
104
|
+
# Algorithme
|
|
105
|
+
t0 = time.perf_counter()
|
|
106
|
+
pop_final, _, min_phi, x_opt = GA4.NewPop(
|
|
107
|
+
population, n_population, explor,
|
|
108
|
+
p_cof1, index_p, p_cofphi1, index_phi,
|
|
109
|
+
phi, alpha, a, b, c, T, n, verbose=verbose)
|
|
110
|
+
elapsed = time.perf_counter() - t0
|
|
111
|
+
|
|
112
|
+
history = np.asarray(min_phi, dtype=float)
|
|
113
|
+
return Result(
|
|
114
|
+
x=np.ravel(x_opt),
|
|
115
|
+
phi=float(history[-1]),
|
|
116
|
+
history=history,
|
|
117
|
+
population=pop_final,
|
|
118
|
+
time=elapsed,
|
|
119
|
+
iterations=T,
|
|
120
|
+
)
|
dega2/upload_cofGA.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from collections import Counter
|
|
3
|
+
def upload(p_cof1,index_p,p_cofphi1,index_phi,tab_ind1,tab_ind1_phi,tab_ind11,tab_ind11_phi, explor,condidat_phi,condidat,n_pop,n_pop_phi):
|
|
4
|
+
x1=Counter(tab_ind11)
|
|
5
|
+
x2=Counter(tab_ind11_phi)
|
|
6
|
+
x3=Counter(tab_ind1)
|
|
7
|
+
x4=Counter(tab_ind1_phi)
|
|
8
|
+
counter_tab11=list(x1.items())
|
|
9
|
+
counter_tab11_phi=list(x2.items())
|
|
10
|
+
counter_tab1=list(x3.items())
|
|
11
|
+
counter_tab1_phi=list(x4.items())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
l1=len(counter_tab1)
|
|
15
|
+
l2=len(counter_tab11)
|
|
16
|
+
l3=len(counter_tab1_phi)
|
|
17
|
+
l4=len(counter_tab11_phi)
|
|
18
|
+
|
|
19
|
+
if (l1==explor) and (l2==explor):
|
|
20
|
+
rho=[]
|
|
21
|
+
for i in range (0,explor):
|
|
22
|
+
rho.append(counter_tab11[i][1]/counter_tab1[i][1])
|
|
23
|
+
else:
|
|
24
|
+
rho=[0]*explor
|
|
25
|
+
for ii in range(0,explor):
|
|
26
|
+
jj=0
|
|
27
|
+
while jj<l1:
|
|
28
|
+
if condidat[ii]==counter_tab1[jj][0]:
|
|
29
|
+
tt=0
|
|
30
|
+
while tt<l2:
|
|
31
|
+
if counter_tab1[jj][0]==counter_tab11[tt][0]:
|
|
32
|
+
rho[ii]=counter_tab11[tt][1]/counter_tab1[jj][1]
|
|
33
|
+
tt=tt+1
|
|
34
|
+
jj=jj+1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if (l3==explor) and (l4==explor):
|
|
38
|
+
rho1=[]
|
|
39
|
+
for i in range (0,explor):
|
|
40
|
+
rho1.append(counter_tab11_phi[i][1]/counter_tab1_phi[i][1])
|
|
41
|
+
else:
|
|
42
|
+
rho1=[0]*explor
|
|
43
|
+
for ii in range(0,explor):
|
|
44
|
+
jj=0
|
|
45
|
+
while jj<l3:
|
|
46
|
+
if condidat_phi[ii]==counter_tab1_phi[jj][0]:
|
|
47
|
+
tt=0
|
|
48
|
+
while tt<l4:
|
|
49
|
+
if counter_tab1_phi[jj][0]==counter_tab11_phi[tt][0]:
|
|
50
|
+
rho1[ii]=counter_tab11_phi[tt][1]/counter_tab1_phi[jj][1]
|
|
51
|
+
tt=tt+1
|
|
52
|
+
jj=jj+1
|
|
53
|
+
rho_p=[]
|
|
54
|
+
rho_phi=[]
|
|
55
|
+
for i in range(0,explor):
|
|
56
|
+
rho_p.append((p_cof1[0,i])*(rho[i])**2)
|
|
57
|
+
p_cof1[0,i]=rho_p[i]
|
|
58
|
+
rho_phi.append((p_cofphi1[0,i])*(rho1[i])**2)
|
|
59
|
+
p_cofphi1[0,i]=rho_phi[i]
|
|
60
|
+
|
|
61
|
+
p_cof1= p_cof1[::, p_cof1[0,].argsort()[::-1]]
|
|
62
|
+
p_cofphi1= p_cofphi1[::, p_cofphi1[0,].argsort()[::-1]]
|
|
63
|
+
index_p=(p_cof1[1,:])[np.newaxis,:]
|
|
64
|
+
index_phi=(p_cofphi1[1,:])[np.newaxis,:]
|
|
65
|
+
index_p=index_p.astype(int)
|
|
66
|
+
index_phi=index_phi.astype(int)
|
|
67
|
+
return p_cof1,p_cofphi1, index_p,index_phi
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dega2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DEGA-2: A optimization a function over the efficient set of multi-objective integer variables using the genetics algorithms
|
|
5
|
+
Author-email: Ali Zaidi <a.zaidi@crlca.dz>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: optimization,genetic algorithm,multi-objective,integer programming
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Provides-Extra: plot
|
|
17
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest; extra == "test"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# DEGA-2
|
|
23
|
+
|
|
24
|
+
A optimisation of function over the efficient set of multi-objective integer variables using a genetics algorithms, by Ali Zaidi.
|
|
25
|
+
|
|
26
|
+
It minimizes a function `phi·x` subject to linear constraints `a·x <= b`
|
|
27
|
+
(`x` integer, and `x >= 0` by default), using `p` objectives `c·x` for the
|
|
28
|
+
non-dominance test.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install dega2
|
|
34
|
+
# with the convergence plot:
|
|
35
|
+
pip install "dega2[plot]"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Entering your problem and parameters
|
|
39
|
+
|
|
40
|
+
### 1. Interactive assistant
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
dega2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The assistant asks for the problem (file, keyboard entry, or a random example),
|
|
47
|
+
then for the parameters (population size, alpha, number of explored variables,
|
|
48
|
+
iterations, `x >= 0` constraints). Press Enter to accept the suggested value.
|
|
49
|
+
|
|
50
|
+
### 2. `.json` file
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
dega2 --exemple my_problem.json # writes a template to edit
|
|
54
|
+
dega2 -f my_problem.json # runs the computation
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
File format:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"a": [[2, 3, 1], [1, 1, 2]],
|
|
62
|
+
"b": [20, 15],
|
|
63
|
+
"c": [[3, -1, 2], [-2, 4, 1]],
|
|
64
|
+
"phi": [-3, -2, -4],
|
|
65
|
+
"parametres": {
|
|
66
|
+
"n_population": 20, "alpha": 4, "explor": 3,
|
|
67
|
+
"iterations": 10, "positivity": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- `a`: constraint matrix (m rows, n columns); `b`: right-hand side (m values)
|
|
73
|
+
- `c`: objectives matrix (p rows, n columns); `phi`: function to minimize (n values)
|
|
74
|
+
- `parametres` is optional. Command-line options (`--population`, `--alpha`,
|
|
75
|
+
`--explor`, `-T`, `--sans-positivite`, `--seed`, `--no-plot`) take precedence over
|
|
76
|
+
the file. A `.npz` file (keys `a`, `b`, `c`, `phi`) is also accepted.
|
|
77
|
+
|
|
78
|
+
### 3. In Python code
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import numpy as np
|
|
82
|
+
import dega2
|
|
83
|
+
|
|
84
|
+
a = np.array([[2, 3, 1], [1, 1, 2]])
|
|
85
|
+
b = np.array([20, 15])
|
|
86
|
+
c = np.array([[3, -1, 2], [-2, 4, 1]])
|
|
87
|
+
phi = np.array([-3, -2, -4])
|
|
88
|
+
|
|
89
|
+
res = dega2.solve(a, b, c, phi, n_population=20, explor=3, iterations=10, seed=0)
|
|
90
|
+
|
|
91
|
+
print(res.phi) # best value of phi
|
|
92
|
+
print(res.x) # best solution
|
|
93
|
+
res.plot() # convergence curve (requires matplotlib)
|
|
94
|
+
|
|
95
|
+
# or from a file:
|
|
96
|
+
a, b, c, phi, params = dega2.charger_probleme("my_problem.json")
|
|
97
|
+
res = dega2.solve(a, b, c, phi, **params)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`solve` parameters: `n_population=80`, `alpha=4`, `explor=3`,
|
|
101
|
+
`iterations=None` (2·n by default), `positivity=True`, `seed=None`, `verbose=True`.
|
|
102
|
+
`solve` returns a `Result` object: `x`, `phi`, `history` (minimum of phi at each
|
|
103
|
+
iteration), `population`, `time`, `iterations`.
|
|
104
|
+
|
|
105
|
+
Note: the algorithm builds a population of `n_population` feasible solutions that
|
|
106
|
+
are mutually non-dominated. With very few objectives or variables, such a
|
|
107
|
+
population may not exist; `dega2` then stops with an error message (reduce
|
|
108
|
+
`n_population` or add objectives).
|
|
109
|
+
|
|
110
|
+
## Random demonstration
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
dega2 --demo --seed 1
|
|
114
|
+
dega2 --demo -n 20 -p 5 --no-plot
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Tests
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install -e ".[test]"
|
|
121
|
+
pytest
|
|
122
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
dega2/BuildpopGA.py,sha256=rjdFi_fuzGCqdHqZioOrO6Moap_tN6i7TgkMMKdhDGg,1953
|
|
2
|
+
dega2/ConsChek.py,sha256=YeR8auh1Pp0qDOvqq5ho74qASVnzfxcuROiXz1TwuK8,169
|
|
3
|
+
dega2/ConsChekGA4.py,sha256=TYfZKhtZrnFfJaUHx3EiMk-akEBv1L1iMGN68v9ZuBo,577
|
|
4
|
+
dega2/DomChek.py,sha256=rafMg_9Xbt_cesNXxOYX2pOI2QX8e_xnGu1PkASijDk,692
|
|
5
|
+
dega2/DomChekGA4.py,sha256=FrLNy1q-PEcHr6kXyMOZa6WMCjs_A8qVs0_irqOU6hc,3332
|
|
6
|
+
dega2/GA4.py,sha256=mI4g6O6gDXia4npm7gpeiEIN9-6EMJHFOYaT3cHudhQ,2181
|
|
7
|
+
dega2/GenPop.py,sha256=ZgcXpGYZhIQnjrrHEa5Z8AP-SxQagRd0dIx_LBzLxQ8,1414
|
|
8
|
+
dega2/__init__.py,sha256=4YUmz0fH3yyfi7wPR5kCQU1-38usChBvgNP3IaGvfuc,307
|
|
9
|
+
dega2/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
10
|
+
dega2/cli.py,sha256=7XmihOue_34Edjov6YykhwFq43lL-GXdoktfMDqzbxY,6112
|
|
11
|
+
dega2/entree.py,sha256=zFqYLJnlpswm3pFKadrCYSLbHmn8rzLbqwZxGQ97kpQ,7009
|
|
12
|
+
dega2/example.py,sha256=iP2zpYYIr2xyGKD9_Besr8yaxsrVyVwgdHTUTMPC36g,987
|
|
13
|
+
dega2/influ_cof.py,sha256=B95jXUxy3JClO8uWKchPDBo3K-aob4xB0y2MXtviLIE,1116
|
|
14
|
+
dega2/plotting.py,sha256=3LgxRzR5SPp1GI80O1sQk1KetEYtTJu-E6qwUJJtlyE,1098
|
|
15
|
+
dega2/problems.py,sha256=xudbft7jXXpXA7actbJVtpqZ7LnPN_s01I8O4s8b5n8,294
|
|
16
|
+
dega2/solver.py,sha256=1F8OO7mIKkd0GIyaoQVwoDfmmnCumjJtcF_0ZafaGt8,4358
|
|
17
|
+
dega2/upload_cofGA.py,sha256=v3QeIWlhK8CQm9CE7Gc0ukIq1hKXkveQ0OxvJfpJYXU,2373
|
|
18
|
+
dega2-0.1.0.dist-info/licenses/LICENSE,sha256=1riZ1sfK9aSG3-OIJCXqWYqNOHIZApqInCP64juYiVU,1066
|
|
19
|
+
dega2-0.1.0.dist-info/METADATA,sha256=oGuueAcuxni3If4K4V8kIbeBARlbqCSTnZvXIAK49NQ,3550
|
|
20
|
+
dega2-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
21
|
+
dega2-0.1.0.dist-info/entry_points.txt,sha256=8kH-bHcg7HiErUcwWDb1tNFiMWeewohUQfG9v1hpe1g,41
|
|
22
|
+
dega2-0.1.0.dist-info/top_level.txt,sha256=3MN8auQqwfRd1lNGX52TzD434deySaQnxiDtg-AI5So,6
|
|
23
|
+
dega2-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ali Zaidi
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dega2
|