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

it.unibo.alchemist.model.sapere.reactions.SAPEREGradient Maven / Gradle / Ivy

There is a newer version: 35.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.model.sapere.reactions;

import gnu.trove.impl.Constants;
import gnu.trove.map.TIntDoubleMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntDoubleHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.procedure.TIntObjectProcedure;
import it.unibo.alchemist.model.sapere.dsl.impl.Expression;
import it.unibo.alchemist.model.sapere.dsl.impl.NumTreeNode;
import it.unibo.alchemist.model.sapere.dsl.impl.Type;
import it.unibo.alchemist.model.sapere.dsl.IExpression;
import it.unibo.alchemist.model.sapere.dsl.ITreeNode;
import it.unibo.alchemist.model.sapere.molecules.LsaMolecule;
import it.unibo.alchemist.model.Action;
import it.unibo.alchemist.model.Condition;
import it.unibo.alchemist.model.Context;
import it.unibo.alchemist.model.Dependency;
import it.unibo.alchemist.model.Environment;
import it.unibo.alchemist.model.sapere.ILsaMolecule;
import it.unibo.alchemist.model.sapere.ILsaNode;
import it.unibo.alchemist.model.maps.MapEnvironment;
import it.unibo.alchemist.model.Molecule;
import it.unibo.alchemist.model.Node;
import it.unibo.alchemist.model.Position;
import it.unibo.alchemist.model.Reaction;
import it.unibo.alchemist.model.Time;
import it.unibo.alchemist.model.TimeDistribution;
import it.unibo.alchemist.model.reactions.AbstractReaction;
import org.danilopianini.lang.HashString;
import org.danilopianini.util.ImmutableListSet;
import org.danilopianini.util.ListSet;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * This class provides a fast and stable gradient implementation, inspired on
 * the NBR construct used in Proto.
 *
 * @param 

Position type */ public final class SAPEREGradient

> extends AbstractReaction> { private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList<>(0)); private static final long serialVersionUID = 8362443887879500016L; private static final IExpression ZERO_NODE = new Expression(new NumTreeNode(0d)); private final int argPosition; private boolean canRun = true; private List contextCache; private final Environment, P> environment; private final List>> fakeacts = new ArrayList<>(1); private final List>> fakeconds = new ArrayList<>(2); private TIntObjectMap> gradCache = new TIntObjectHashMap<>(); private final MapEnvironment, ?, ?> mapenvironment; private P mypos; private TIntObjectMap

