All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.unibo.alchemist.protelis.AlchemistNetworkManager.kt Maven / Gradle / Ivy

Go to download

Implementation of the Alchemist's meta-meta model supporting execution of Protelis programs

There is a newer version: 35.0.0
Show newest version
/*
 * Copyright (C) 2010-2022, Danilo Pianini and contributors
 * listed, for each module, in the respective subproject's build.gradle.kts file.
 *
 * This file is part of Alchemist, and is distributed under the terms of the
 * GNU General Public License, with a linking exception,
 * as described in the file LICENSE in the Alchemist distribution's top directory.
 */
package it.unibo.alchemist.protelis

import com.google.common.collect.ImmutableMap
import it.unibo.alchemist.model.Environment
import it.unibo.alchemist.model.Node.Companion.asProperty
import it.unibo.alchemist.model.Node.Companion.asPropertyOrNull
import it.unibo.alchemist.model.Reaction
import it.unibo.alchemist.protelis.actions.RunProtelisProgram
import it.unibo.alchemist.protelis.properties.ProtelisDevice
import org.apache.commons.math3.distribution.RealDistribution
import org.protelis.lang.datatype.DeviceUID
import org.protelis.vm.CodePath
import org.protelis.vm.NetworkManager
import java.io.Serializable
import java.util.Collections
import java.util.Objects

/**
 * Emulates a [NetworkManager]. This particular network manager does not
 * send messages instantly. Instead, it records the last message to send, and
 * only when [simulateMessageArrival] is called the transfer is
 * actually performed.
 */
class AlchemistNetworkManager @JvmOverloads constructor(
    /**
     * This reaction stores the time at which the neighbor state is read.
     */
    val event: Reaction,
    /**
     * The [ProtelisDevice] required to run Protelis.
     */
    val device: ProtelisDevice<*> = event.node.asProperty(),
    /**
     * The action this network manager is associated with.
     */
    val program: RunProtelisProgram<*>,
    /**
     * the message retention time, or NaN if all the messages get
     * discarded as soon as a computation cycle is concluded.
     */
    val retentionTime: Double = Double.NaN,
    /**
     * the distribution connecting the distance to the packet loss.
     */
    val distanceLossDistribution: RealDistribution? = null,
) : NetworkManager, Serializable {

    private val environment: Environment = Objects.requireNonNull(program.environment)
    private val messages: MutableMap = LinkedHashMap()
    private var toBeSent: Map = emptyMap()
    private var neighborState = ImmutableMap.of>()
    private var timeAtLastValidityCheck = Double.NEGATIVE_INFINITY

    init {
        require(retentionTime.isNaN() || retentionTime >= 0) { "The retention time can't be negative." }
    }

    override fun getNeighborState(): ImmutableMap> {
        val currentTime = event.tau.toDouble()
        /*
         * If no time has passed, the last result is still valid, otherwise needs to be recomputed
         */
        if (timeAtLastValidityCheck != currentTime) {
            neighborState = if (messages.isEmpty()) {
                ImmutableMap.of()
            } else {
                /*
                 * If retentionTime is a number, use it. Otherwise clean messages of lost neighbors
                 */
                val stateBuilder = ImmutableMap.builder>()
                val messagesIterator = messages.values.iterator()
                val retainsNeighbors = retentionTime.isNaN()
                val neighbors: Set = emptySet().takeUnless { retainsNeighbors }
                    ?: environment.getNeighborhood(device.node)
                        .neighbors
                        .mapNotNull { it.asPropertyOrNull>() }
                        .toSet()
                while (messagesIterator.hasNext()) {
                    val message = messagesIterator.next()
                    if (retainsNeighbors && message.source in neighbors || currentTime - message.time < retentionTime) {
                        stateBuilder.put(message.source, message.payload)
                    } else {
                        // Removes from this.messages as well
                        messagesIterator.remove()
                    }
                }
                stateBuilder.build()
            }
            timeAtLastValidityCheck = currentTime
        }
        return neighborState
    }

    private fun receiveMessage(msg: MessageInfo) {
        messages[msg.source] = msg
    }

    override fun shareState(toSend: Map) {
        toBeSent = Collections.unmodifiableMap(toSend)
    }

    /**
     * Simulates the arrival of the message to other nodes.
     *
     * @param currentTime
     * the current simulation time (used to understand when a message
     * should get dropped).
     */
    fun simulateMessageArrival(currentTime: Double) {
        if (toBeSent.isNotEmpty()) {
            val msg = MessageInfo(currentTime, device, toBeSent)
            environment.getNeighborhood(device.node)
                .mapNotNull { it.asPropertyOrNull>() }
                .forEach { neighborDevice ->
                    val destination = neighborDevice.getNetworkManager(program)
                    var packetArrives = true
                    if (distanceLossDistribution != null) {
                        val distance = environment.getDistanceBetweenNodes(device.node, neighborDevice.node)
                        val random = program.randomGenerator.nextDouble()
                        packetArrives = random > distanceLossDistribution.cumulativeProbability(distance)
                    }
                    if (packetArrives) {
                        /*
                         * The node is running the program, and the loss model actually makes the packet arrive.
                         * Otherwise, the message is discarded
                         */
                        destination.receiveMessage(msg)
                    }
                }
            toBeSent = emptyMap()
        }
    }

    private data class MessageInfo(
        val time: Double,
        val source: DeviceUID,
        val payload: Map,
    ) : Serializable {
        companion object {
            private const val serialVersionUID = 2L
        }
    }

    companion object {
        private const val serialVersionUID = 2L
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy