README.md 24,5 ko
Newer Older
ABROUS Celia's avatar
ABROUS Celia a validé
# Projet RI

Nous allons analyser un réseau de collaboration scientifique en informatique. 
Le réseau est extrait de DBLP et disponible sur SNAP.
GraphStream permet de mesurer de nombreuses caractéristiques d'un réseau.
La plupart de ces mesures sont implantées comme des méthodes statiques dans la classe Toolkit. 
Elles vous seront très utiles par la suite.
ABROUS Celia's avatar
ABROUS Celia a validé
# TP 1: 
ABROUS Celia's avatar
ABROUS Celia a validé

## 1- Tout d'abord :

j'ai commencé par télécharger les données et les lire avec GraphStream.

## 2- Quelques mesures de base :
J'ai calculé quelques mesures de base : comme le nombre de nœuds et d'arêtes, le degré moyen ainsi que le coefficient de clustring.
- Nombre de noeuds : N = 317080
```java
int nodeCount = graph.getNodeCount();
```
- Nombre de liens : E = 1049866
```java
int edgeCount = graph.getEdgeCount();
```



- Degré moyen : ⟨k⟩ = 6.62208890914917
```java
double averageDegree = Toolkit.averageDegree(graph);
```

- Coefficient de clustering (réel) : 0.6324308280637396 
```java
double clusteringCoefficient = Toolkit.averageClusteringCoefficient(graph);
```
- Coefficient de clustering (aléatoire) : ⟨k⟩/N = 2.0884599814397534E-5

## 3- Autres mesures 
Voyons maintenant si le graphe est connexe : 
ABROUS Celia's avatar
ABROUS Celia a validé
- Le réseau est-il connexe ? : Oui

```java
boolean isConnected = Toolkit.isConnected(graph);
```

ABROUS Celia's avatar
ABROUS Celia a validé
- Degré moyen minimal pour qu'un graphe aléatoire soit connexe : 12.666909386951092

ABROUS Celia's avatar
ABROUS Celia a validé
```java
double minAverageDegreeForConnectivity = Math.log(nodeCount);
```

## 4- Distribution des Degrés
ABROUS Celia's avatar
ABROUS Celia a validé

Exportation des données : Les degrés des nœuds sont exportés dans le fichier degree_distribution.txt.

#### Analyse graphique : 

- Échelle linéaire :
  Tout d'abord,
nous pouvons tracer la distribution des degrés en
échelle linéaire. Cette courbe nous montrera comment 
la probabilité p(k) varie avec le degré k. 
Dans un graphique en échelle linéaire, 
la relation entre k et p(k) peut ne pas être évidente
et pourrait ne pas révéler de structure particulière.

![Distribution des degrés](distribution_degres.png)


- Échelle log-log :
  Lorsque nous traçons la même distribution en échelle
log-log, c'est-à-dire avec les axes 
log(k) et log(p(k)), cela permet de mieux observer les tendances à grande échelle.
nous observons une ligne droite ce qui nous dit que la distribution suit une loi de puissance.

![Distribution des degrés](distribution_degres_loglog.png)


une ligne droite est observée, cela indique une loi de puissance.
- Comparaison avec la distribution de Poisson : Superposer la distribution de Poisson avec ⟨k⟩.

ABROUS Celia's avatar
ABROUS Celia a validé
![Dblp](dd_dblp.png)
ABROUS Celia's avatar
ABROUS Celia a validé


## 5- Distance moyenne et distribution des distances 


Distance moyenne de notre réseau: 6.700611818856679

```java
double degreeAverage = Toolkit.averageDegree(graph);
double NetworkDistance = Math.log(graph.getNodeCount()) / Math.log(degreeAverage);
System.out.println("Distance moyenne dans notre réseau  : " + NetworkDistance);
```


ABROUS Celia's avatar
ABROUS Celia a validé
- L'hypothèse des six degrés de séparation se confirme-t-elle ?
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Oui, l'hypothèse des six degrés de séparation est confirmée : la distance moyenne entre les nœuds est proche de 6.
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
- Est-ce qu'il s'agit d'un réseau petit monde ? 
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Le réseau montre des propriétés caractéristiques des réseaux "petit monde" : une distance moyenne faible et un clustering élevé.
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
- Quelle sera la distance moyenne dans un réseau aléatoire avec les mêmes caractéristiques ?
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
J'ai calculé la distance moyenne d'un réseau aléatoire de même taille et de même degré moyen que j'ai généré avec randomGenerator de graphstream et ça nous donne à peu prés la même chose 
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Distance moyenne du réseau Aléatoire : 6.70193902051403

```java
double degreeAverage = Toolkit.averageDegree(randomGraph);
double NetworkDistance = Math.log(randomGraph.getNodeCount()) / Math.log(degreeAverage);
System.out.println("Distance moyenne du réseau Aléatoire : " + NetworkDistance);
```
Tracer les distributions des distances :


![Distribution des distances](distance_distribution.png)


- Analyse de la courbe des distances moyennes :

Forme en cloche avec une décroissance symétrique de chaque côté.

La courbe montre un pic central autour de 6. Cela suggère que la distance moyenne entre 
les paires de nœuds est proche de cette valeur. Cela soutient l'hypothèse des six degrés de séparation, car la majorité des distances sont à 6 ou moins.

Décroissance rapide : Les distances très petites et très grandes sont rares, tandis que les distances proches de la moyenne sont les plus fréquentes.


- Formulez une hypothèse sur la loi de cette distribution.

On conclut donc que la forme de la distribution est proche d'une loi normale, ce qui est souvent observé dans les réseaux petits mondes.
ABROUS Celia's avatar
ABROUS Celia a validé


ABROUS Celia's avatar
ABROUS Celia a validé
## 6- Générer deux graphes de même taille et de même degré :
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
1 - le graphe aléatoire:

Cette méthode génère un graphe aléatoire basé sur les propriétés d'un graphe d'entrée initialGraph, à l'aide d'un générateur de graphes aléatoires de GraphStream. 
Le graphe généré a un nombre total de nœuds fixé (N = 317080) et un degré moyen similaire à celui de initialGraph.




```java
public static Graph createRandomGraph(Graph initialGraph) {

        double averageDegree = Toolkit.averageDegree(initialGraph);

        Generator generator = new RandomGenerator(averageDegree, false);
        Graph graphAlea = new SingleGraph("graphe aléatoire");
        int N = 317080;

        // graphe aléatoire générer de taille N
        generator.addSink(graphAlea);
        generator.begin();
        while (graphAlea.getNodeCount() < N) {
            generator.nextEvents();
        }
        generator.end();
        if (graphAlea.getNodeCount() == N) {
            System.out.println("Graph aléatoire générer !");
        }


        return graphAlea;
    }

```
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Les caractéristiques de ce graphe sont : 
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Réseau aléatoire :
- Nombre de nœuds : 317080
- Nombre de liens : 1049473
- Degré moyen : 6.61961030960083
- Coefficient de clustering : 4.364833098823207E-5
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
j'ai aussi calculé les degrés de distribution à l'échelle linéaire et à l'échelle log-log
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
- Linéaire : 

![Distribution des degrés](degree_distribution_random.png)

- Log-log :

ABROUS Celia's avatar
ABROUS Celia a validé
![Distribution des degrés](degree_distribution_loglog_random.png)
ABROUS Celia's avatar
ABROUS Celia a validé


2 - Le graphe Barabàsi-Albert:

Cette méthode génère un graphe Barabási-Albert en utilisant le graphe d'entrée initialGraph comme référence. 
Le modèle Barabási-Albert est un modèle de croissance qui suit le principe de l'attachement préférentiel : 
les nouveaux nœuds ont une probabilité plus élevée de se connecter aux nœuds déjà fortement connectés (hubs). 
Ce modèle permet de générer des graphes sans échelle,
où la distribution des degrés suit une loi de puissance.



```java

public static Graph generateBarabasiAlbertGraph(Graph initialGraph) {
  int nodeCount = initialGraph.getNodeCount();
  double averageDegree = Toolkit.averageDegree(initialGraph);

  Graph barabasiGraph = new SingleGraph("Barabasi-Albert Network");
  BarabasiAlbertGenerator generator = new BarabasiAlbertGenerator((int) averageDegree);
  generator.addSink(barabasiGraph);
  generator.begin();
  while (barabasiGraph.getNodeCount() < nodeCount) {
    generator.nextEvents();
  }
  generator.end();
  return barabasiGraph;
}


```


Les caractéristiques de ce graphe sont :
ABROUS Celia's avatar
ABROUS Celia a validé

Nombre de nœuds : 317080
Nombre de liens : 1108680
Degré moyen : 6.993061542510986
ABROUS Celia's avatar
ABROUS Celia a validé
Coefficient de clustering : 4.235788849759945E-4


