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

it.unibo.alchemist.boundary.launchers.DefaultLauncher.kt Maven / Gradle / Ivy

There is a newer version: 37.0.1
Show newest version
/*
 * Copyright (C) 2010-2023, 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.boundary.launchers

import com.google.common.collect.Lists
import it.unibo.alchemist.boundary.Launcher
import it.unibo.alchemist.boundary.Loader
import it.unibo.alchemist.boundary.Variable
import it.unibo.alchemist.core.Simulation
import it.unibo.alchemist.util.BugReporting
import org.slf4j.LoggerFactory
import java.io.Serializable
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger

/**
 * The default launcher for simulations.
 * If [batch] variables are specified, the simulation is run once for each combination of their values.
 * If [autoStart] is true (default), the simulation is started automatically.
 * If [showProgress] is true (default), a message is printed to the console every time a simulation completes.
 * If [parallelism] is greater than 1, the simulations are run in parallel;
 * defaults to the number of logical cores detected by the JVM.
 */
open class DefaultLauncher
    @JvmOverloads
    constructor(
        val batch: List = emptyList(),
        val autoStart: Boolean = true,
        val showProgress: Boolean = true,
        val parallelism: Int = Runtime.getRuntime().availableProcessors(),
    ) : Launcher {
        @JvmOverloads
        constructor(
            autoStart: Boolean,
            showProgress: Boolean = true,
            parallelism: Int = Runtime.getRuntime().availableProcessors(),
        ) : this(emptyList(), autoStart, showProgress, parallelism)

        /**
         * Launches a simulation using the provided [loader].
         */
        @Synchronized
        override fun launch(loader: Loader) {
            fun Simulation<*, *>.configured() =
                apply {
                    if (autoStart) {
                        play()
                    }
                }
            val instances = loader.variables.cartesianProductOf(batch)
            if (instances.isEmpty()) {
                BugReporting.reportBug(
                    "No simulation instances were created",
                    mapOf(
                        "requested batch" to batch,
                        "variables" to loader.variables,
                    ),
                )
            }
            val launchId = launchId.getAndIncrement()
            when {
                instances.size == 1 ->
                    loader.getWith(instances.first()).configured().run()
                parallelism == 1 ->
                    instances.forEach {
                        loader.getWith(it).configured().run()
                    }
                else -> {
                    val workerId = AtomicInteger(0)
                    val executor =
                        Executors.newFixedThreadPool(parallelism) {
                            Thread(it, "Alchemist Pool $launchId worker ${workerId.getAndIncrement()}")
                        }
                    val errorQueue = ConcurrentLinkedDeque()
                    instances.forEachIndexed { index, instance ->
                        executor.submit {
                            runCatching { loader.getWith(instance).configured() }
                                .mapCatching { simulation ->
                                    simulation.run()
                                    simulation.error.ifPresent { throw it }
                                    logger.info("Simulation with {} completed successfully", instance)
                                }.onFailure {
                                    logger.error("Failure for simulation with $instance", it)
                                    errorQueue.add(it)
                                    executor.shutdownNow()
                                }.onSuccess {
                                    if (showProgress) {
                                        logger.info("Simulation {} of {} completed", index + 1, instances.size)
                                    }
                                }
                        }
                    }
                    executor.shutdown()
                    executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)
                    if (errorQueue.isNotEmpty()) {
                        throw errorQueue.reduce { previous, other ->
                            previous.addSuppressed(other)
                            previous
                        }
                    }
                }
            }
        }

        protected companion object {
            /**
             * If no specific number of parallel threads to use is specified, this value is used.
             * Defaults to the number of logical cores detected by the JVM.
             */
            @JvmStatic
            protected val defaultParallelism = Runtime.getRuntime().availableProcessors()

            private val launchId = AtomicInteger(0)

            private val logger = LoggerFactory.getLogger(this::class.java)

            @JvmStatic
            protected fun Map>.cartesianProductOf(
                variables: Collection,
            ): List> {
                require(keys.containsAll(variables)) {
                    "Variables ${variables - keys} are not defined. Valid values are: $this"
                }
                val variableValues =
                    variables
                        .map { variableName ->
                            this[variableName]
                                ?.map { variableName to it }
                                ?: BugReporting.reportBug(
                                    "Variable was supposed to be available, but it is not",
                                    mapOf(
                                        "variableName" to variableName,
                                        "requested variables" to variables,
                                        "available variables" to this,
                                    ),
                                )
                        }.toList()
                return Lists
                    .cartesianProduct(variableValues)
                    .map { it.toMap() }
                    .takeUnless { it.isEmpty() }
                    ?: listOf(emptyMap())
            }
        }
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy