org.integratedmodelling.engine.modelling.runtime.DirectObservation Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (C) 2007, 2015:
*
* - Ferdinando Villa - integratedmodelling.org - any
* other authors listed in @author annotations
*
* All rights reserved. This file is part of the k.LAB software suite, meant to enable
* modular, collaborative, integrated development of interoperable data and model
* components. For details, see http://integratedmodelling.org.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the Affero General Public License Version 3 or any later version.
*
* This program is distributed in the hope that it will be useful, but without any
* warranty; without even the implied warranty of merchantability or fitness for a
* particular purpose. See the Affero General Public License for more details.
*
* You should have received a copy of the Affero General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite
* 330, Boston, MA 02111-1307, USA. The license is also available at:
* https://www.gnu.org/licenses/agpl.html
*******************************************************************************/
package org.integratedmodelling.engine.modelling.runtime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IObservation;
import org.integratedmodelling.api.knowledge.IProperty;
import org.integratedmodelling.api.knowledge.ISemantic;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.IAction;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveEvent;
import org.integratedmodelling.api.modelling.IActiveProcess;
import org.integratedmodelling.api.modelling.IActiveRelationship;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.ICoverage;
import org.integratedmodelling.api.modelling.IDirectObservation;
import org.integratedmodelling.api.modelling.IEvent;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.IObservableSemantics;
import org.integratedmodelling.api.modelling.IProcess;
import org.integratedmodelling.api.modelling.IRelationship;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.ISubject;
import org.integratedmodelling.api.modelling.agents.IAgentState;
import org.integratedmodelling.api.modelling.agents.ICollision;
import org.integratedmodelling.api.modelling.agents.IObservationController;
import org.integratedmodelling.api.modelling.agents.IObservationGraphNode;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.modelling.scheduling.ITransition;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.provenance.IProvenance;
import org.integratedmodelling.api.runtime.IContext;
import org.integratedmodelling.api.time.ITemporalExtent;
import org.integratedmodelling.api.time.ITimePeriod;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.beans.State;
import org.integratedmodelling.common.beans.responses.LocalExportResponse;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.interfaces.Monitorable;
import org.integratedmodelling.common.interfaces.actuators.IDirectActuator;
import org.integratedmodelling.common.interfaces.actuators.IStateActuator;
import org.integratedmodelling.common.kim.KIMAction;
import org.integratedmodelling.common.kim.KIMModel;
import org.integratedmodelling.common.kim.KIMObservableSemantics;
import org.integratedmodelling.common.knowledge.Observation;
import org.integratedmodelling.common.metadata.Metadata;
import org.integratedmodelling.common.model.Coverage;
import org.integratedmodelling.common.model.actuators.Actuator;
import org.integratedmodelling.common.model.actuators.EventActuator;
import org.integratedmodelling.common.model.actuators.ProcessActuator;
import org.integratedmodelling.common.model.actuators.RelationshipActuator;
import org.integratedmodelling.common.model.actuators.SubjectContextualizerActuator;
import org.integratedmodelling.common.model.runtime.Transition;
import org.integratedmodelling.common.states.States;
import org.integratedmodelling.common.utils.NameGenerator;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.common.vocabulary.ObservableSemantics;
import org.integratedmodelling.engine.modelling.AgentState;
import org.integratedmodelling.engine.modelling.TemporalCausalGraph;
import org.integratedmodelling.engine.modelling.resolver.Dataflow;
import org.integratedmodelling.engine.modelling.resolver.ResolutionScope;
import org.integratedmodelling.engine.modelling.resolver.Resolver;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabValidationException;
/**
* Holds the methods that interface each directly observed object (subject/agent, process,
* event) with the multiagent/multiscale contextualizer. Moved all methods from Subject to
* here so that Process and Event can react to transitions.
*
* @author Ferd
* @author Luke
*/
public abstract class DirectObservation extends Observation
implements IActiveDirectObservation, Monitorable {
/**
* Saved with artifact IDs; will build the artifact on demand.
*
* @author ferdinando.villa
*
*/
public static interface ArtifactGenerator {
/**
* Build the artifact and return the bean allowing access to the outputs.
*
* This is called only after a user has selected the export option on the
* local engine, so it should be free to simply export any files needed to
* a temporary directory and set their path and (if applicable) local paths
* into the response, including, if applicable, model source code.
*
* @return
*/
LocalExportResponse generateArtifact();
}
protected boolean initialized = false;
// index value of _scale.getTime().getExtent(i) corresponding to agent's
// temporal
// location
protected int currentTemporalExtentIndex = 0;
protected ITimePeriod currentTimePeriodCache = null;
protected TemporalCausalGraph causalGraph;
protected INamespace namespace;
protected String name;
protected IMonitor monitor;
protected IMetadata metadata = new Metadata();
protected ResolutionScope resolutionContext;
protected ITransition lastTransitionSeen;
protected ArrayList> states = new ArrayList<>();
/*
* for the Groovy interface. See setVar/getVar
*/
private Map vars = new HashMap<>();
/*
* internal ID for observation paths
*/
protected String internalID = NameGenerator
.shortUUID();
private List directObservations = new ArrayList<>();
IDirectObservation contextSubject = null;
ArrayList> processes = new ArrayList<>();
/*
* holders for exportable artifacts other than observations
*/
protected Map datasetOutputs = new HashMap<>();
protected Map modelOutputs = new HashMap<>();
/**
* Roles stated or inferred from the model chosen.
*/
List> localRoles = new ArrayList<>();
/**
* Coverage for all observables already observed in our context, whether successfully
* or not.
*/
protected Map resolved = new HashMap<>();
// we store compiled versions of any actions coming from the model.
List initActions = new ArrayList<>();
List timeActions = new ArrayList<>();
boolean actionsStored;
private int priorityOrder = 0;
/*
* set in top subject after dataflow is compiled.
*/
protected Dataflow dataflow;
protected DirectObservation(IObservableSemantics observable,
IActiveDirectObservation contextObservation, IScale scale, IContext context,
INamespace namespace,
String name,
IMonitor monitor) {
this.scale = scale;
this.namespace = namespace;
this.name = name;
this.monitor = monitor;
this.context = context;
setObservable(observable, context, name);
}
public void setModel(IModel model) {
this.model = model;
if (model instanceof KIMModel) {
this.localRoles.addAll(((KIMModel) model).getLocalRoles());
}
}
@Override
public Collection getRolesFor(ISemantic observable) {
Set ret = new HashSet<>();
IConcept concept = observable.getType();
if (observable instanceof IObservation) {
/*
* add roles set directly by the client. These are not specific of our context
* but they're always valid.
*/
ret.addAll(((Observation) observable).getExplicitRoles());
/*
* switch to the concept to proceed with role inference
*/
concept = ((IObservation) observable).getObservable().getType();
}
if (observable instanceof KIMObservableSemantics) {
ret.add(((KIMObservableSemantics) observable).getStatedRole());
}
for (Pair rd : localRoles) {
if (concept.is(rd.getFirst().getType())) {
ret.add(rd.getSecond());
}
}
return ret;
}
@Override
public IModel getModel() {
return resolutionContext == null ? null : resolutionContext.getModel();
}
public void setResolutionContext(ResolutionScope resolutionContext) {
this.resolutionContext = resolutionContext;
}
public void setVar(String id, Object value) {
vars.put(id, value);
}
public Object getVar(String id) {
return vars.get(id);
}
public IResolutionScope getResolutionScope() {
return this.resolutionContext;
}
@Override
public IMetadata getMetadata() {
return metadata;
}
@Override
public IDirectObservation getContextObservation() {
return contextSubject;
}
public void prepareForTransition(ITransition transition) {
if (lastTransitionSeen == null
|| transition.getTimeIndex() > lastTransitionSeen.getTimeIndex()) {
lastTransitionSeen = transition;
}
/*
* ensure that all states are tuned to the current transition
*/
for (IState state : (this instanceof ISubject || this instanceof IEvent ? this.getStates()
: context.getSubject().getStates())) {
((org.integratedmodelling.common.states.State) state).flushStorage(transition);
}
}
public ICoverage initialize(IResolutionScope context, IProvenance.Action cause, IMonitor monitor)
throws KlabException {
ICoverage ret = Coverage.EMPTY;
try {
Resolver resolver = new Resolver(context, cause);
ret = resolver.resolve(this, monitor);
} catch (Exception e) {
monitor.error(e);
}
initialized = true;
return ret;
}
public void addProcess(IProcess process) throws KlabException {
IProperty p = NS
.getPropertyFor(process, getObservable().getSemantics(), getNamespace());
processes.add(new Pair<>(p, process));
addDirectObservation((DirectObservation) process);
}
public void addEvent(IEvent event) throws KlabException {
addDirectObservation((DirectObservation) event);
/*
* TODO notify
*/
}
/**
* Called by instantiators that have actions other than initialization or resolution.
* These are added to the actuator if we have one, or a new actuator is created if
* missing. Will only be called after resolution by
* {@link Dataflow#initializeDirectActuator}.
*
* @param action
*/
public void addAction(IAction action) {
if (actuator == null) {
if (this instanceof ISubject) {
actuator = new SubjectContextualizerActuator((IActiveSubject) this, null, null, Collections
.singletonList(action), monitor);
} else if (this instanceof IProcess) {
actuator = new ProcessActuator((IActiveProcess) this, null, null, Collections
.singletonList(action), monitor);
} else if (this instanceof IEvent) {
actuator = new EventActuator((IActiveEvent) this, null, null, Collections
.singletonList(action), monitor);
} else if (this instanceof IRelationship) {
actuator = new RelationshipActuator((IActiveRelationship) this, null, null, Collections
.singletonList(action), monitor);
}
} else {
((Actuator) actuator).addAction(action);
}
/*
* TODO
*/
}
/**
* Re-evaluate states given the new temporal location of this agent. The agent has
* access to the current states during execution of this method because they have not
* yet changed. The agent is expected to update the state values and set them in the
* result. this method is intended to be overridden by subclasses as appropriate.
*
* @param timePeriod
* @return new transition
*/
@Override
public ITransition reEvaluateStates(ITimePeriod timePeriod) {
Map currentStates = getObjectStateCopy();
IAgentState agentState = new AgentState(this, timePeriod, currentStates);
ITransition noTransition = new Transition(getScale(), agentState, true);
return noTransition;
}
@Override
public ICollision detectCollision(IObservationGraphNode myAgentState, IObservationGraphNode otherAgentState) {
// TODO not going to implement this. Collision detection is extremely
// difficult.
return null;
}
@Override
public boolean doesThisCollisionAffectYou(IAgentState agentState, ICollision collision) {
// TODO not going to implement this. Collision detection is extremely
// difficult.
return false;
}
@Override
public IState getState(IObservableSemantics obs, Object... data)
throws KlabException {
for (Pair s : this.states) {
/*
* Only compare observable and observation: the inherent type has been set to
* equal the subject's, so it may differ without incompatibility. FIXME -
* should be OK if the observable doesn't have it but not OK if it does and
* it's not the same type (using is()).
*/
if (((ObservableSemantics) obs).equalsWithoutInherency(s.getSecond()
.getObservable().getSemantics())) {
return s.getSecond();
}
}
if (!NS.isQuality(obs)) {
throw new KlabValidationException("cannot create a state for a non-quality: "
+ obs.getType());
}
// if (obs.getInherentType() == null) {
// obs = ((Observable)
// obs).withInherentType(this.getObservable().getType());
// }
IState ret = States.create(obs, this);
// FIXME remove this - we do not need a data property until we need it.
this.states.add(new Pair<>(NS
.getPropertyFor(ret, this.getObservable()
.getSemantics(), getNamespace()), ret));
((Context) this.context).addDelta(ret);
((org.integratedmodelling.common.states.State) ret).setContext(this.context);
return ret;
}
public IState getStateFor(IObservableSemantics obs, IStateActuator actuator)
throws KlabException {
for (Pair s : this.states) {
/*
* Only compare observable and observation: the inherent type has been set to
* equal the subject's, so it may differ without incompatibility. FIXME -
* should be OK if the observable doesn't have it but not OK if it does and
* it's not the same type (using is()).
*/
if (((ObservableSemantics) obs).equalsWithoutInherency(s.getSecond()
.getObservable().getSemantics())) {
return s.getSecond();
}
}
if (!NS.isQuality(obs) && !NS.isTrait(obs)) {
throw new KlabValidationException("cannot create a state for a non-quality: "
+ obs.getType());
}
IState ret = null;
Map dummy = new HashMap<>();
if (actuator.isConstant()) {
ret = new org.integratedmodelling.common.states.State(actuator
.process(0, dummy, ITransition.INITIALIZATION)
.get(actuator.getName()), obs, this, false, false);
} else {
/*
* TODO for now we only let process contextualizers create dynamic states
* explicitly.
*/
ret = actuator.isProbabilistic()
? (/*
* actuator.isTemporal() ? States.createProbabilistic(obs, this) :
*/ States.createProbabilisticStatic(obs, this))
: (/*
* actuator.isTemporal() ? States.create(obs, this) :
*/ States.createStatic(obs, this));
}
// FIXME remove this - we do not need a data property until we need it.
this.states.add(new Pair<>(NS
.getPropertyFor(ret, this.getObservable()
.getSemantics(), getNamespace()), ret));
((Context) this.context).addDelta(ret);
((org.integratedmodelling.common.states.State) ret).setContext(this.context);
return ret;
}
@Override
public Collection getStates() {
ArrayList ret = new ArrayList<>();
for (Pair pd : this.states) {
ret.add(pd.getSecond());
}
return ret;
}
@Override
public IState getStaticState(IObservableSemantics obs) throws KlabException {
for (Pair s : this.states) {
/*
* Only compare observable and observation: the inherent type has been set to
* equal the subject's, so it may differ without incompatibility. FIXME -
* should be OK if the observable doesn't have it but not OK if it does and
* it's not the same type (using is()).
*/
if (((ObservableSemantics) obs).equalsWithoutInherency(s.getSecond()
.getObservable().getSemantics())) {
return s.getSecond();
}
}
if (!NS.isQuality(obs)) {
throw new KlabValidationException("cannot create a state for a non-quality: "
+ obs.getType());
}
// if (obs.getInherentType() == null) {
// obs = ((Observable)
// obs).withInherentType(this.getObservable().getType());
// }
IState ret = States.createStatic(obs, this);
// FIXME remove this - we do not need a data property until we need it.
this.states.add(new Pair<>(NS
.getPropertyFor(ret, this.getObservable()
.getSemantics(), getNamespace()), ret));
((org.integratedmodelling.common.states.State) ret).setContext(this.context);
((Context) this.context).addDelta(ret);
return ret;
}
/**
* @return context subject
*/
public IDirectObservation getContextSubject() {
return this.contextSubject;
}
/**
* @param subject
*/
public void setContextSubject(IDirectObservation subject) {
this.contextSubject = subject;
this.parentId = ((Observation) subject).getInternalId();
if (subject instanceof Subject) {
this.context = ((Subject) subject).getContext();
}
}
/**
* Return all direct observations in order of dependency (stated order in model).
*
* @return direct observations
*/
public List getDirectObservations() {
return this.directObservations;
}
/*
* called to add a direct observation to the observation graph. The order of addition
* will determine the order of contextualization.
*/
protected void addDirectObservation(DirectObservation obs) {
this.directObservations.add(obs);
((Context) this.context).addDelta(obs);
/*
* TODO synchronize event handlers with interceptor - use semantics
*/
}
/**
* Recursively add the subject and all its dependencies to the observation graph
* (which is contained in the controller parameter). Keep a list of what's been added
* already, to speed up performance, avoid infinite recursion, and avoid duplicating
* the initial observation tasks for the agents.
*
* @param subjectObserver
* @param simulationTimePeriod
* @param controller
* @param alreadyAdded
*/
protected void addSubjectToObservationGraph(DirectObservation subject, ITimePeriod simulationTimePeriod, IObservationController controller, HashSet alreadyAdded) {
if (alreadyAdded == null) {
alreadyAdded = new HashSet<>();
}
if (!alreadyAdded.contains(subject)) {
alreadyAdded.add(subject);
for (DirectObservation child : subject.getDirectObservations()) {
addSubjectToObservationGraph(child, simulationTimePeriod, controller, alreadyAdded);
}
ITimePeriod agentCreationTimePeriod = simulationTimePeriod;
ITemporalExtent subjectTime = subject.getScale().getTime();
if (subjectTime != null) {
// agent has its own temporal scale, so don't inherit the
// default. Start
// with extent 0.
agentCreationTimePeriod = subjectTime.getExtent(0).collapse();
}
AgentState initialState = new AgentState(subject, agentCreationTimePeriod, ((IActiveDirectObservation) subject)
.getObjectStateCopy());
controller
.createAgent(subject, initialState, agentCreationTimePeriod, null, null, true);
}
}
/**
* Default implementation for Subjects (non-agents) without any explicit view of time.
* This means that they can be re-evaluated over time by repeating the same
* observations, but they do not perform any logic or application of rules based on
* states or state transitions.
*
* @throws KlabException
*/
@Override
public ITransition performTemporalTransitionFromCurrentState() throws KlabException {
ITransition result;
// set the clock forward
if (moveToNextTimePeriod() == null) {
// we went beyond the agent's last temporal extent, so the agent
// dies
result = new Transition(getScale(), null, false);
} else {
result = reEvaluateStates(getCurrentTimePeriod());
if (result.agentSurvives()) {
prepareForTransition(result);
if (actuator instanceof IDirectActuator) {
((IDirectActuator) actuator).processTransition(result, monitor);
}
}
}
return result;
}
protected ITimePeriod moveToNextTimePeriod() {
if (getCurrentTimePeriod() == null) {
// if we're already beyond the valid index numbers, then don't
// increment any
// more.
return null;
}
// set the clock forward & invalidate the cache
currentTemporalExtentIndex++;
currentTimePeriodCache = null;
// reload the cache and validate that we're still within the subject's
// temporal
// scale
ITimePeriod result = getCurrentTimePeriod();
if (result == null) {
// TODO either extend the scale or the agent must die
// currently, the agent dies by default
}
return result;
}
@Override
public String getName() {
return name;
}
/**
* get the TemporalExtent (which is also a TimePeriod) of the agent's current temporal
* location. Will return null if the index has gone beyond the agent's temporal scale.
*
* @return
*/
protected ITimePeriod getCurrentTimePeriod() {
if (currentTimePeriodCache == null) {
ITemporalExtent time = getScale().getTime();
if (time != null) {
// FIXME check the -1 - not sure yet
if (currentTemporalExtentIndex >= (time.getMultiplicity()) - 1)
return null;
ITemporalExtent extent = time.getExtent(currentTemporalExtentIndex);
currentTimePeriodCache = extent.collapse();
}
}
return currentTimePeriodCache;
}
/**
* @return the causal graph
*/
public TemporalCausalGraph getCausalGraph() {
return causalGraph;
}
/**
* @return true if initialization has completed
*/
public boolean isInitialized() {
return this.initialized;
}
@Override
public INamespace getNamespace() {
return namespace;
}
private void storeActions() {
if (!actionsStored) {
actionsStored = true;
if (getModel() != null) {
for (IAction a : getModel().getActions()) {
if (a.getDomain().isEmpty()) {
initActions.add(((KIMAction) a)
.compileFor(this.getScale(), getModel()));
} else if (a.getDomain().contains(KLAB.c(NS.TIME_DOMAIN))) {
timeActions.add(((KIMAction) a)
.compileFor(this.getScale(), getModel()));
}
}
}
}
}
/**
* @param transition
* @throws KlabException
*/
public void execActions(ITransition transition) throws KlabException {
if (!actionsStored) {
storeActions();
}
if (transition == null) {
for (IAction a : initActions) {
((KIMAction) a).execute(contextSubject, this, transition, monitor);
}
} else {
for (IAction a : timeActions) {
((KIMAction) a).execute(contextSubject, this, transition, monitor);
}
}
}
@Override
public void setMonitor(IMonitor monitor) {
this.monitor = monitor;
}
/**
* @param ret
*/
public void serialize(org.integratedmodelling.common.beans.DirectObservation ret) {
// TODO Auto-generated method stub
super.serialize(ret);
ret.setName(getName());
for (IState s : getStates()) {
ret.getStates().add(KLAB.MFACTORY.adapt(s, State.class));
}
for (IDirectObservation d : directObservations) {
}
/**
* export any other artifacts
*/
for (String m : modelOutputs.keySet()) {
ret.getModelOutputs().add(m);
}
for (String m : datasetOutputs.keySet()) {
ret.getDatasetOutputs().add(m);
}
}
@Override
public IObservation find(String id) {
if (this.id.equals(id)) {
return this;
}
IObservation ret = null;
for (IState sub : getStates()) {
ret = ((Observation) sub).find(id);
if (ret != null) {
return ret;
}
}
return ret;
}
@Override
public void addEventHandler(IAction action) {
// TODO Auto-generated method stub
}
public void addOutputModel(String artifactId, ArtifactGenerator artifactGenerator) {
modelOutputs.put(artifactId, artifactGenerator);
}
public void addOutputDataset(String artifactId, ArtifactGenerator artifactGenerator) {
datasetOutputs.put(artifactId, artifactGenerator);
}
/**
* Return our priority in the dataflow we're in, if any. If this is 0, we can
* contextualize concurrently with others having 0 order.
*
* @return
*/
public int getPriorityOrder() {
return this.priorityOrder;
}
public void computePriorityOrder(Dataflow dataflow) {
if (this instanceof Subject) {
for (ISubject s : ((Subject) this).getSubjects()) {
((DirectObservation) s).computePriorityOrder(dataflow);
}
for (IProcess s : ((Subject) this).getProcesses()) {
((DirectObservation) s).computePriorityOrder(dataflow);
}
/*
* relationships and events are only created later.
*/
}
/*
* if this is a subject and has a dataflow, its priority is the depth of the
* dataflow plus one, so that we are guaranteed to be contextualized after all of
* our children.
*/
if (this instanceof Subject && dataflow.getSubject().equals(this)) {
this.priorityOrder = dataflow.getDepth() + 1;
} else {
int d = dataflow.getDepth(this);
/*
* subtract 1 if < 0 so that children with no dependencies get the fully
* contextualized subject in their actions.
*/
this.priorityOrder = dataflow.getDepth() - (d < 0 ? (d - 1) : d);
}
KLAB.info("Priority for " + this + " is " + priorityOrder);
}
public void setDataflow(Dataflow dataflow) {
this.dataflow = dataflow;
}
@Override
public ICoverage getResolvedCoverage(ISemantic concept) {
for (IObservableSemantics os : resolved.keySet()) {
if (os.getType().is(concept)) {
return resolved.get(os);
}
}
return null;
}
public LocalExportResponse buildArtifact(String artifactId, boolean isModel) {
ArtifactGenerator builder = isModel ? modelOutputs.get(artifactId) : datasetOutputs.get(artifactId);
return builder == null ? null : builder.generateArtifact() ;
}
}