positionCache = new TIntObjectHashMap<>(); private final TIntDoubleMap routecache = new TIntDoubleHashMap( Constants.DEFAULT_CAPACITY, Constants.DEFAULT_LOAD_FACTOR, -1, Double.NaN ); private final ILsaMolecule source, gradient, gradientExpr, context; private List sourceCache; private final double threshold; /** * Builds a new SAPERE Gradient. * * @param environment * the current environment * @param node * the node where this reaction is scheduled * @param sourceTemplate * a template ILsaMolecule representing the source * @param gradientTemplate * a template ILsaMolecule representing the gradient. ALL the * variables MUST be the same of sourceTemplate: no un-instanced * variables are admitted when inserting tuples into nodes * @param valuePosition * the point at which the computation of the new values should be * inserted. All the data after this position will be considered * "additional information" propagated by the source. All the * values before this one, instead, will be used to distinct * different gradients * @param expression * the expression to use in order to calculate the new gradient * value. #T and #D are admitted, plus every variable present in * the gradient before valuePosition, and every variable matched * by the contextTemplate * @param contextTemplate * a template ILsaMolecule. It can be used to match some contents * of the local node in order to have local information to use * in the gradient value computation * @param gradThreshold * if the value of the gradient grows above this threshold, the * gradient evaporates * @param timeDistribution * Markovian Rate */ @SuppressWarnings("unchecked") public SAPEREGradient( final Environment, P> environment, final ILsaNode node, final ILsaMolecule sourceTemplate, final ILsaMolecule gradientTemplate, final int valuePosition, final String expression, final ILsaMolecule contextTemplate, final double gradThreshold, final TimeDistribution> timeDistribution ) { super(node, timeDistribution); setInputContext(Context.NEIGHBORHOOD); setOutputContext(Context.LOCAL); gradient = Objects.requireNonNull(gradientTemplate); source = Objects.requireNonNull(sourceTemplate); context = contextTemplate; this.environment = environment; if (valuePosition < 0) { throw new IllegalArgumentException("The position in the gradient LSA must be a positive integer"); } argPosition = valuePosition; final IExpression exp = new Expression(expression); threshold = gradThreshold; final List grexp = gradient.allocateVar(null); grexp.set(argPosition, exp); gradientExpr = new LsaMolecule(grexp); /* * Dependency management: this reaction depends on the value of source in this node, the value of gradient in * the neighbors, and the value of the context locally. Moreover, the value may change if the neighborhood * changes, or if the node moves. */ addOutboundDependency(gradient); addInboundDependency(source); addInboundDependency(Dependency.MOVEMENT); fakeconds.add(new SGFakeConditionAction(source)); addInboundDependency(gradient); fakeacts.add(new SGFakeConditionAction(gradient)); if (context != null) { addInboundDependency(context); fakeconds.add(new SGFakeConditionAction(context)); } final boolean usesRoutes = this.environment instanceof MapEnvironment && (gradientTemplate.toString().contains(LsaMolecule.SYN_ROUTE) || expression.contains(LsaMolecule.SYN_ROUTE)); mapenvironment = usesRoutes ? (MapEnvironment, ?, ?>) this.environment : null; } /** * Builds a new SAPERE Gradient. This constructor is slower, and is provided * for compatibility with the YAML-based Alchemist loader. It should be * avoided when possible, by relying on the other constructor instead. * * @param environment * the current environment * @param node * the node where this reaction is scheduled * @param sourceTemplate * a template ILsaMolecule representing the source * @param gradientTemplate * a template ILsaMolecule representing the gradient. ALL the * variables MUST be the same of sourceTemplate: no un-instanced * variables are admitted when inserting tuples into nodes * @param valuePosition * the point at which the computation of the new values should be * inserted. All the data after this position will be considered * "additional information" propagated by the source. All the * values before this one, instead, will be used to distinct * different gradients * @param expression * the expression to use in order to calculate the new gradient * value. #T and #D are admitted, plus every variable present in * the gradient before valuePosition, and every variable matched * by the contextTemplate * @param contextTemplate * a template ILsaMolecule. It can be used to match some contents * of the local node in order to have local information to use * in the gradient value computation * @param gradThreshold * if the value of the gradient grows above this threshold, the * gradient evaporates * @param timeDistribution * Markovian Rate */ public SAPEREGradient(final Environment, P> environment, final ILsaNode node, final TimeDistribution> timeDistribution, final String sourceTemplate, final String gradientTemplate, final int valuePosition, final String expression, final String contextTemplate, final double gradThreshold) { this( environment, node, new LsaMolecule(sourceTemplate), new LsaMolecule(gradientTemplate), valuePosition, expression, new LsaMolecule(contextTemplate), gradThreshold, timeDistribution ); } @Override public boolean canExecute() { return canRun; } /* * Clean up existing gradients. The new values will be computed upon * neighbors' */ private List cleanUpExistingAndRecomputeFromSource(final Map> matches) { for (final ILsaMolecule g : getNode().getConcentration(gradient)) { getLsaNode().removeConcentration(g); } final List createdFromSource = new ArrayList<>(sourceCache.size()); if (!sourceCache.isEmpty()) { matches.put(LsaMolecule.SYN_O, new NumTreeNode(getNode().getId())); for (final ILsaMolecule s : sourceCache) { for (int i = 0; i < source.size(); i++) { final ITreeNode uninstancedArg = source.getArg(i).getRootNode(); if (uninstancedArg.getType().equals(Type.VAR)) { matches.put(uninstancedArg.toHashString(), s.getArg(i).getRootNode()); } } final List gl = gradient.allocateVar(matches); final ILsaMolecule m = new LsaMolecule(gl); createdFromSource.add(m); getLsaNode().setConcentration(m); } } return createdFromSource; } @Nonnull @Override public Reaction> cloneOnNewNode( @Nonnull final Node> node, @Nonnull final Time currentTime ) { throw new UnsupportedOperationException(); } @Override public void execute() { if (sourceCache == null) { /* * First run */ updateInternalStatus(Time.ZERO, true, environment); } canRun = false; final Map> matches = new HashMap<>(); matches.put(LsaMolecule.SYN_T, new NumTreeNode(getTau().toDouble())); // PMD suppression: there is a side effect final List createdFromSource = cleanUpExistingAndRecomputeFromSource(matches); //NOPMD /* * Context computation: if there are contexts matched, use the first. * Otherwise, assign zero to every variable not yet instanced (to allow * computation to proceed). */ if (!contextCache.isEmpty()) { final ILsaMolecule contextInstance = contextCache.get(0); for (int i = 0; i < context.argsNumber(); i++) { final ITreeNode uninstancedArg = context.getArg(i).getRootNode(); if (uninstancedArg.getType().equals(Type.VAR)) { final HashString varName = uninstancedArg.toHashString(); final ITreeNode matched = matches.get(varName); final ITreeNode localVal = contextInstance.getArg(i).getRootNode(); if (matched == null || matched.equals(localVal)) { matches.put(varName, localVal); } else { throw new IllegalStateException("You are doing something nasty."); } } } } else if (context != null) { for (int i = 0; i < context.argsNumber(); i++) { final ITreeNode uninstancedArg = context.getArg(i).getRootNode(); if (uninstancedArg.getType().equals(Type.VAR)) { final HashString varName = uninstancedArg.toHashString(); final ITreeNode matched = matches.get(varName); if (matched == null) { matches.put(varName, ZERO_NODE.getRootNode()); } } } } /* * All the gradients in the neighborhood which conflict with those * generated by a source should not be considered */ final TIntObjectMap> filteredGradCache; if (createdFromSource.isEmpty()) { filteredGradCache = gradCache; } else { filteredGradCache = new TIntObjectHashMap<>(gradCache.size()); gradCache.forEachEntry(new Cleaner(createdFromSource, filteredGradCache)); } /* * Gradients in neighborhood must be discovered */ final List gradientsFound = new ArrayList<>(); final GradientSearch gradSearch = new GradientSearch(gradientsFound, matches); filteredGradCache.forEachEntry(gradSearch); gradientsFound.addAll(createdFromSource); gradientsFound.forEach(grad -> getLsaNode().setConcentration(grad)); } @Nonnull @Override public List>> getActions() { return fakeacts; } @Nonnull @Override public List>> getConditions() { return fakeconds; } /** * @return the current node as {@link ILsaNode} */ public ILsaNode getLsaNode() { return (ILsaNode) getNode(); } @Override public double getRate() { return canRun ? getTimeDistribution().getRate() : 0; } @Override protected void updateInternalStatus( final Time currentTime, final boolean hasBeenExecuted, final Environment, ?> environment ) { /* * It makes sense to reschedule the reaction if: * * the source has changed * * the contextual information has changed * * the neighbors have moved * * the gradients in the neighborhood have changed * * my position is changed */ final List sourceCacheTemp = getNode().getConcentration(source); final List contextCacheTemp = context == null ? EMPTY_LIST : getNode().getConcentration(context); final TIntObjectMap

positionCacheTemp = new TIntObjectHashMap<>(positionCache.size()); final TIntObjectMap> gradCacheTemp = new TIntObjectHashMap<>(gradCache.size()); final P curPos = this.environment.getPosition(getNode()); final boolean positionChanged = !curPos.equals(mypos); boolean neighPositionChanged = false; for (final Node> n : this.environment.getNeighborhood(getNode())) { final P p = this.environment.getPosition(n); final int nid = n.getId(); positionCacheTemp.put(nid, p); gradCacheTemp.put(n.getId(), n.getConcentration(gradient)); final boolean pConstant = p.equals(positionCache.get(nid)); if (!pConstant) { neighPositionChanged = true; } if (mapenvironment != null && (!pConstant || positionChanged)) { routecache.put(nid, mapenvironment.computeRoute(n, getNode()).length()); } } if (!sourceCacheTemp.equals(sourceCache) || !contextCacheTemp.equals(contextCache) || neighPositionChanged || !gradCacheTemp.equals(gradCache) || positionChanged ) { sourceCache = sourceCacheTemp; contextCache = contextCacheTemp; positionCache = positionCacheTemp; gradCache = gradCacheTemp; mypos = curPos; canRun = true; } } private class Cleaner implements TIntObjectProcedure> { private final List createdFromSource; private final TIntObjectMap> filteredGradCache; Cleaner(final List cfs, final TIntObjectMap> fgc) { createdFromSource = cfs; filteredGradCache = fgc; } @Override public boolean execute(final int a, final List ol) { final List nl = new ArrayList<>(ol.size()); for (final ILsaMolecule matchedGrad : ol) { boolean hasMatched = false; for (final ILsaMolecule sm : createdFromSource) { int i = 0; /* * Check the exogenous gradients for all the arguments * before the distance: if they match with at least one of * the sources, they should not be considered */ while (i < argPosition && matchedGrad.getArg(i).matches(sm.getArg(i), null)) { i++; } if (i == argPosition) { hasMatched = true; break; } } if (!hasMatched) { nl.add(matchedGrad); } } filteredGradCache.put(a, nl); return true; } } private class GradientSearch implements TIntObjectProcedure> { private final List gradientsFound; private final Map> matches; GradientSearch(final List gf, final Map> m) { gradientsFound = gf; matches = m; } @Override public boolean execute(final int a, final List mgnList) { if (!mgnList.isEmpty()) { final P aPos = positionCache.get(a); final double distNode = aPos.distanceTo(mypos); matches.put(LsaMolecule.SYN_O, new NumTreeNode(a)); matches.put(LsaMolecule.SYN_D, new NumTreeNode(distNode)); if (mapenvironment != null) { matches.put(LsaMolecule.SYN_ROUTE, new NumTreeNode(routecache.get(a))); } final Map> localMatches = mgnList.size() > 1 ? new HashMap<>(matches) : matches; for (final ILsaMolecule mgn : mgnList) { /* * Instance all the variables but synthetics. */ for (int i = 0; i < gradient.size(); i++) { final ITreeNode uninstancedArg = gradient.getArg(i).getRootNode(); if (uninstancedArg.getType().equals(Type.VAR)) { final HashString varName = uninstancedArg.toHashString(); if (!varName.toString().startsWith("#")) { final ITreeNode localVal = mgn.getArg(i).getRootNode(); localMatches.put(varName, localVal); } } } /* * Compute new value */ final List valuesFound = gradientExpr.allocateVar(localMatches); if (gradientsFound.isEmpty()) { if ((Double) valuesFound.get(argPosition).getRootNodeData() <= threshold) { gradientsFound.add(new LsaMolecule(valuesFound)); } } else { boolean compatibleFound = false; for (int j = 0; j < gradientsFound.size(); j++) { final ILsaMolecule gradToCompare = gradientsFound.get(j); int i = 0; for (; i < argPosition; i++) { if (!gradToCompare.getArg(i).matches(valuesFound.get(i), null)) { /* * Gradients are not compatible */ break; } } if (i == argPosition) { /* * These two gradients are comparable */ compatibleFound = true; final double newVal = (Double) valuesFound.get(argPosition).getRootNodeData(); final double oldVal = (Double) gradToCompare.getArg(argPosition).getRootNodeData(); if (newVal < oldVal) { gradientsFound.set(j, new LsaMolecule(valuesFound)); } } } if (!compatibleFound && (Double) valuesFound.get(argPosition).getRootNodeData() < threshold) { gradientsFound.add(new LsaMolecule(valuesFound)); } } } } return true; } } private static class SGFakeConditionAction implements Action>, Condition> { private static final long serialVersionUID = 1L; private static final ListSet DEPENDENCY = ImmutableListSet.of(Dependency.EVERYTHING); private final Molecule mol; SGFakeConditionAction(final Molecule m) { super(); mol = m; } @Override public Action> cloneAction( final Node> node, final Reaction> reaction ) { return null; } @Override public Condition> cloneCondition( final Node> node, final Reaction> reaction ) { return null; } @Override public void execute() { } @Override public Context getContext() { return null; } @Override public ListSet getInboundDependencies() { return DEPENDENCY; } @Nonnull @Override public ListSet getOutboundDependencies() { return DEPENDENCY; } @Override public Node> getNode() { return null; } @Override public double getPropensityContribution() { return 0; } @Override public boolean isValid() { return false; } @Override public String toString() { return mol.toString(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy