com.net2plan.internal.sim.SimStats.java Source code

Java tutorial

Introduction

Here is the source code for com.net2plan.internal.sim.SimStats.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Pablo Pavon Mario.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 * 
 * Contributors:
 *     Pablo Pavon Mario - initial API and implementation
 ******************************************************************************/

package com.net2plan.internal.sim;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLOutputFactory;

import org.apache.commons.lang3.mutable.MutableDouble;
import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.XMLStreamWriter2;

import com.net2plan.interfaces.networkDesign.Demand;
import com.net2plan.interfaces.networkDesign.Link;
import com.net2plan.interfaces.networkDesign.NetPlan;
import com.net2plan.interfaces.networkDesign.NetworkLayer;
import com.net2plan.interfaces.networkDesign.Node;
import com.net2plan.utils.HTMLUtils;
import com.net2plan.utils.StringUtils;

/**
 * Abstract class defining a template for statistics classes for simulations.
 *
 * @author Pablo Pavon-Marino, Jose-Luis Izquierdo-Zaragoza
 * @since 0.2.3
 */
public class SimStats {
    /* Input and Net2Plan-wide parameters */
    private final double precisionFactor;

    private final NetPlan netState;

    private double lastEventTime, transitoryTime;

    private Set<Long> previousState_layerIds;
    private Set<Long> previousState_nodeIds, previousState_nodeDownIds;
    private Map<Long, Set<Long>> previousState_linkIds, previousState_linkDownIds, previousState_demandIds;

    /* Network information */
    private double accum_avgNumLayers, accum_avgNumNodes;
    private int maxNumLayers, maxNumNodes, minNumLayers, minNumNodes;

    /* Layer information */
    private Map<Long, MutableDouble> accum_layerTotalTime, accum_avgNumLinks, accum_avgNumDemands,
            accum_avgTotalOfferedTraffic, maxTotalOfferedTraffic, minTotalOfferedTraffic,
            accum_avgTotalCarriedTraffic, maxTotalCarriedTraffic, minTotalCarriedTraffic, accum_avgTotalCapacity,
            maxTotalCapacity, minTotalCapacity, accum_avgCongestion, maxCongestion, minCongestion,
            accum_availabilityClassic, accum_availabilityWeighted, worstDemandAvailabilityClassic,
            worstDemandAvailabilityWeighted;
    private Map<Long, Integer> maxNumLinks, minNumLinks, maxNumDemands, minNumDemands;

    /* Node information */
    private Map<Long, MutableDouble> accum_nodeUpTime, accum_nodeTotalTime;
    private Map<Long, Map<Long, MutableDouble>> accum_avgNodeInDegree, accum_avgNodeOutDegree,
            accum_avgNodeIngressTraffic, accum_avgNodeEgressTraffic;
    private Map<Long, Map<Long, Double>> previousState_nodeIngressTraffic, previousState_nodeEgressTraffic,
            maxNodeIngressTraffic, minNodeIngressTraffic, maxNodeEgressTraffic, minNodeEgressTraffic;
    private Map<Long, Map<Long, Integer>> previousState_nodeInDegree, previousState_nodeOutDegree, maxNodeInDegree,
            maxNodeOutDegree, minNodeInDegree, minNodeOutDegree;

    /* Link information */
    private Map<Long, Map<Long, Double>> previousState_linkLengthInKm, previousState_linkCapacity,
            previousState_linkOccupiedCapacity;
    private Map<Long, Map<Long, MutableDouble>> accum_avgLinkLengthInKm, minLinkLengthInKm, maxLinkLengthInKm;
    private Map<Long, Map<Long, MutableDouble>> accum_avgCapacity, minCapacity, maxCapacity;
    private Map<Long, Map<Long, MutableDouble>> accum_avgLinkOccupiedCapacity, minLinkOccupiedCapacity,
            maxLinkOccupiedCapacity;
    private Map<Long, Map<Long, MutableDouble>> accum_avgUtilization, minUtilization, maxUtilization;
    private Map<Long, Map<Long, MutableDouble>> accum_avgOversubscribedCapacity, minOversubscribedCapacity,
            maxOversubscribedCapacity;
    private Map<Long, Map<Long, MutableDouble>> accum_linkOversubscribedTime, accum_linkUpTime, accum_linkTotalTime;

    /* Demand information */
    private Map<Long, Map<Long, Double>> previousState_demandOfferedTraffic, previousState_demandCarriedTraffic;
    private Map<Long, Map<Long, MutableDouble>> accum_avgDemandOfferedTraffic, minDemandOfferedTraffic,
            maxDemandOfferedTraffic;
    private Map<Long, Map<Long, MutableDouble>> accum_avgDemandCarriedTraffic, minDemandCarriedTraffic,
            maxDemandCarriedTraffic;
    private Map<Long, Map<Long, MutableDouble>> accum_avgDemandBlockedTraffic, minDemandBlockedTraffic,
            maxDemandBlockedTraffic;
    private Map<Long, Map<Long, MutableDouble>> accum_avgExcessCarriedTraffic, minDemandExcessCarriedTraffic,
            maxDemandExcessCarriedTraffic;
    private Map<Long, Map<Long, MutableDouble>> accum_demandAvailabilityClassic, accum_demandAvailabilityWeighted;
    private Map<Long, Map<Long, MutableDouble>> excessDemandCarriedTrafficTime, demandTotalTime;

    /**
     * Default constructor.
     * 
     * @param netPlan  Reference to the planned network design
     * @param netState Reference to the current network state
     * @param simulationParameters A key-value map with simulation options
     * @param net2planParameters A key-value map with {@code Net2Plan}-wide configuration options
     * @since 0.3.0
     */
    public SimStats(NetPlan netState, Map<String, String> simulationParameters,
            Map<String, String> net2planParameters) {
        this.netState = netState;
        precisionFactor = Double.parseDouble(net2planParameters.get("precisionFactor"));

        reset(0);
    }

    private void checkAndCreateDemand(long layerId, long demandId) {
        if (!accum_avgDemandOfferedTraffic.get(layerId).containsKey(demandId)) {
            /* Initialize demand information */
            accum_avgDemandOfferedTraffic.get(layerId).put(demandId, new MutableDouble());
            minDemandOfferedTraffic.get(layerId).put(demandId, new MutableDouble(Double.MAX_VALUE));
            maxDemandOfferedTraffic.get(layerId).put(demandId, new MutableDouble());
            accum_avgDemandCarriedTraffic.get(layerId).put(demandId, new MutableDouble());
            minDemandCarriedTraffic.get(layerId).put(demandId, new MutableDouble(Double.MAX_VALUE));
            maxDemandCarriedTraffic.get(layerId).put(demandId, new MutableDouble());
            accum_avgDemandBlockedTraffic.get(layerId).put(demandId, new MutableDouble());
            minDemandBlockedTraffic.get(layerId).put(demandId, new MutableDouble(Double.MAX_VALUE));
            maxDemandBlockedTraffic.get(layerId).put(demandId, new MutableDouble());
            accum_avgExcessCarriedTraffic.get(layerId).put(demandId, new MutableDouble());
            minDemandExcessCarriedTraffic.get(layerId).put(demandId, new MutableDouble(Double.MAX_VALUE));
            maxDemandExcessCarriedTraffic.get(layerId).put(demandId, new MutableDouble());
            accum_demandAvailabilityClassic.get(layerId).put(demandId, new MutableDouble());
            accum_demandAvailabilityWeighted.get(layerId).put(demandId, new MutableDouble());
            excessDemandCarriedTrafficTime.get(layerId).put(demandId, new MutableDouble());
            demandTotalTime.get(layerId).put(demandId, new MutableDouble());
        }
    }

    private void checkAndCreateLayer(long layerId) {
        if (!accum_avgLinkLengthInKm.containsKey(layerId)) {
            /* Initialize layer information */
            accum_layerTotalTime.put(layerId, new MutableDouble());
            accum_avgNumLinks.put(layerId, new MutableDouble());
            accum_avgNumDemands.put(layerId, new MutableDouble());
            accum_avgTotalOfferedTraffic.put(layerId, new MutableDouble());
            maxTotalOfferedTraffic.put(layerId, new MutableDouble());
            minTotalOfferedTraffic.put(layerId, new MutableDouble(Double.MAX_VALUE));
            accum_avgTotalCarriedTraffic.put(layerId, new MutableDouble());
            maxTotalCarriedTraffic.put(layerId, new MutableDouble());
            minTotalCarriedTraffic.put(layerId, new MutableDouble(Double.MAX_VALUE));
            accum_avgTotalCapacity.put(layerId, new MutableDouble());
            maxTotalCapacity.put(layerId, new MutableDouble());
            minTotalCapacity.put(layerId, new MutableDouble(Double.MAX_VALUE));
            accum_avgCongestion.put(layerId, new MutableDouble());
            maxCongestion.put(layerId, new MutableDouble());
            minCongestion.put(layerId, new MutableDouble(Double.MAX_VALUE));
            accum_availabilityClassic.put(layerId, new MutableDouble());
            accum_availabilityWeighted.put(layerId, new MutableDouble());
            worstDemandAvailabilityClassic.put(layerId, new MutableDouble(1));
            worstDemandAvailabilityWeighted.put(layerId, new MutableDouble(1));
            maxNumLinks.put(layerId, 0);
            minNumLinks.put(layerId, Integer.MAX_VALUE);
            maxNumDemands.put(layerId, 0);
            minNumDemands.put(layerId, Integer.MAX_VALUE);

            /* Initialize node information in this layer */
            accum_avgNodeInDegree.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minNodeInDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            maxNodeInDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            accum_avgNodeOutDegree.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minNodeOutDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            maxNodeOutDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            accum_avgNodeIngressTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxNodeIngressTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            minNodeIngressTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            accum_avgNodeEgressTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxNodeEgressTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            minNodeEgressTraffic.put(layerId, new LinkedHashMap<Long, Double>());

            /* Initialize link information in this layer */
            accum_avgLinkLengthInKm.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minLinkLengthInKm.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxLinkLengthInKm.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgLinkOccupiedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minLinkOccupiedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxLinkOccupiedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgUtilization.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minUtilization.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxUtilization.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgOversubscribedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minOversubscribedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxOversubscribedCapacity.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_linkOversubscribedTime.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_linkUpTime.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_linkTotalTime.put(layerId, new LinkedHashMap<Long, MutableDouble>());

            /* Initialize demand information in this layer */
            accum_avgDemandOfferedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minDemandOfferedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxDemandOfferedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgDemandCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minDemandCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxDemandCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgDemandBlockedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minDemandBlockedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxDemandBlockedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_avgExcessCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            minDemandExcessCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            maxDemandExcessCarriedTraffic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_demandAvailabilityClassic.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            accum_demandAvailabilityWeighted.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            excessDemandCarriedTrafficTime.put(layerId, new LinkedHashMap<Long, MutableDouble>());
            demandTotalTime.put(layerId, new LinkedHashMap<Long, MutableDouble>());
        }
    }

    private void checkAndCreateLink(long layerId, long linkId) {
        if (!accum_avgLinkLengthInKm.get(layerId).containsKey(linkId)) {
            /* Initialize link information */
            accum_avgLinkLengthInKm.get(layerId).put(linkId, new MutableDouble());
            minLinkLengthInKm.get(layerId).put(linkId, new MutableDouble(Double.MAX_VALUE));
            maxLinkLengthInKm.get(layerId).put(linkId, new MutableDouble());
            accum_avgCapacity.get(layerId).put(linkId, new MutableDouble());
            minCapacity.get(layerId).put(linkId, new MutableDouble(Double.MAX_VALUE));
            maxCapacity.get(layerId).put(linkId, new MutableDouble());
            accum_avgLinkOccupiedCapacity.get(layerId).put(linkId, new MutableDouble());
            minLinkOccupiedCapacity.get(layerId).put(linkId, new MutableDouble(Double.MAX_VALUE));
            maxLinkOccupiedCapacity.get(layerId).put(linkId, new MutableDouble());
            accum_avgUtilization.get(layerId).put(linkId, new MutableDouble());
            minUtilization.get(layerId).put(linkId, new MutableDouble(Double.MAX_VALUE));
            maxUtilization.get(layerId).put(linkId, new MutableDouble());
            accum_avgOversubscribedCapacity.get(layerId).put(linkId, new MutableDouble());
            minOversubscribedCapacity.get(layerId).put(linkId, new MutableDouble(Double.MAX_VALUE));
            maxOversubscribedCapacity.get(layerId).put(linkId, new MutableDouble());
            accum_linkOversubscribedTime.get(layerId).put(linkId, new MutableDouble());
            accum_linkUpTime.get(layerId).put(linkId, new MutableDouble());
            accum_linkTotalTime.get(layerId).put(linkId, new MutableDouble());
        }
    }

    private void checkAndCreateNode(long nodeId) {
        if (!accum_nodeUpTime.containsKey(nodeId)) {
            /* Initialize node information */
            accum_nodeUpTime.put(nodeId, new MutableDouble());
            accum_nodeTotalTime.put(nodeId, new MutableDouble());
        }
    }

    private void checkAndCreateNode(long layerId, long nodeId) {
        if (!accum_avgNodeInDegree.get(layerId).containsKey(nodeId)) {
            accum_avgNodeInDegree.get(layerId).put(nodeId, new MutableDouble());
            minNodeInDegree.get(layerId).put(nodeId, Integer.MAX_VALUE);
            maxNodeInDegree.get(layerId).put(nodeId, 0);
            accum_avgNodeOutDegree.get(layerId).put(nodeId, new MutableDouble());
            minNodeOutDegree.get(layerId).put(nodeId, Integer.MAX_VALUE);
            maxNodeOutDegree.get(layerId).put(nodeId, 0);
            accum_avgNodeIngressTraffic.get(layerId).put(nodeId, new MutableDouble());
            maxNodeIngressTraffic.get(layerId).put(nodeId, 0.0);
            minNodeIngressTraffic.get(layerId).put(nodeId, Double.MAX_VALUE);
            accum_avgNodeEgressTraffic.get(layerId).put(nodeId, new MutableDouble());
            maxNodeEgressTraffic.get(layerId).put(nodeId, 0.0);
            minNodeEgressTraffic.get(layerId).put(nodeId, Double.MAX_VALUE);
        }
    }