ABROUS Celia's avatar
ABROUS Celia a validé
j'ai aussi calculé les degrés de distribution à l'échelle linéaire et à l'échelle log-log

- Linéaire :

![Distribution des degrés](degree_distribution_ba.png)

- Log-log :

ABROUS Celia's avatar
ABROUS Celia a validé
![Distribution des degrés](degree_distribution_loglog_ba.png)
ABROUS Celia's avatar
ABROUS Celia a validé



ABROUS Celia's avatar
ABROUS Celia a validé
Comparaison des réseaux générés
En théorie, les deux modèles de réseaux, aléatoire
(Erdős-Rényi) et Barabási-Albert, se distinguent
par leur structure et leur comportement. 
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
En théorie :
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
- Réseau aléatoire :
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Les liens sont créés de manière totalement aléatoire entre les nœuds.
Le degré moyen suit une distribution de Poisson : 
la plupart des nœuds ont un degré proche de 
la moyenne, et il y a très peu de nœuds avec un
degré beaucoup plus élevé ou plus faible.
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Le coefficient de clustering est très faible car les
triangles (groupes de trois nœuds connectés entre 
eux) sont rares dans ce type de réseau.
ABROUS Celia's avatar
ABROUS Celia a validé


ABROUS Celia's avatar
ABROUS Celia a validé
- Réseau Barabási-Albert :
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Ce modèle suit une loi de puissance : quelques 
nœuds, appelés hubs, ont un très grand nombre de 
connexions, tandis que la majorité des nœuds sont 
faiblement connectés.
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
Le coefficient de clustering est plus élevé que
dans un réseau aléatoire, car les hubs favorisent
la connexion entre plusieurs nœuds, augmentant la
probabilité de former des triangles.
ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé

ABROUS Celia's avatar
ABROUS Celia a validé
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672

Les résultats expérimentaux correspondent à ces prédictions théoriques comme le démontre le tableau ci-dessous :


| Propriété                 | Réseau Aléatoire            | Réseau Barabási-Albert |
|---------------------------|-----------------------------|-------------------|
| **Nombre de nœuds**       | 317 080                     | 317 080           |
| **Nombre de liens**       | 1 049 907                   | 1 108 082         |
| **Degré moyen**           | 6,6223                      | 6,9893            |
| **Clustering**            | 3.57 × 10⁻⁵                 | 3.82 × 10⁻⁴         |

Le faible clustering du réseau random et le clustering un peu plus élevé du réseau BA sont conformes à ce qu'on attend.
En résumé, ces expériences confirment que le modèle Barabási-Albert est plus adapté pour modéliser des réseaux où les hubs
jouent un rôle important.

## 7- La méthode Copie :
La méthode de copie modifiée sert à générer un réseau en imitant des caractéristiques des réseaux réels, comme la formation 
de triangles et un haut coefficient de clustering.

Comment ça fonctionne ?

- On commence par choisir un nœud au hasard.

À chaque étape :

- Un nouveau nœud est ajouté au réseau.

- Ce nouveau nœud choisit au hasard un nœud existant dans le réseau, qu’on appelle nœud cible.

- Il parcourt les voisins du nœud cible. Pour chaque voisin, il a une probabilité p (par exemple, p=0,5) de se connecter à ce voisin.

- À la fin, il se connecte obligatoirement au nœud cible.

Pourquoi c'est intéressant ?

La probabilité p permet de créer des connexions locales, ce qui augmente le coefficient de clustering.

ATTENTION : Le nouveau nœud ne se connecte pas à tous les voisins du nœud cible, mais seulement à certains d’entre eux,
en fonction de la probabilité p.
Cette méthode garde le réseau connexe grâce à la connexion obligatoire au nœud cible.

Voici comment je m'y suis prise :

```java

public static Graph genererGrapheCopie(int nombreNoeuds, double probabilitéCopie) {
Graph grapheCopie = new SingleGraph("GrapheCopie");
Random aleatoire = new Random();

        // Ajouter un premier nœud pour démarrer le réseau
        grapheCopie.addNode("0");

        for (int i = 1; i < nombreNoeuds; i++) {
            Node nouveauNoeud = grapheCopie.addNode(String.valueOf(i));

            // Choisir un nœud aléatoire existant
            Node noeudCible = grapheCopie.getNode(aleatoire.nextInt(grapheCopie.getNodeCount()));

            // Parcourir les voisins de noeudCible
            noeudCible.edges().forEach(edge -> {
                if (edge != null) { // Vérifier que l'arête n'est pas null
                    Node voisin = edge.getOpposite(noeudCible);

                    // Connecter au voisin avec probabilité p
                    String edgeIdVoisin = nouveauNoeud.getId() + "-" + voisin.getId();
                    if (aleatoire.nextDouble() < probabilitéCopie && grapheCopie.getEdge(edgeIdVoisin) == null) {
                        grapheCopie.addEdge(edgeIdVoisin, nouveauNoeud, voisin);
                    }
                }
            });

            // Connecter le nouveau nœud au nœud cible
            String edgeIdCible = nouveauNoeud.getId() + "-" + noeudCible.getId();
            if (grapheCopie.getEdge(edgeIdCible) == null) {
                grapheCopie.addEdge(edgeIdCible, nouveauNoeud, noeudCible);
            }
        }

        return grapheCopie;
    }


```
Voici les résultats après éxecution du code :

- Nombre de nœuds : 317080
- Nombre de liens : 3392116
- Degré moyen : 21.395963668823242
- Coefficient de clustering : 0.44452828185775123

On remarque bien que le degré moyen est très élevé, mais aussi le coeff de clustring.





# TP 2 :

## 1 - Taux de propagation et seuil épidémique du réseau ainsi que le seuil théorique d'un réseau aléatoire du même degré moyen :

Avant de commencer, il faut savoir que ce modèle est un SIS (Susceptible-Infected-Susceptible)
##### Dans un modèle SIS:

chaque individu de la population peut être dans l’un des deux états suivants :

- Susceptible (S) :
L'individu est en bonne santé mais peut être infecté.
- Infecté (I) :
L'individu est malade et peut transmettre l'infection.

##### Dynamique des transitions
- Un individu susceptible (S) peut devenir infecté (I) après un contact avec une personne infectée, avec une probabilité appelée taux d'infection (β).
- Un individu infecté (I) peut se rétablir et redevenir susceptible (S) avec une probabilité appelée taux de récupération (μ).

La principale caractéristique du modèle SIS est que les individus infectés ne développent pas d'immunité après récupération, ce qui signifie qu'ils peuvent être réinfectés.


- Taux de propagation :

###### τ = β/μ 

avec β = 1 (une personne est infécté a partir du moment où elle reçoit un mail)

et μ = 0.5 ( il mettent à jour leurs anti-virus 2 fois par mois donc 2 semaines sur 4)


ce qui nous donne un taux de propagation (τ) : 2.0 que j'ai calculé comme suit :


```java
double infectionProbability = 1.0; // β = 1
double recoveryProbability = 0.5; // μ = 0.5

        double tau = infectionRate / recoveryRate;         // Taux de propagation
```

- Seuil épidémique :

###### c = < k > / <k^2>

avec <k> : degré moyen (le nombre moyen de connexions par nœud)

et <k^2>: moyenne des carrés des degrés des nœuds.

ce qui nous donne un seuil épidémique réel (c_réel) de 0.04598472436222584 que j'ai calculé comme suit :


```java
double cReal = avgDegree / avgDegreeSquared;
```
```java

private static double calculateSquaredAverageDegree(Graph graph) {
double totalDegreeSquared = 0;
for (Node node : graph) {
totalDegreeSquared += Math.pow(node.getDegree(), 2); // Somme des carrés des degrés
}
return totalDegreeSquared / graph.getNodeCount(); // Moyenne des carrés des degrés
}
```

enfin, le seuil théorique : 

```java
double cTheoretical = 1.0 / (avgDegree + 1);       // Seuil théorique pour un réseau aléatoire avec normalisation
```
ce qui nous donne un seuil théorique de 0.13119762993051035.

on constate que la maladie persiste La maladie persiste dans le réseau réel (τ > c_réel) et que le réseau réel est plus vulnérable que le réseau aléatoire (c_réel < c_théorique).

## 2 - Simulation des 3 scénario :

Voici les paramètres de ma méthode de simulation : il y'en a 6 :

- Graph graph : Le graphe
- double infectionRate : La probabilité qu'un nœud infecté transmette la maladie à un voisin susceptible.
- double recoveryRate : La probabilité qu'un nœud infecté se rétablisse et redevienne susceptible.
- int days : Le nombre total de jours pendant lesquels la simulation est exécutée.
- double immunizationFraction : La fraction de nœuds ou voisins à immuniser avant le début de la simulation.
- boolean randomImmunization : Détermine la stratégie d'immunisation :

true : Immunisation aléatoire.

