@@ -389,22 +389,25 @@ public MonteCarloResult monteCarlo(Collection<String> seeds,
389389 Map <String , Integer > infectionFrequency = new LinkedHashMap <>();
390390
391391 for (int i = 0 ; i < numTrials ; i ++) {
392- SimulationResult result ;
392+ // Use lightweight simulation (no snapshots/timeline) to avoid
393+ // O(rounds × V) deep-copy overhead per trial. Monte Carlo only
394+ // needs final state + round count, not per-round snapshots.
395+ LightweightResult lr ;
393396 switch (model ) {
394397 case INDEPENDENT_CASCADE :
395- result = simulateIC (seeds , probability , maxRounds ); break ;
398+ lr = simulateICLightweight (seeds , probability , maxRounds ); break ;
396399 case LINEAR_THRESHOLD :
397- result = simulateLT (seeds , maxRounds ); break ;
400+ lr = simulateLTLightweight (seeds , maxRounds ); break ;
398401 case SIR :
399- result = simulateSIR (seeds , probability , recoveryRate , maxRounds ); break ;
402+ lr = simulateSIRLightweight (seeds , probability , recoveryRate , maxRounds ); break ;
400403 default :
401404 throw new IllegalArgumentException ("Unknown model: " + model );
402405 }
403406
404- spreads .add (result . getTotalInfected () );
405- durations .add (result . getRoundCount () );
407+ spreads .add (lr . totalInfected );
408+ durations .add (lr . rounds );
406409
407- for (Map .Entry <String , NodeState > entry : result . getFinalState () .entrySet ()) {
410+ for (Map .Entry <String , NodeState > entry : lr . finalState .entrySet ()) {
408411 if (entry .getValue () == NodeState .INFECTED ||
409412 entry .getValue () == NodeState .RECOVERED ) {
410413 infectionFrequency .merge (entry .getKey (), 1 , Integer ::sum );
@@ -509,6 +512,124 @@ public VaccinationStrategy findVaccinationTargets(int k) {
509512 return new VaccinationStrategy (targets , totalEdgesBlocked , coverageRatio );
510513 }
511514
515+ // ─── Lightweight simulation (Monte Carlo fast path) ───────────
516+
517+ /**
518+ * Minimal result for Monte Carlo: final state + round count only.
519+ * Avoids the per-round LinkedHashMap deep-copies and InfectionEvent
520+ * timeline that {@link SimulationResult} carries.
521+ */
522+ private static class LightweightResult {
523+ final Map <String , NodeState > finalState ;
524+ final int rounds ;
525+ final int totalInfected ;
526+
527+ LightweightResult (Map <String , NodeState > finalState , int rounds ) {
528+ this .finalState = finalState ;
529+ this .rounds = rounds ;
530+ int cnt = 0 ;
531+ for (NodeState s : finalState .values ())
532+ if (s == NodeState .INFECTED || s == NodeState .RECOVERED ) cnt ++;
533+ this .totalInfected = cnt ;
534+ }
535+ }
536+
537+ private LightweightResult simulateICLightweight (Collection <String > seeds ,
538+ double probability , int maxRounds ) {
539+ Map <String , NodeState > state = initState (seeds );
540+ Set <String > newlyInfected = new LinkedHashSet <>();
541+ for (String seed : seeds )
542+ if (graph .containsVertex (seed )) newlyInfected .add (seed );
543+ int round = 0 ;
544+
545+ while (!newlyInfected .isEmpty ()) {
546+ round ++;
547+ if (maxRounds > 0 && round > maxRounds ) break ;
548+ Set <String > nextInfected = new LinkedHashSet <>();
549+ for (String node : newlyInfected ) {
550+ for (String neighbor : getNeighbors (node )) {
551+ if (state .get (neighbor ) == NodeState .SUSCEPTIBLE ) {
552+ double edgeProb = getEdgeProbability (node , neighbor , probability );
553+ if (random .nextDouble () < edgeProb ) {
554+ state .put (neighbor , NodeState .INFECTED );
555+ nextInfected .add (neighbor );
556+ }
557+ }
558+ }
559+ }
560+ for (String node : newlyInfected ) state .put (node , NodeState .RECOVERED );
561+ newlyInfected = nextInfected ;
562+ }
563+ return new LightweightResult (state , round );
564+ }
565+
566+ private LightweightResult simulateLTLightweight (Collection <String > seeds , int maxRounds ) {
567+ Map <String , NodeState > state = initState (seeds );
568+ Map <String , Double > thresholds = new HashMap <>();
569+ for (String node : graph .getVertices ()) thresholds .put (node , random .nextDouble ());
570+
571+ int round = 0 ;
572+ boolean changed = true ;
573+ while (changed ) {
574+ round ++;
575+ if (maxRounds > 0 && round > maxRounds ) break ;
576+ changed = false ;
577+ List <String > toActivate = new ArrayList <>();
578+ for (String node : graph .getVertices ()) {
579+ if (state .get (node ) != NodeState .SUSCEPTIBLE ) continue ;
580+ Collection <String > influencers = getPredecessors (node );
581+ if (influencers .isEmpty ()) continue ;
582+ int active = 0 ;
583+ for (String nb : influencers )
584+ if (state .get (nb ) == NodeState .INFECTED || state .get (nb ) == NodeState .RECOVERED )
585+ active ++;
586+ if ((double ) active / influencers .size () >= thresholds .get (node ))
587+ toActivate .add (node );
588+ }
589+ for (String node : toActivate ) {
590+ state .put (node , NodeState .INFECTED );
591+ changed = true ;
592+ }
593+ }
594+ return new LightweightResult (state , round );
595+ }
596+
597+ private LightweightResult simulateSIRLightweight (Collection <String > seeds ,
598+ double infectionRate ,
599+ double recoveryRate , int maxRounds ) {
600+ Map <String , NodeState > state = initState (seeds );
601+ Set <String > currentlyInfected = new LinkedHashSet <>();
602+ for (String seed : seeds )
603+ if (graph .containsVertex (seed )) currentlyInfected .add (seed );
604+ int round = 0 ;
605+
606+ while (!currentlyInfected .isEmpty ()) {
607+ round ++;
608+ if (maxRounds > 0 && round > maxRounds ) break ;
609+ Set <String > toInfect = new LinkedHashSet <>();
610+ Set <String > toRecover = new LinkedHashSet <>();
611+ for (String node : currentlyInfected ) {
612+ for (String neighbor : getNeighbors (node )) {
613+ if (state .get (neighbor ) == NodeState .SUSCEPTIBLE && !toInfect .contains (neighbor )) {
614+ double edgeProb = getEdgeProbability (node , neighbor , infectionRate );
615+ if (random .nextDouble () < edgeProb ) toInfect .add (neighbor );
616+ }
617+ }
618+ }
619+ for (String node : currentlyInfected )
620+ if (random .nextDouble () < recoveryRate ) toRecover .add (node );
621+ for (String node : toInfect ) {
622+ state .put (node , NodeState .INFECTED );
623+ currentlyInfected .add (node );
624+ }
625+ for (String node : toRecover ) {
626+ state .put (node , NodeState .RECOVERED );
627+ currentlyInfected .remove (node );
628+ }
629+ }
630+ return new LightweightResult (state , round );
631+ }
632+
512633 // ─── Helpers ────────────────────────────────────────────────
513634
514635 private void validateSeeds (Collection <String > seeds ) {
0 commit comments