    /**
     * Computes statistics for the current simulation time.
     *
     * @param simTime Current simulation time
     * @since 0.3.0
     */
    public void computeNextState(double simTime) {
        /* Do not update metrics for events in the same simulation time */
        if (simTime > lastEventTime) {
            /*
             * Update metrics:
             * - Cumulative metrics: accum += previous * timeInterval
             * - Max/min metrics: metric = max/min(metric, previous)
             */
            double timeInterval = simTime - lastEventTime;

            /* Network metrics */
            int numLayers = previousState_layerIds.size();
            accum_avgNumLayers += numLayers * timeInterval;
            minNumLayers = Math.min(numLayers, minNumLayers);
            maxNumLayers = Math.max(numLayers, maxNumLayers);

            int numNodes = previousState_nodeIds.size();
            accum_avgNumNodes += numNodes * timeInterval;
            minNumNodes = Math.min(numNodes, minNumNodes);
            maxNumNodes = Math.max(numNodes, maxNumNodes);

            for (long nodeId : previousState_nodeIds) {
                checkAndCreateNode(nodeId);

                if (netState.getNodeFromId(nodeId) != null) {
                    if (!previousState_nodeDownIds.contains(nodeId))
                        accum_nodeUpTime.get(nodeId).add(timeInterval);
                    accum_nodeTotalTime.get(nodeId).add(timeInterval);
                } else {
                    accum_nodeUpTime.remove(nodeId);
                    accum_nodeTotalTime.remove(nodeId);
                }
            }

            for (long layerId : previousState_layerIds) {
                checkAndCreateLayer(layerId);

                if (netState.getNetworkLayerFromId(layerId) != null) {
                    accum_layerTotalTime.get(layerId).add(timeInterval);

                    for (long nodeId : previousState_nodeIds) {
                        checkAndCreateNode(layerId, nodeId);

                        if (netState.getNodeFromId(nodeId) != null) {
                            int nodeInDegree = previousState_nodeInDegree.get(layerId).get(nodeId);
                            accum_avgNodeInDegree.get(layerId).get(nodeId).add(nodeInDegree * timeInterval);
                            minNodeInDegree.get(layerId).put(nodeId,
                                    Math.min(nodeInDegree, minNodeInDegree.get(layerId).get(nodeId)));
                            maxNodeInDegree.get(layerId).put(nodeId,
                                    Math.max(nodeInDegree, maxNodeInDegree.get(layerId).get(nodeId)));

                            int nodeOutDegree = previousState_nodeOutDegree.get(layerId).get(nodeId);
                            accum_avgNodeOutDegree.get(layerId).get(nodeId).add(nodeOutDegree * timeInterval);
                            minNodeOutDegree.get(layerId).put(nodeId,
                                    Math.min(nodeInDegree, minNodeOutDegree.get(layerId).get(nodeId)));
                            maxNodeOutDegree.get(layerId).put(nodeId,
                                    Math.max(nodeInDegree, maxNodeOutDegree.get(layerId).get(nodeId)));

                            double nodeIngressTraffic = previousState_nodeIngressTraffic.get(layerId).get(nodeId);
                            accum_avgNodeIngressTraffic.get(layerId).get(nodeId)
                                    .add(nodeIngressTraffic * timeInterval);
                            minNodeIngressTraffic.get(layerId).put(nodeId,
                                    Math.min(nodeIngressTraffic, minNodeIngressTraffic.get(layerId).get(nodeId)));
                            maxNodeIngressTraffic.get(layerId).put(nodeId,
                                    Math.max(nodeIngressTraffic, maxNodeIngressTraffic.get(layerId).get(nodeId)));

                            double nodeEgressTraffic = previousState_nodeEgressTraffic.get(layerId).get(nodeId);
                            accum_avgNodeEgressTraffic.get(layerId).get(nodeId)
                                    .add(nodeEgressTraffic * timeInterval);
                            minNodeEgressTraffic.get(layerId).put(nodeId,
                                    Math.min(nodeEgressTraffic, minNodeEgressTraffic.get(layerId).get(nodeId)));
                            maxNodeEgressTraffic.get(layerId).put(nodeId,
                                    Math.max(nodeEgressTraffic, maxNodeEgressTraffic.get(layerId).get(nodeId)));

                        } else {
                            accum_avgNodeInDegree.get(layerId).remove(nodeId);
                            minNodeInDegree.get(layerId).remove(nodeId);
                            maxNodeInDegree.get(layerId).remove(nodeId);
                            accum_avgNodeOutDegree.get(layerId).remove(nodeId);
                            minNodeOutDegree.get(layerId).remove(nodeId);
                            maxNodeOutDegree.get(layerId).remove(nodeId);
                            accum_avgNodeIngressTraffic.get(layerId).remove(nodeId);
                            maxNodeIngressTraffic.get(layerId).remove(nodeId);
                            minNodeIngressTraffic.get(layerId).remove(nodeId);
                            accum_avgNodeEgressTraffic.get(layerId).remove(nodeId);
                            maxNodeEgressTraffic.get(layerId).remove(nodeId);
                            minNodeEgressTraffic.get(layerId).remove(nodeId);
                        }
                    }

                    Set<Long> previousState_linkIds_thisLayer = previousState_linkIds.get(layerId);
                    int numLinks = previousState_linkIds_thisLayer.size();
                    accum_avgNumLinks.get(layerId).add(numLinks * timeInterval);
                    maxNumLinks.put(layerId, Math.max(numLinks, maxNumLinks.get(layerId)));
                    minNumLinks.put(layerId, Math.min(numLinks, minNumLinks.get(layerId)));

                    Set<Long> previousState_demandIds_thisLayer = previousState_demandIds.get(layerId);
                    int numDemands = previousState_demandIds_thisLayer.size();
                    accum_avgNumDemands.get(layerId).add(numDemands * timeInterval);
                    maxNumDemands.put(layerId, Math.max(numDemands, maxNumDemands.get(layerId)));
                    minNumDemands.put(layerId, Math.min(numDemands, minNumDemands.get(layerId)));

                    double totalCapacityInstalled = 0;
                    double congestion = 0;

                    Set<Long> previousState_linkDownIds_thisLayer = previousState_linkDownIds.get(layerId);
                    Map<Long, Double> previousState_linkCapacity_thisLayer = previousState_linkCapacity
                            .get(layerId);
                    Map<Long, Double> previousState_linkOccupiedCapacity_thisLayer = previousState_linkOccupiedCapacity
                            .get(layerId);
                    Map<Long, Double> previousState_linkLengthInKm_thisLayer = previousState_linkLengthInKm
                            .get(layerId);
                    for (long linkId : previousState_linkIds_thisLayer) {
                        double u_e = previousState_linkCapacity_thisLayer.get(linkId);
                        double y_e = previousState_linkOccupiedCapacity_thisLayer.get(linkId);
                        double rho_e = y_e == 0 ? 0 : Math.max(y_e / u_e, 0);

                        totalCapacityInstalled += u_e;
                        congestion = Math.max(congestion, rho_e);

                        checkAndCreateLink(layerId, linkId);

                        if (netState.getLinkFromId(linkId) != null) {
                            double l_e = previousState_linkLengthInKm_thisLayer.get(linkId);
                            double oversubscribedCapacity = y_e - u_e;
                            if (oversubscribedCapacity < precisionFactor)
                                oversubscribedCapacity = 0;

                            accum_avgLinkLengthInKm.get(layerId).get(linkId).add(l_e * timeInterval);
                            minLinkLengthInKm.get(layerId).get(linkId).setValue(
                                    Math.min(minLinkLengthInKm.get(layerId).get(linkId).doubleValue(), l_e));
                            maxLinkLengthInKm.get(layerId).get(linkId).setValue(
                                    Math.max(maxLinkLengthInKm.get(layerId).get(linkId).doubleValue(), l_e));
                            accum_avgCapacity.get(layerId).get(linkId).add(u_e * timeInterval);
                            minCapacity.get(layerId).get(linkId)
                                    .setValue(Math.min(minCapacity.get(layerId).get(linkId).doubleValue(), u_e));
                            maxCapacity.get(layerId).get(linkId)
                                    .setValue(Math.max(maxCapacity.get(layerId).get(linkId).doubleValue(), u_e));
                            accum_avgLinkOccupiedCapacity.get(layerId).get(linkId).add(y_e * timeInterval);
                            minLinkOccupiedCapacity.get(layerId).get(linkId).setValue(
                                    Math.min(minLinkOccupiedCapacity.get(layerId).get(linkId).doubleValue(), y_e));
                            maxLinkOccupiedCapacity.get(layerId).get(linkId).setValue(
                                    Math.max(maxLinkOccupiedCapacity.get(layerId).get(linkId).doubleValue(), y_e));
                            accum_avgUtilization.get(layerId).get(linkId).add(rho_e * timeInterval);
                            minUtilization.get(layerId).get(linkId).setValue(
                                    Math.min(minUtilization.get(layerId).get(linkId).doubleValue(), rho_e));
                            maxUtilization.get(layerId).get(linkId).setValue(
                                    Math.max(maxUtilization.get(layerId).get(linkId).doubleValue(), rho_e));
                            accum_avgOversubscribedCapacity.get(layerId).get(linkId)
                                    .add(oversubscribedCapacity * timeInterval);
                            minOversubscribedCapacity.get(layerId).get(linkId)
                                    .setValue(Math.min(
                                            minOversubscribedCapacity.get(layerId).get(linkId).doubleValue(),
                                            oversubscribedCapacity));
                            maxOversubscribedCapacity.get(layerId).get(linkId)
                                    .setValue(Math.max(
                                            maxOversubscribedCapacity.get(layerId).get(linkId).doubleValue(),
                                            oversubscribedCapacity));
                            if (oversubscribedCapacity > 0)
                                accum_linkOversubscribedTime.get(layerId).get(linkId).add(timeInterval);
                            if (!previousState_linkDownIds_thisLayer.contains(linkId))
                                accum_linkUpTime.get(layerId).get(linkId).add(timeInterval);
                            accum_linkTotalTime.get(layerId).get(linkId).add(timeInterval);
                        } else {
                            accum_avgLinkLengthInKm.get(layerId).remove(linkId);
                            minLinkLengthInKm.get(layerId).remove(linkId);
                            maxLinkLengthInKm.get(layerId).remove(linkId);
                            accum_avgCapacity.get(layerId).remove(linkId);
                            minCapacity.get(layerId).remove(linkId);
                            maxCapacity.get(layerId).remove(linkId);
                            accum_avgLinkOccupiedCapacity.get(layerId).remove(linkId);
                            minLinkOccupiedCapacity.get(layerId).remove(linkId);
                            maxLinkOccupiedCapacity.get(layerId).remove(linkId);
                            accum_avgUtilization.get(layerId).remove(linkId);
                            minUtilization.get(layerId).remove(linkId);
                            maxUtilization.get(layerId).remove(linkId);
                            accum_avgOversubscribedCapacity.get(layerId).remove(linkId);
                            minOversubscribedCapacity.get(layerId).remove(linkId);
                            maxOversubscribedCapacity.get(layerId).remove(linkId);
                            accum_linkOversubscribedTime.get(layerId).remove(linkId);
                            accum_linkUpTime.get(layerId).remove(linkId);
                            accum_linkTotalTime.get(layerId).remove(linkId);
                        }
                    }

                    accum_avgTotalCapacity.get(layerId).add(totalCapacityInstalled * timeInterval);
                    maxTotalCapacity.get(layerId).setValue(
                            Math.max(totalCapacityInstalled, maxTotalCapacity.get(layerId).doubleValue()));
                    minTotalCapacity.get(layerId).setValue(
                            Math.min(totalCapacityInstalled, minTotalCapacity.get(layerId).doubleValue()));
                    accum_avgCongestion.get(layerId).add(congestion * timeInterval);
                    maxCongestion.get(layerId)
                            .setValue(Math.max(congestion, maxCongestion.get(layerId).doubleValue()));
                    minCongestion.get(layerId)
                            .setValue(Math.min(congestion, minCongestion.get(layerId).doubleValue()));

                    double totalOfferedTraffic = 0;
                    double totalCarriedTraffic = 0;
                    double totalBlockedTraffic = 0;

                    double worstDemandAvailabilityClassic_thisLayer = 1;
                    double worstDemandAvailabilityWeighted_thisLayer = 1;

                    Map<Long, Double> previousState_demandOfferedTraffic_thislayer = previousState_demandOfferedTraffic
                            .get(layerId);
                    Map<Long, Double> previousState_demandCarriedTraffic_thisLayer = previousState_demandCarriedTraffic
                            .get(layerId);
                    for (long demandId : previousState_demandIds_thisLayer) {
                        double h_d = previousState_demandOfferedTraffic_thislayer.get(demandId);
                        double r_d = previousState_demandCarriedTraffic_thisLayer.get(demandId);
                        double blockedTraffic_d = h_d - r_d;
                        if (blockedTraffic_d < precisionFactor)
                            blockedTraffic_d = 0;
                        totalOfferedTraffic += h_d;
                        totalCarriedTraffic += r_d;
                        totalBlockedTraffic += blockedTraffic_d;

                        checkAndCreateDemand(layerId, demandId);

                        if (netState.getDemandFromId(demandId) != null) {
                            double excessCarriedTraffic_d = r_d - h_d;
                            if (excessCarriedTraffic_d < precisionFactor)
                                excessCarriedTraffic_d = 0;

                            accum_avgDemandOfferedTraffic.get(layerId).get(demandId).add(h_d * timeInterval);
                            minDemandOfferedTraffic.get(layerId).get(demandId).setValue(Math
                                    .min(minDemandOfferedTraffic.get(layerId).get(demandId).doubleValue(), h_d));
                            maxDemandOfferedTraffic.get(layerId).get(demandId).setValue(Math
                                    .max(maxDemandOfferedTraffic.get(layerId).get(demandId).doubleValue(), h_d));
                            accum_avgDemandCarriedTraffic.get(layerId).get(demandId).add(r_d * timeInterval);
                            minDemandCarriedTraffic.get(layerId).get(demandId).setValue(Math
                                    .min(minDemandCarriedTraffic.get(layerId).get(demandId).doubleValue(), r_d));
                            maxDemandCarriedTraffic.get(layerId).get(demandId).setValue(Math
                                    .max(maxDemandCarriedTraffic.get(layerId).get(demandId).doubleValue(), r_d));
                            accum_avgDemandBlockedTraffic.get(layerId).get(demandId)
                                    .add(blockedTraffic_d * timeInterval);
                            minDemandBlockedTraffic.get(layerId).get(demandId)
                                    .setValue(Math.min(
                                            minDemandBlockedTraffic.get(layerId).get(demandId).doubleValue(),
                                            blockedTraffic_d));
                            maxDemandBlockedTraffic.get(layerId).get(demandId)
                                    .setValue(Math.max(
                                            maxDemandBlockedTraffic.get(layerId).get(demandId).doubleValue(),
                                            blockedTraffic_d));
                            if (blockedTraffic_d == 0)
                                accum_demandAvailabilityClassic.get(layerId).get(demandId).add(timeInterval);
                            accum_demandAvailabilityWeighted.get(layerId).get(demandId)
                                    .add(h_d > 0 ? (1 - blockedTraffic_d / h_d) * timeInterval : timeInterval);
                            accum_avgExcessCarriedTraffic.get(layerId).get(demandId)
                                    .add(excessCarriedTraffic_d * timeInterval);
                            minDemandExcessCarriedTraffic.get(layerId).get(demandId)
                                    .setValue(Math.min(
                                            minDemandExcessCarriedTraffic.get(layerId).get(demandId).doubleValue(),
                                            excessCarriedTraffic_d));
                            maxDemandExcessCarriedTraffic.get(layerId).get(demandId)
                                    .setValue(Math.max(
                                            maxDemandExcessCarriedTraffic.get(layerId).get(demandId).doubleValue(),
                                            excessCarriedTraffic_d));
                            if (excessCarriedTraffic_d > 0)
                                excessDemandCarriedTrafficTime.get(layerId).get(demandId).add(timeInterval);
                            demandTotalTime.get(layerId).get(demandId).add(timeInterval);
                        } else {
                            double totalTime_thisDemand = demandTotalTime.get(layerId).get(demandId).doubleValue();
                            if (totalTime_thisDemand > 0) {
                                worstDemandAvailabilityClassic_thisLayer = Math.min(
                                        worstDemandAvailabilityClassic_thisLayer,
                                        accum_demandAvailabilityClassic.get(layerId).get(demandId).doubleValue()
                                                / totalTime_thisDemand);
                                worstDemandAvailabilityWeighted_thisLayer = Math.min(
                                        worstDemandAvailabilityWeighted_thisLayer,
                                        accum_demandAvailabilityWeighted.get(layerId).get(demandId).doubleValue()
                                                / totalTime_thisDemand);
                            }

                            accum_avgDemandOfferedTraffic.get(layerId).remove(demandId);
                            minDemandOfferedTraffic.get(layerId).remove(demandId);
                            maxDemandOfferedTraffic.get(layerId).remove(demandId);
                            accum_avgDemandCarriedTraffic.get(layerId).remove(demandId);
                            minDemandCarriedTraffic.get(layerId).remove(demandId);
                            maxDemandCarriedTraffic.get(layerId).remove(demandId);
                            accum_avgDemandBlockedTraffic.get(layerId).remove(demandId);
                            minDemandBlockedTraffic.get(layerId).remove(demandId);
                            maxDemandBlockedTraffic.get(layerId).remove(demandId);
                            accum_demandAvailabilityClassic.get(layerId).remove(demandId);
                            accum_demandAvailabilityWeighted.get(layerId).remove(demandId);
                            accum_avgExcessCarriedTraffic.get(layerId).remove(demandId);
                            minDemandExcessCarriedTraffic.get(layerId).remove(demandId);
                            maxDemandExcessCarriedTraffic.get(layerId).remove(demandId);
                            excessDemandCarriedTrafficTime.get(layerId).remove(demandId);
                            demandTotalTime.get(layerId).remove(demandId);
                        }
                    }

                    accum_avgTotalOfferedTraffic.get(layerId).add(totalOfferedTraffic * timeInterval);
                    maxTotalOfferedTraffic.get(layerId).setValue(
                            Math.max(totalOfferedTraffic, maxTotalOfferedTraffic.get(layerId).doubleValue()));
                    minTotalOfferedTraffic.get(layerId).setValue(
                            Math.min(totalOfferedTraffic, minTotalOfferedTraffic.get(layerId).doubleValue()));
                    accum_avgTotalCarriedTraffic.get(layerId).add(totalCarriedTraffic * timeInterval);
                    maxTotalCarriedTraffic.get(layerId).setValue(
                            Math.max(totalCarriedTraffic, maxTotalCarriedTraffic.get(layerId).doubleValue()));
                    minTotalCarriedTraffic.get(layerId).setValue(
                            Math.min(totalCarriedTraffic, minTotalCarriedTraffic.get(layerId).doubleValue()));
                    if (totalBlockedTraffic < precisionFactor)
                        accum_availabilityClassic.get(layerId).add(timeInterval);
                    accum_availabilityWeighted.get(layerId)
                            .add(totalOfferedTraffic > 0
                                    ? Math.min(1, 1 - totalBlockedTraffic / totalOfferedTraffic) * timeInterval
                                    : timeInterval);
                    worstDemandAvailabilityClassic.get(layerId)
                            .setValue(Math.min(worstDemandAvailabilityClassic_thisLayer,
                                    worstDemandAvailabilityClassic.get(layerId).doubleValue()));
                    worstDemandAvailabilityWeighted.get(layerId)
                            .setValue(Math.min(worstDemandAvailabilityWeighted_thisLayer,
                                    worstDemandAvailabilityWeighted.get(layerId).doubleValue()));
                } else {
                    /* Remove layer information */
                    accum_layerTotalTime.remove(layerId);
                    accum_avgNumLinks.remove(layerId);
                    accum_avgNumDemands.remove(layerId);
                    accum_avgTotalOfferedTraffic.remove(layerId);
                    maxTotalOfferedTraffic.remove(layerId);
                    minTotalOfferedTraffic.remove(layerId);
                    accum_avgTotalCarriedTraffic.remove(layerId);
                    maxTotalCarriedTraffic.remove(layerId);
                    minTotalCarriedTraffic.remove(layerId);
                    accum_avgTotalCapacity.remove(layerId);
                    maxTotalCapacity.remove(layerId);
                    minTotalCapacity.remove(layerId);
                    accum_avgCongestion.remove(layerId);
                    maxCongestion.remove(layerId);
                    minCongestion.remove(layerId);
                    accum_availabilityClassic.remove(layerId);
                    accum_availabilityWeighted.remove(layerId);
                    worstDemandAvailabilityClassic.remove(layerId);
                    worstDemandAvailabilityWeighted.remove(layerId);
                    maxNumLinks.remove(layerId);
                    minNumLinks.remove(layerId);
                    maxNumDemands.remove(layerId);
                    minNumDemands.remove(layerId);

                    /* Remove node information in this layer */
                    accum_avgNodeInDegree.remove(layerId);
                    minNodeInDegree.remove(layerId);
                    maxNodeInDegree.remove(layerId);
                    accum_avgNodeOutDegree.remove(layerId);
                    minNodeOutDegree.remove(layerId);
                    maxNodeOutDegree.remove(layerId);
                    accum_avgNodeIngressTraffic.remove(layerId);
                    maxNodeIngressTraffic.remove(layerId);
                    minNodeIngressTraffic.remove(layerId);
                    accum_avgNodeEgressTraffic.remove(layerId);
                    maxNodeEgressTraffic.remove(layerId);
                    minNodeEgressTraffic.remove(layerId);

                    /* Remove link information in this layer */
                    accum_avgLinkLengthInKm.remove(layerId);
                    minLinkLengthInKm.remove(layerId);
                    maxLinkLengthInKm.remove(layerId);
                    accum_avgCapacity.remove(layerId);
                    minCapacity.remove(layerId);
                    maxCapacity.remove(layerId);
                    accum_avgLinkOccupiedCapacity.remove(layerId);
                    minLinkOccupiedCapacity.remove(layerId);
                    maxLinkOccupiedCapacity.remove(layerId);
                    accum_avgUtilization.remove(layerId);
                    minUtilization.remove(layerId);
                    maxUtilization.remove(layerId);
                    accum_avgOversubscribedCapacity.remove(layerId);
                    minOversubscribedCapacity.remove(layerId);
                    maxOversubscribedCapacity.remove(layerId);
                    accum_linkOversubscribedTime.remove(layerId);
                    accum_linkUpTime.remove(layerId);
                    accum_linkTotalTime.remove(layerId);

                    /* Remove demand information in this layer */
                    accum_avgDemandOfferedTraffic.remove(layerId);
                    minDemandOfferedTraffic.remove(layerId);
                    maxDemandOfferedTraffic.remove(layerId);
                    accum_avgDemandCarriedTraffic.remove(layerId);
                    minDemandCarriedTraffic.remove(layerId);
                    maxDemandCarriedTraffic.remove(layerId);
                    accum_avgDemandBlockedTraffic.remove(layerId);
                    minDemandBlockedTraffic.remove(layerId);
                    maxDemandBlockedTraffic.remove(layerId);
                    accum_avgExcessCarriedTraffic.remove(layerId);
                    minDemandExcessCarriedTraffic.remove(layerId);
                    maxDemandExcessCarriedTraffic.remove(layerId);
                    accum_demandAvailabilityClassic.remove(layerId);
                    accum_demandAvailabilityWeighted.remove(layerId);
                    excessDemandCarriedTrafficTime.remove(layerId);
                    demandTotalTime.remove(layerId);
                }
            }
        }

        /* Update previous state (previous = current) */
        previousState_layerIds = new LinkedHashSet<Long>(netState.getNetworkLayerIds());
        previousState_nodeIds = new LinkedHashSet<Long>(netState.getNodeIds());
        previousState_nodeDownIds = new LinkedHashSet<Long>(NetPlan.getIds(netState.getNodesDown()));
        previousState_nodeInDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        previousState_nodeOutDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        previousState_nodeIngressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_nodeEgressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_linkIds = new LinkedHashMap<Long, Set<Long>>();
        previousState_linkDownIds = new LinkedHashMap<Long, Set<Long>>();
        previousState_linkLengthInKm = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_linkCapacity = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_linkOccupiedCapacity = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_demandIds = new LinkedHashMap<Long, Set<Long>>();
        previousState_demandOfferedTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        previousState_demandCarriedTraffic = new LinkedHashMap<Long, Map<Long, Double>>();

        /* Compute node, link metrics */
        for (long layerId : previousState_layerIds) {
            NetworkLayer netStateLayer = netState.getNetworkLayerFromId(layerId);
            previousState_nodeInDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            previousState_nodeOutDegree.put(layerId, new LinkedHashMap<Long, Integer>());
            previousState_nodeIngressTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            previousState_nodeEgressTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            for (long nodeId : previousState_nodeIds) {
                Node netStateNode = netState.getNodeFromId(nodeId);
                previousState_nodeInDegree.get(layerId).put(nodeId,
                        netStateNode.getIncomingLinks(netStateLayer).size());
                previousState_nodeOutDegree.get(layerId).put(nodeId,
                        netStateNode.getOutgoingLinks(netStateLayer).size());
                previousState_nodeIngressTraffic.get(layerId).put(nodeId,
                        netStateNode.getIngressCarriedTraffic(netStateLayer));
                previousState_nodeEgressTraffic.get(layerId).put(nodeId,
                        netStateNode.getEgressCarriedTraffic(netStateLayer));
            }

            previousState_linkIds.put(layerId, new LinkedHashSet<Long>(netState.getLinkIds(netStateLayer)));
            previousState_linkDownIds.put(layerId,
                    new LinkedHashSet<Long>(NetPlan.getIds(netState.getLinksDown(netStateLayer))));
            previousState_linkCapacity.put(layerId, new LinkedHashMap<Long, Double>());
            previousState_linkOccupiedCapacity.put(layerId, new LinkedHashMap<Long, Double>());
            previousState_linkLengthInKm.put(layerId, new LinkedHashMap<Long, Double>());
            for (long linkId : previousState_linkIds.get(layerId)) {
                Link netStateLink = netState.getLinkFromId(linkId);
                previousState_linkCapacity.get(layerId).put(linkId, netStateLink.getCapacity());
                previousState_linkOccupiedCapacity.get(layerId).put(linkId, netStateLink.getOccupiedCapacity());
                previousState_linkLengthInKm.get(layerId).put(linkId, netStateLink.getLengthInKm());
            }
        }

        /* Compute demand metrics */
        for (long layerId : previousState_layerIds) {
            NetworkLayer netStateLayer = netState.getNetworkLayerFromId(layerId);
            previousState_demandIds.put(layerId, new LinkedHashSet<Long>(netState.getDemandIds(netStateLayer)));
            previousState_demandOfferedTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            previousState_demandCarriedTraffic.put(layerId, new LinkedHashMap<Long, Double>());
            for (long demandId : previousState_demandIds.get(layerId)) {
                Demand netStateDemand = netState.getDemandFromId(demandId);
                previousState_demandOfferedTraffic.get(layerId).put(demandId, netStateDemand.getOfferedTraffic());
                previousState_demandCarriedTraffic.get(layerId).put(demandId, netStateDemand.getCarriedTraffic());
            }
        }

        lastEventTime = simTime;
    }

    /**
     * Resets the statistics.
     * 
     * @param simTime Current simulation time
     * @since 0.2.3
     */
    public void reset(double simTime) {
        lastEventTime = simTime;

        /* Network information */
        accum_avgNumLayers = 0;
        accum_avgNumNodes = 0;
        maxNumLayers = 0;
        maxNumNodes = 0;
        minNumLayers = Integer.MAX_VALUE;
        minNumNodes = Integer.MAX_VALUE;

        /* Layer information */
        accum_layerTotalTime = new LinkedHashMap<Long, MutableDouble>();
        accum_avgNumLinks = new LinkedHashMap<Long, MutableDouble>();
        accum_avgNumDemands = new LinkedHashMap<Long, MutableDouble>();
        accum_avgTotalOfferedTraffic = new LinkedHashMap<Long, MutableDouble>();
        maxTotalOfferedTraffic = new LinkedHashMap<Long, MutableDouble>();
        minTotalOfferedTraffic = new LinkedHashMap<Long, MutableDouble>();
        accum_avgTotalCarriedTraffic = new LinkedHashMap<Long, MutableDouble>();
        maxTotalCarriedTraffic = new LinkedHashMap<Long, MutableDouble>();
        minTotalCarriedTraffic = new LinkedHashMap<Long, MutableDouble>();
        accum_avgTotalCapacity = new LinkedHashMap<Long, MutableDouble>();
        maxTotalCapacity = new LinkedHashMap<Long, MutableDouble>();
        minTotalCapacity = new LinkedHashMap<Long, MutableDouble>();
        accum_avgCongestion = new LinkedHashMap<Long, MutableDouble>();
        maxCongestion = new LinkedHashMap<Long, MutableDouble>();
        minCongestion = new LinkedHashMap<Long, MutableDouble>();
        accum_availabilityClassic = new LinkedHashMap<Long, MutableDouble>();
        accum_availabilityWeighted = new LinkedHashMap<Long, MutableDouble>();
        worstDemandAvailabilityClassic = new LinkedHashMap<Long, MutableDouble>();
        worstDemandAvailabilityWeighted = new LinkedHashMap<Long, MutableDouble>();
        maxNumLinks = new LinkedHashMap<Long, Integer>();
        minNumLinks = new LinkedHashMap<Long, Integer>();
        maxNumDemands = new LinkedHashMap<Long, Integer>();
        minNumDemands = new LinkedHashMap<Long, Integer>();

        /* Node information */
        accum_nodeUpTime = new LinkedHashMap<Long, MutableDouble>();
        accum_nodeTotalTime = new LinkedHashMap<Long, MutableDouble>();
        accum_avgNodeInDegree = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxNodeInDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        minNodeInDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        accum_avgNodeOutDegree = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxNodeOutDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        minNodeOutDegree = new LinkedHashMap<Long, Map<Long, Integer>>();
        accum_avgNodeIngressTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxNodeIngressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        minNodeIngressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        accum_avgNodeEgressTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxNodeEgressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();
        minNodeEgressTraffic = new LinkedHashMap<Long, Map<Long, Double>>();

        /* Link information */
        accum_avgLinkLengthInKm = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minLinkLengthInKm = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxLinkLengthInKm = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgLinkOccupiedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minLinkOccupiedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxLinkOccupiedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgUtilization = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minUtilization = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxUtilization = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgOversubscribedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minOversubscribedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxOversubscribedCapacity = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_linkOversubscribedTime = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_linkUpTime = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_linkTotalTime = new LinkedHashMap<Long, Map<Long, MutableDouble>>();

        /* Demand information */
        accum_avgDemandOfferedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minDemandOfferedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxDemandOfferedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgDemandCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minDemandCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxDemandCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgDemandBlockedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minDemandBlockedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxDemandBlockedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_avgExcessCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        minDemandExcessCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        maxDemandExcessCarriedTraffic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_demandAvailabilityClassic = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        accum_demandAvailabilityWeighted = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        excessDemandCarriedTrafficTime = new LinkedHashMap<Long, Map<Long, MutableDouble>>();
        demandTotalTime = new LinkedHashMap<Long, Map<Long, MutableDouble>>();

        computeNextState(simTime);
        transitoryTime = simTime;
    };