false : Immunisation sélective (immuniser un voisin d'un nœud sélectionné).

Cette partie là concerne les deux scénario où il y'a des gens à immuniser (scénario 2 et 3)
ceci est assuré grâce au premier if, et le deuxième if nous permet de savoir si c'est une random immunisation donc le scénario 2 (on convint 50% des gens à mettre à jour leurs anti-virus) où la condition nous retournera true car c'est un boolean 
ou non et on passera au else donc le scénario 3 (on convint 50% des gens à convaincre un de leurs voisins).

```java
// Immunization
        if (immunizationFraction > 0) {
            int immunizedCount = (int) (immunizationFraction * graph.getNodeCount());
            if (randomImmunization) {
                // Immunisation aléatoire : immuniser des nœuds directement
                for (int i = 0; i < immunizedCount; i++) {
                    Node node = graph.getNode(random.nextInt(graph.getNodeCount()));
                    node.setAttribute("immune", true);
                }
            } else {
                // Immunisation sélective : immuniser un voisin pour 50% des nœuds existants
                List<Node> nodes = new ArrayList<>( graph.nodes().toList());

                // Calculer le nombre de nœuds à immuniser (50% des nœuds existants)
                int nodesToImmunize = (int) (immunizationFraction * nodes.size());

                // Mélanger les nœuds pour garantir une sélection aléatoire
                Collections.shuffle(nodes, random);

                // Sélectionner les premiers `nodesToImmunize` nœuds et immuniser un voisin pour chacun
                for (int i = 0; i < nodesToImmunize; i++) {
                    Node node = nodes.get(i);

                    // Vérifier que le nœud a des voisins
                    if (node.getDegree() > 0) {
                        // Prendre un voisin aléatoire
                        List<Node> neighbors = node.neighborNodes().toList();
                        Node neighbor = neighbors.get(random.nextInt(neighbors.size()));

                        // Immuniser ce voisin
                        neighbor.setAttribute("immune", true);
                    }
                }

            }

        }
```

viens maintenant la simulation (c'est un peu là ou tout se déroule y compris le scénario 1)

On commence par infecter un patient qu'on appellera patient 0.

- infectedCount : Nombre de nœuds infectés au cours de la journée actuelle.
- totalNonImmune : Nombre total de nœuds non immunisés dans le graphe 
(c'est-à-dire les nœuds susceptibles ou infectés).

- Exclusion des nœuds immunisés : Les nœuds avec l'attribut immune ne participent pas à la propagation.

###### Propagation (S→I) :
- Les nœuds infectés propagent l'infection à leurs voisins susceptibles avec une probabilité définie par infectionRate.
- Les nœuds susceptibles infectés reçoivent un état futur "infected" stocké dans nextState.
###### Récupération (I→S) :
- Les nœuds infectés peuvent récupérer avec une probabilité définie par recoveryRate et sont marqués comme "susceptible" dans nextState.


On fait ensuite une mise à jour des états des nœuds, Les changements d’état sont appliqués après chaque jour.

Résultats : La fraction de nœuds infectés est calculée chaque jour et retournée à la fin.

```java
// Infect patient zero
Node patientZero = graph.getNode(new Random().nextInt(graph.getNodeCount()));
        patientZero.setAttribute("state", "infected");


// Simulation
for (int day = 0; day < days; day++) {
int infectedCount = 0;
int totalNonImmune = 0;

            for (Node node : graph) {
                if (!node.hasAttribute("immune")) {
                    totalNonImmune++;
                    if (node.getAttribute("state").equals("infected")) {
                        infectedCount++;
                        for (Node neighbor : node.neighborNodes().toList()) {
                            if (!neighbor.hasAttribute("immune") && neighbor.getAttribute("state").equals("susceptible") && random.nextDouble() < infectionRate) {
                                neighbor.setAttribute("nextState", "infected");
                            }
                        }
                        if (random.nextDouble() < recoveryRate) {
                            node.setAttribute("nextState", "susceptible");
                        }
                    }
                }
            }
            
            // mise à jour de l'état des noeuds
            for (Node node : graph) {
                if (node.hasAttribute("nextState")) {
                    node.setAttribute("state", node.getAttribute("nextState"));
                    node.removeAttribute("nextState");
                }
            } 
            infectedFractions[day] = (double) infectedCount / totalNonImmune;
        }

                return infectedFractions;
    }
```
#### on obtient ces courbes qui montrent bien l'efficacité de l'immunisation séléctive.

![propagation simulation](propagationsimulation.png)


## 3 - Le degré moyen des groupes 0 et 1 :

Il faut savoir que le groupe 0 sont les nœuds choisis aléatoirement et le groupe un de leurs voisins.

Resumé :
- Groupe 0 => 50% des nœuds [non immunisé]
- Groupe 1 => 1 voisin de ces nœuds [immunisés]

voici comment j'ai procédé :

```java
// Appliquer une immunisation aléatoire (immuniser 50 % des nœuds)
        Set<Node> immune = new HashSet<>();
        Random random = new Random();
        int immuneCount = (int) (0.5 * graph.getNodeCount()); // Immuniser 50 % des nœuds
        List<Node> nodes = new ArrayList<>(graph.nodes().toList());
        Collections.shuffle(nodes, random);
        for (int i = 0; i < immuneCount; i++) {
            Node node = nodes.get(i);
            node.setAttribute("immune", true); // Marquer comme immunisé
            immune.add(node);
        }

        // Groupes
        Set<Node> group0 = new HashSet<>(); // Groupe 0 : 50 % des nœuds non immunisés
        Set<Node> group1 = new HashSet<>(); // Groupe 1 : Un voisin immunisé de chaque nœud du Groupe 0

        // Sélectionner 50 % des nœuds non immunisés pour le Groupe 0
        List<Node> nonImmuneNodes = new ArrayList<>();
        for (Node node : graph) {
            if (!immune.contains(node)) { // Nœuds non immunisés
                nonImmuneNodes.add(node);
            }
        }
        Collections.shuffle(nonImmuneNodes, random); // Mélanger les nœuds non immunisés
        int targetCount = (int) (0.5 * nonImmuneNodes.size()); // 50 % des nœuds non immunisés
        for (int i = 0; i < targetCount; i++) {
            group0.add(nonImmuneNodes.get(i)); // Ajouter au groupe 0
        }

        // Pour chaque nœud du Groupe 0, ajouter un voisin immunisé au Groupe 1
        for (Node node : group0) {
            List<Node> neighbors = node.neighborNodes().toList();
            for (Node neighbor : neighbors) {
                if (immune.contains(neighbor)) { // Vérifier si le voisin est immunisé
                    group1.add(neighbor); // Ajouter un seul voisin immunisé au groupe 1
                    break; // Sortir de la boucle après avoir trouvé un voisin immunisé
                }
            }
        }

        // Calcul du degré moyen pour le Groupe 0 (non immunisé)
        double totalDegreeGroup0 = 0;
        for (Node node : group0) {
            totalDegreeGroup0 += node.getDegree();
        }
        double averageDegreeGroup0 = group0.isEmpty() ? 0 : totalDegreeGroup0 / group0.size();
        System.out.printf("Degré moyen (Groupe 0 - Non immunisé) : %.9f%n", averageDegreeGroup0);

        // Calcul du degré moyen pour le Groupe 1 (voisins immunisés)
        double totalDegreeGroup1 = 0;
        for (Node node : group1) {
            totalDegreeGroup1 += node.getDegree();
        }
        double averageDegreeGroup1 = group1.isEmpty() ? 0 : totalDegreeGroup1 / group1.size();
        System.out.printf("Degré moyen (Groupe 1 - Voisins immunisés) : %.9f%n", averageDegreeGroup1);
```

Immunisation aléatoire :

50 % des nœuds du graphe sont sélectionnés de manière aléatoire pour être immunisés.

Ces nœuds sont marqués avec l'attribut "immune" et ajoutés à un ensemble immune.


###### Groupe 0 (non immunisé) :

les nœuds non immunisés forment le groupe 0.

Ces nœuds représentent une population aléatoire non immunisée dans le graphe.

###### Groupe 1 (voisins immunisés) :

Pour chaque nœud du Groupe 0, on cherche un voisin immunisé (parmi ceux marqués "immune").
Ce voisin immunisé est ajouté au Groupe 1.

Chaque nœud du Groupe 0 est associé à un seul voisin immunisé.


Voici les résultats : 

- Degré moyen (Groupe 0 - Non immunisé) : 6,601652580
- Degré moyen (Groupe 1 - Voisins immunisés) : 14,067850086

La différence entre les degrés moyens reflète l’efficacité de l’immunisation sélective :

###### Immunisation aléatoire : 
Le degré moyen est similaire au degré moyen global du graphe.
###### Immunisation sélective :
Le degré moyen est nettement plus élevé, car les voisins des hubs 
(nœuds critiques) sont ciblés, ce qui fragmente davantage le réseau.