    /**
     * Returns a HTML {@code String} with statistics.
     * 
     * @param simTime Current simulation time
     * @return Statistics in HTML format
     * @since 0.2.3
     */
    public String getResults(double simTime) {
        if (lastEventTime == 0)
            return "<p>No event was processed</p>";

        double totalSimulationTime = simTime - transitoryTime;
        if (totalSimulationTime == 0)
            return "<p>Simulation time equal to zero. No results</p>";

        //      computeNextState(simTime+0.0000000000001);
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            XMLOutputFactory2 output = (XMLOutputFactory2) XMLOutputFactory.newFactory();
            XMLStreamWriter2 writer = (XMLStreamWriter2) output.createXMLStreamWriter(os);

            writer.writeStartDocument("UTF-8", "1.0");

            /* Write network information */
            writer.writeStartElement("network");
            writer.writeAttribute("avgNumLayers",
                    String.format("%.3f", totalSimulationTime > 0 ? accum_avgNumLayers / totalSimulationTime : 0));

            int minNumLayers_thisNetwork = minNumLayers;
            if (minNumLayers_thisNetwork == Integer.MAX_VALUE)
                minNumLayers_thisNetwork = 0;
            writer.writeAttribute("minNumLayers", Integer.toString(minNumLayers_thisNetwork));
            writer.writeAttribute("maxNumLayers", Integer.toString(maxNumLayers));
            writer.writeAttribute("avgNumNodes",
                    String.format("%.3f", totalSimulationTime > 0 ? accum_avgNumNodes / totalSimulationTime : 0));

            int minNumNodes_thisNetwork = minNumNodes;
            if (minNumNodes_thisNetwork == Integer.MAX_VALUE)
                minNumNodes_thisNetwork = 0;
            writer.writeAttribute("minNumNodes", Integer.toString(minNumNodes_thisNetwork));
            writer.writeAttribute("maxNumNodes", Integer.toString(maxNumNodes));

            /* Write node information */
            Collection<Long> nodeIds = netState.getNodeIds();
            for (long nodeId : nodeIds) {
                checkAndCreateNode(nodeId);

                double upTime_thisNode = accum_nodeUpTime.get(nodeId).doubleValue();
                double totalTime_thisNode = accum_nodeTotalTime.get(nodeId).doubleValue();
                double upTimePercentage_thisNode = totalTime_thisNode > 0
                        ? 100 * upTime_thisNode / totalTime_thisNode
                        : 0;

                writer.writeStartElement("node");
                writer.writeAttribute("id", Long.toString(nodeId));
                writer.writeAttribute("name", netState.getNodeFromId(nodeId).getName());
                writer.writeAttribute("upTime", StringUtils.secondsToYearsDaysHoursMinutesSeconds(upTime_thisNode));
                writer.writeAttribute("upTimePercentage", String.format("%.3f", upTimePercentage_thisNode));
                writer.writeAttribute("totalTime",
                        StringUtils.secondsToYearsDaysHoursMinutesSeconds(totalTime_thisNode));
                writer.writeEndElement();
            }

            /* Write layer information */
            for (long layerId : netState.getNetworkLayerIds()) {
                checkAndCreateLayer(layerId);

                double totalTime_thisLayer = accum_layerTotalTime.get(layerId).doubleValue();

                NetworkLayer netStateLayer = netState.getNetworkLayerFromId(layerId);

                String trafficUnitsName = netState.getDemandTrafficUnitsName(netStateLayer);
                if (trafficUnitsName.isEmpty())
                    trafficUnitsName = "none";
                String capacityUnitsName = netState.getLinkCapacityUnitsName(netStateLayer);
                if (capacityUnitsName.isEmpty())
                    capacityUnitsName = "none";

                writer.writeStartElement("layer");
                writer.writeAttribute("id", Long.toString(layerId));
                writer.writeAttribute("name", netStateLayer.getName());
                writer.writeAttribute("avgNumLinks",
                        String.format("%.3f",
                                totalTime_thisLayer > 0
                                        ? accum_avgNumLinks.get(layerId).doubleValue() / totalTime_thisLayer
                                        : 0));

                int minNumLinks_thisLayer = minNumLinks.get(layerId);
                if (minNumLinks_thisLayer == Integer.MAX_VALUE)
                    minNumLinks_thisLayer = 0;
                writer.writeAttribute("minNumLinks", Integer.toString(minNumLinks_thisLayer));
                writer.writeAttribute("maxNumLinks", Integer.toString(maxNumLinks.get(layerId)));
                writer.writeAttribute("avgNumDemands",
                        String.format("%.3f",
                                totalTime_thisLayer > 0
                                        ? accum_avgNumDemands.get(layerId).doubleValue() / totalTime_thisLayer
                                        : 0));

                int minNumDemands_thisLayer = minNumDemands.get(layerId);
                if (minNumDemands_thisLayer == Integer.MAX_VALUE)
                    minNumDemands_thisLayer = 0;
                writer.writeAttribute("minNumDemands", Integer.toString(minNumDemands_thisLayer));
                writer.writeAttribute("maxNumDemands", Integer.toString(maxNumDemands.get(layerId)));
                writer.writeAttribute("totalTime",
                        StringUtils.secondsToYearsDaysHoursMinutesSeconds(totalTime_thisLayer));
                writer.writeAttribute("trafficUnitsName", trafficUnitsName);
                writer.writeAttribute("avgOfferedTraffic", String.format("%.3f",
                        totalTime_thisLayer > 0
                                ? accum_avgTotalOfferedTraffic.get(layerId).doubleValue() / totalTime_thisLayer
                                : 0));

                double minTotalOfferedTraffic_thisLayer = minTotalOfferedTraffic.get(layerId).doubleValue();
                if (minTotalOfferedTraffic_thisLayer == Double.MAX_VALUE)
                    minTotalOfferedTraffic_thisLayer = 0;
                writer.writeAttribute("minOfferedTraffic", String.format("%.3f", minTotalOfferedTraffic_thisLayer));
                writer.writeAttribute("maxOfferedTraffic",
                        String.format("%.3f", maxTotalOfferedTraffic.get(layerId).doubleValue()));
                writer.writeAttribute("avgCarriedTraffic", String.format("%.3f",
                        totalTime_thisLayer > 0
                                ? accum_avgTotalCarriedTraffic.get(layerId).doubleValue() / totalTime_thisLayer
                                : 0));

                double minTotalCarriedTraffic_thisLayer = minTotalCarriedTraffic.get(layerId).doubleValue();
                if (minTotalCarriedTraffic_thisLayer == Double.MAX_VALUE)
                    minTotalCarriedTraffic_thisLayer = 0;
                writer.writeAttribute("minCarriedTraffic", String.format("%.3f", minTotalCarriedTraffic_thisLayer));
                writer.writeAttribute("maxCarriedTraffic",
                        String.format("%.3f", maxTotalCarriedTraffic.get(layerId).doubleValue()));
                writer.writeAttribute("capacityUnitsName", capacityUnitsName);
                writer.writeAttribute("avgTotalCapacity",
                        String.format("%.3f",
                                totalTime_thisLayer > 0
                                        ? accum_avgTotalCapacity.get(layerId).doubleValue() / totalTime_thisLayer
                                        : 0));

                double minTotalCapacity_thisLayer = minTotalCapacity.get(layerId).doubleValue();
                if (minTotalCapacity_thisLayer == Double.MAX_VALUE)
                    minTotalCapacity_thisLayer = 0;
                writer.writeAttribute("minTotalCapacity", String.format("%.3f", minTotalCapacity_thisLayer));
                writer.writeAttribute("maxTotalCapacity",
                        String.format("%.3f", maxTotalCapacity.get(layerId).doubleValue()));
                writer.writeAttribute("avgCongestion",
                        String.format("%.3f",
                                totalTime_thisLayer > 0
                                        ? accum_avgCongestion.get(layerId).doubleValue() / totalTime_thisLayer
                                        : 0));

                double minCongestion_thisLayer = minCongestion.get(layerId).doubleValue();
                if (minCongestion_thisLayer == Double.MAX_VALUE)
                    minCongestion_thisLayer = 0;
                writer.writeAttribute("minCongestion", String.format("%.3f", minCongestion_thisLayer));
                writer.writeAttribute("maxCongestion",
                        String.format("%.3f", maxCongestion.get(layerId).doubleValue()));
                writer.writeAttribute("availabilityClassic",
                        String.format("%.6f",
                                totalTime_thisLayer > 0
                                        ? accum_availabilityClassic.get(layerId).doubleValue() / totalTime_thisLayer
                                        : 0));
                writer.writeAttribute("availabilityWeighted", String.format("%.6f",
                        totalTime_thisLayer > 0
                                ? accum_availabilityWeighted.get(layerId).doubleValue() / totalTime_thisLayer
                                : 0));

                double worstDemandAvailabilityClassic_thisLayer = worstDemandAvailabilityClassic.get(layerId)
                        .doubleValue();
                double worstDemandAvailabilityWeighted_thisLayer = worstDemandAvailabilityWeighted.get(layerId)
                        .doubleValue();

                /* Write demand information */
                Collection<Long> demandIds_thisLayer = netState.getDemandIds(netStateLayer);
                for (long demandId : netState.getDemandIds(netStateLayer)) {
                    checkAndCreateDemand(layerId, demandId);

                    double totalTime_thisDemand = demandTotalTime.get(layerId).get(demandId).doubleValue();
                    worstDemandAvailabilityClassic_thisLayer = Math.min(worstDemandAvailabilityClassic_thisLayer,
                            totalTime_thisDemand > 0
                                    ? accum_demandAvailabilityClassic.get(layerId).get(demandId).doubleValue()
                                            / totalTime_thisDemand
                                    : 0);
                    worstDemandAvailabilityWeighted_thisLayer = Math.min(worstDemandAvailabilityWeighted_thisLayer,
                            totalTime_thisDemand > 0
                                    ? accum_demandAvailabilityWeighted.get(layerId).get(demandId).doubleValue()
                                            / totalTime_thisDemand
                                    : 0);
                }

                writer.writeAttribute("worstDemandAvailabilityClassic",
                        String.format("%.6f", worstDemandAvailabilityClassic_thisLayer));
                writer.writeAttribute("worstDemandAvailabilityWeighted",
                        String.format("%.6f", worstDemandAvailabilityWeighted_thisLayer));

                /* Write node information */
                for (long nodeId : nodeIds) {
                    checkAndCreateNode(layerId, nodeId);

                    double totalTime_thisNode_thisLayer = Math.min(accum_nodeTotalTime.get(nodeId).doubleValue(),
                            totalTime_thisLayer);
                    writer.writeStartElement("node");
                    writer.writeAttribute("id", Long.toString(nodeId));
                    writer.writeAttribute("name", netState.getNodeFromId(nodeId).getName());
                    writer.writeAttribute("avgInDegree",
                            String.format("%.3f",
                                    totalTime_thisNode_thisLayer > 0
                                            ? accum_avgNodeInDegree.get(layerId).get(nodeId).doubleValue()
                                                    / totalTime_thisNode_thisLayer
                                            : 0));

                    int minNodeInDegree_thisNode_thisLayer = minNodeInDegree.get(layerId).get(nodeId);
                    if (minNodeInDegree_thisNode_thisLayer == Integer.MAX_VALUE)
                        minNodeInDegree_thisNode_thisLayer = 0;
                    writer.writeAttribute("minInDegree", Integer.toString(minNodeInDegree_thisNode_thisLayer));
                    writer.writeAttribute("maxInDegree",
                            Integer.toString(maxNodeInDegree.get(layerId).get(nodeId)));
                    writer.writeAttribute("avgOutDegree",
                            String.format("%.3f",
                                    totalTime_thisNode_thisLayer > 0
                                            ? accum_avgNodeOutDegree.get(layerId).get(nodeId).doubleValue()
                                                    / totalTime_thisNode_thisLayer
                                            : 0));

                    int minNodeOutDegree_thisNode_thisLayer = minNodeOutDegree.get(layerId).get(nodeId);
                    if (minNodeOutDegree_thisNode_thisLayer == Integer.MAX_VALUE)
                        minNodeOutDegree_thisNode_thisLayer = 0;
                    writer.writeAttribute("minOutDegree", Integer.toString(minNodeOutDegree_thisNode_thisLayer));
                    writer.writeAttribute("maxOutDegree",
                            Integer.toString(maxNodeOutDegree.get(layerId).get(nodeId)));
                    writer.writeAttribute("avgIngressTraffic",
                            String.format("%.3f",
                                    totalTime_thisNode_thisLayer > 0
                                            ? accum_avgNodeIngressTraffic.get(layerId).get(nodeId).doubleValue()
                                                    / totalTime_thisNode_thisLayer
                                            : 0));

                    double minNodeIngressTraffic_thisNode_thisLayer = minNodeIngressTraffic.get(layerId)
                            .get(nodeId);
                    if (minNodeIngressTraffic_thisNode_thisLayer == Double.MAX_VALUE)
                        minNodeIngressTraffic_thisNode_thisLayer = 0;
                    writer.writeAttribute("minIngressTraffic",
                            String.format("%.3f", minNodeIngressTraffic.get(layerId).get(nodeId)));
                    writer.writeAttribute("maxIngressTraffic",
                            String.format("%.3f", maxNodeIngressTraffic.get(layerId).get(nodeId)));
                    writer.writeAttribute("avgEgressTraffic",
                            String.format("%.3f",
                                    totalTime_thisNode_thisLayer > 0
                                            ? accum_avgNodeEgressTraffic.get(layerId).get(nodeId).doubleValue()
                                                    / totalTime_thisNode_thisLayer
                                            : 0));

                    double minNodeEgressTraffic_thisNode_thisLayer = minNodeEgressTraffic.get(layerId).get(nodeId);
                    if (minNodeEgressTraffic_thisNode_thisLayer == Double.MAX_VALUE)
                        minNodeEgressTraffic_thisNode_thisLayer = 0;
                    writer.writeAttribute("minEgressTraffic",
                            String.format("%.3f", minNodeEgressTraffic_thisNode_thisLayer));
                    writer.writeAttribute("maxEgressTraffic",
                            String.format("%.3f", maxNodeEgressTraffic.get(layerId).get(nodeId)));

                    writer.writeEndElement();
                }

                /* Write link information */
                Collection<Long> linkIds_thisLayer = netState.getLinkIds(netState.getNetworkLayerFromId(layerId));
                for (long linkId : linkIds_thisLayer) {
                    checkAndCreateLink(layerId, linkId);
                    Link netStateLink = netState.getLinkFromId(linkId);
                    long originNodeId_thisLink = netStateLink.getOriginNode().getId();
                    long destinationNodeId_thisLink = netStateLink.getDestinationNode().getId();
                    String originNodeName = netStateLink.getOriginNode().getName();
                    String destinationNodeName = netStateLink.getDestinationNode().getName();
                    double upTime_thisLink = accum_linkUpTime.get(layerId).get(linkId).doubleValue();
                    double totalTime_thisLink = accum_linkTotalTime.get(layerId).get(linkId).doubleValue();
                    double upTimePercentage_thisLink = totalTime_thisLink > 0
                            ? 100 * upTime_thisLink / totalTime_thisLink
                            : 0;
                    double oversubscribedTime_thisLink = accum_linkOversubscribedTime.get(layerId).get(linkId)
                            .doubleValue();
                    double oversubscribedTimePercentage_thisLink = totalTime_thisLink > 0
                            ? 100 * oversubscribedTime_thisLink / totalTime_thisLink
                            : 0;

                    writer.writeStartElement("link");
                    writer.writeAttribute("id", Long.toString(linkId));
                    writer.writeAttribute("originNode",
                            originNodeName.isEmpty() ? Long.toString(originNodeId_thisLink)
                                    : String.format("%d (%s)", originNodeId_thisLink, originNodeName));
                    writer.writeAttribute("destinationNode",
                            destinationNodeName.isEmpty() ? Long.toString(destinationNodeId_thisLink)
                                    : String.format("%d (%s)", destinationNodeId_thisLink, destinationNodeName));
                    writer.writeAttribute("avgLengthInKm",
                            String.format("%.3f",
                                    totalTime_thisLink > 0
                                            ? accum_avgLinkLengthInKm.get(layerId).get(linkId).doubleValue()
                                                    / totalTime_thisLink
                                            : 0));

                    double minLinkLengthInKm_thisLink = minLinkLengthInKm.get(layerId).get(linkId).doubleValue();
                    if (minLinkLengthInKm_thisLink == Double.MAX_VALUE)
                        minLinkLengthInKm_thisLink = 0;
                    writer.writeAttribute("minLengthInKm", String.format("%.3f", minLinkLengthInKm_thisLink));
                    writer.writeAttribute("maxLengthInKm",
                            String.format("%.3f", maxLinkLengthInKm.get(layerId).get(linkId).doubleValue()));
                    writer.writeAttribute("avgCapacity", String.format("%.3f",
                            totalTime_thisLink > 0
                                    ? accum_avgCapacity.get(layerId).get(linkId).doubleValue() / totalTime_thisLink
                                    : 0));

                    double minCapacity_thisLayer = minCapacity.get(layerId).get(linkId).doubleValue();
                    if (minCapacity_thisLayer == Double.MAX_VALUE)
                        minCapacity_thisLayer = 0;
                    writer.writeAttribute("minCapacity", String.format("%.3f", minCapacity_thisLayer));
                    writer.writeAttribute("maxCapacity",
                            String.format("%.3f", maxCapacity.get(layerId).get(linkId).doubleValue()));
                    writer.writeAttribute("avgOccupiedCapacity",
                            String.format("%.3f",
                                    totalTime_thisLink > 0
                                            ? accum_avgLinkOccupiedCapacity.get(layerId).get(linkId).doubleValue()
                                                    / totalTime_thisLink
                                            : 0));

                    double minCarriedTraffic_thisLink = minLinkOccupiedCapacity.get(layerId).get(linkId)
                            .doubleValue();
                    if (minCarriedTraffic_thisLink == Double.MAX_VALUE)
                        minCarriedTraffic_thisLink = 0;
                    writer.writeAttribute("minOccupiedCapacity", String.format("%.3f", minCarriedTraffic_thisLink));
                    writer.writeAttribute("maxOccupiedCapacity",
                            String.format("%.3f", maxLinkOccupiedCapacity.get(layerId).get(linkId).doubleValue()));

                    writer.writeAttribute("avgUtilization",
                            String.format("%.3f",
                                    totalTime_thisLink > 0
                                            ? accum_avgUtilization.get(layerId).get(linkId).doubleValue()
                                                    / totalTime_thisLink
                                            : 0));

                    double minUtilization_thisLink = minUtilization.get(layerId).get(linkId).doubleValue();
                    if (minUtilization_thisLink == Double.MAX_VALUE)
                        minUtilization_thisLink = 0;
                    writer.writeAttribute("minUtilization", String.format("%.3f", minUtilization_thisLink));
                    writer.writeAttribute("maxUtilization",
                            String.format("%.3f", maxUtilization.get(layerId).get(linkId).doubleValue()));

                    writer.writeAttribute("avgOversubscribedCapacity",
                            String.format("%.3f",
                                    totalTime_thisLink > 0
                                            ? accum_avgOversubscribedCapacity.get(layerId).get(linkId).doubleValue()
                                                    / totalTime_thisLink
                                            : 0));

                    double minOversubscribedCapacity_thisLink = minOversubscribedCapacity.get(layerId).get(linkId)
                            .doubleValue();
                    if (minOversubscribedCapacity_thisLink == Double.MAX_VALUE)
                        minOversubscribedCapacity_thisLink = 0;
                    writer.writeAttribute("minOversubscribedCapacity",
                            String.format("%.3f", minOversubscribedCapacity_thisLink));
                    writer.writeAttribute("maxOversubscribedCapacity", String.format("%.3f",
                            maxOversubscribedCapacity.get(layerId).get(linkId).doubleValue()));
                    writer.writeAttribute("oversubscribedTime",
                            StringUtils.secondsToYearsDaysHoursMinutesSeconds(oversubscribedTime_thisLink));
                    writer.writeAttribute("oversubscribedTimePercentage",
                            String.format("%.3f", oversubscribedTimePercentage_thisLink));
                    writer.writeAttribute("upTime",
                            StringUtils.secondsToYearsDaysHoursMinutesSeconds(upTime_thisLink));
                    writer.writeAttribute("upTimePercentage", String.format("%.3f", upTimePercentage_thisLink));
                    writer.writeAttribute("totalTime",
                            StringUtils.secondsToYearsDaysHoursMinutesSeconds(totalTime_thisLink));
                    writer.writeEndElement();
                }

                /* Write demand information */
                for (long demandId : demandIds_thisLayer) {
                    Demand netStateDemand = netState.getDemandFromId(demandId);
                    long ingressNodeId_thisDemand = netStateDemand.getIngressNode().getId();
                    long egressNodeId_thisDemand = netStateDemand.getEgressNode().getId();
                    String ingressNodeName = netStateDemand.getIngressNode().getName();
                    String egressNodeName = netStateDemand.getEgressNode().getName();
                    double totalTime_thisDemand = demandTotalTime.get(layerId).get(demandId).doubleValue();
                    double excessCarriedTrafficTime_thisDemand = excessDemandCarriedTrafficTime.get(layerId)
                            .get(demandId).doubleValue();
                    double excessCarriedTrafficTimePercentage_thisDemand = totalTime_thisDemand > 0
                            ? 100 * excessCarriedTrafficTime_thisDemand / totalTime_thisDemand
                            : 0;

                    writer.writeStartElement("demand");
                    writer.writeAttribute("id", Long.toString(demandId));
                    writer.writeAttribute("ingressNode",
                            ingressNodeName.isEmpty() ? Long.toString(ingressNodeId_thisDemand)
                                    : String.format("%d (%s)", ingressNodeId_thisDemand, ingressNodeName));
                    writer.writeAttribute("egressNode",
                            egressNodeName.isEmpty() ? Long.toString(egressNodeId_thisDemand)
                                    : String.format("%d (%s)", egressNodeId_thisDemand, egressNodeName));
                    writer.writeAttribute("avgOfferedTraffic",
                            String.format("%.3f",
                                    totalTime_thisDemand > 0
                                            ? accum_avgDemandOfferedTraffic.get(layerId).get(demandId).doubleValue()
                                                    / totalTime_thisDemand
                                            : 0));

                    double minOfferedTraffic_thisDemand = minDemandOfferedTraffic.get(layerId).get(demandId)
                            .doubleValue();
                    if (minOfferedTraffic_thisDemand == Double.MAX_VALUE)
                        minOfferedTraffic_thisDemand = 0;
                    writer.writeAttribute("minOfferedTraffic", String.format("%.3f", minOfferedTraffic_thisDemand));
                    writer.writeAttribute("maxOfferedTraffic", String.format("%.3f",
                            maxDemandOfferedTraffic.get(layerId).get(demandId).doubleValue()));
                    writer.writeAttribute("avgCarriedTraffic",
                            String.format("%.3f",
                                    totalTime_thisDemand > 0
                                            ? accum_avgDemandCarriedTraffic.get(layerId).get(demandId).doubleValue()
                                                    / totalTime_thisDemand
                                            : 0));

                    double minCarriedTraffic_thisDemand = minDemandCarriedTraffic.get(layerId).get(demandId)
                            .doubleValue();
                    if (minCarriedTraffic_thisDemand == Double.MAX_VALUE)
                        minCarriedTraffic_thisDemand = 0;
                    writer.writeAttribute("minCarriedTraffic", String.format("%.3f", minCarriedTraffic_thisDemand));
                    writer.writeAttribute("maxCarriedTraffic", String.format("%.3f",
                            maxDemandCarriedTraffic.get(layerId).get(demandId).doubleValue()));
                    writer.writeAttribute("avgBlockedTraffic",
                            String.format("%.3f",
                                    totalTime_thisDemand > 0
                                            ? accum_avgDemandBlockedTraffic.get(layerId).get(demandId).doubleValue()
                                                    / totalTime_thisDemand
                                            : 0));

                    double minBlockedTraffic_thisDemand = minDemandBlockedTraffic.get(layerId).get(demandId)
                            .doubleValue();
                    if (minBlockedTraffic_thisDemand == Double.MAX_VALUE)
                        minBlockedTraffic_thisDemand = 0;
                    writer.writeAttribute("minBlockedTraffic", String.format("%.3f", minBlockedTraffic_thisDemand));
                    writer.writeAttribute("maxBlockedTraffic", String.format("%.3f",
                            maxDemandBlockedTraffic.get(layerId).get(demandId).doubleValue()));
                    writer.writeAttribute("availabilityClassic", String.format("%.6f",
                            totalTime_thisDemand > 0
                                    ? accum_demandAvailabilityClassic.get(layerId).get(demandId).doubleValue()
                                            / totalTime_thisDemand
                                    : 0));
                    writer.writeAttribute("availabilityWeighted", String.format("%.6f",
                            totalTime_thisDemand > 0
                                    ? accum_demandAvailabilityWeighted.get(layerId).get(demandId).doubleValue()
                                            / totalTime_thisDemand
                                    : 0));
                    writer.writeAttribute("avgExcessCarriedTraffic",
                            String.format("%.3f",
                                    totalTime_thisDemand > 0
                                            ? accum_avgExcessCarriedTraffic.get(layerId).get(demandId).doubleValue()
                                                    / totalTime_thisDemand
                                            : 0));

                    double minExcessCarriedTraffic_thisDemand = minDemandExcessCarriedTraffic.get(layerId)
                            .get(demandId).doubleValue();
                    if (minExcessCarriedTraffic_thisDemand == Double.MAX_VALUE)
                        minExcessCarriedTraffic_thisDemand = 0;
                    writer.writeAttribute("minExcessCarriedTraffic",
                            String.format("%.3f", minExcessCarriedTraffic_thisDemand));
                    writer.writeAttribute("maxExcessCarriedTraffic", String.format("%.3f",
                            maxDemandExcessCarriedTraffic.get(layerId).get(demandId).doubleValue()));
                    writer.writeAttribute("excessCarriedTrafficTime",
                            StringUtils.secondsToYearsDaysHoursMinutesSeconds(excessCarriedTrafficTime_thisDemand));
                    writer.writeAttribute("excessCarriedTrafficTimePercentage",
                            String.format("%.3f", excessCarriedTrafficTimePercentage_thisDemand));
                    writer.writeAttribute("totalTime",
                            StringUtils.secondsToYearsDaysHoursMinutesSeconds(totalTime_thisDemand));
                    writer.writeEndElement();
                }

                writer.writeEndElement();
            }

            writer.writeEndElement();
            writer.writeEndDocument();
            writer.flush();
            writer.close();

            String xml = os.toString(StandardCharsets.UTF_8.name());
            return HTMLUtils.getHTMLFromXML(xml,
                    SimStats.class.getResource("/com/net2plan/internal/sim/SimStats.xsl").toURI().toURL());
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}