org.integratedmodelling.engine.modelling.ScaleMediator 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;
import java.util.HashMap;
import java.util.Map;
import org.integratedmodelling.api.knowledge.IProperty;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.agents.IObservationGraphNode;
import org.integratedmodelling.api.modelling.agents.IScaleMediator;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.api.time.ITemporalSeries;
import org.integratedmodelling.api.time.ITimeInstant;
import org.integratedmodelling.engine.modelling.ObservationController.AgentStatus;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabInternalErrorException;
import org.integratedmodelling.exceptions.KlabResourceNotFoundException;
import org.integratedmodelling.exceptions.KlabUnsupportedOperationException;
import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.RTree;
/**
* Scale Mediators do the work of modifying a state expressed in one scale into another scale, and possibly
* caching an intermediate representation for efficient re-modification into other arbitrary scales.
*
* @author luke
*
*/
public abstract class ScaleMediator implements IScaleMediator {
protected static final IMetadata metadata = new ScaleMediatorMetadata(); // override
// in
// subclasses
protected final ObservationController controller;
protected final IActiveDirectObservation subject;
protected final ITemporalSeries timeOrderedStateObservations = new TemporalSeries();
private static final long SOME_ACCEPTABLE_SLEEP_WAIT_INTERVAL = 500;
/**
* SubjectObservation: a node in the temporal series of observations of the observedSubject, as seen by
* this ScaleMediator.
*
* Subclass this to fit the various interpolation strategies. The representation can be changed in any way
* appropriate for the interpolation mechanism; for instance, if one of the state properties is useless,
* feel free to delete it from the SubjectObservation nodes as they are created.
*
* Consider adding a member: "Object intermediateRepresentation" if the interpolation strategy cannot
* provide temporal continuity in its representation; that is, if the intermediate representation must be
* stored as snapshots, then it would make sense to store each snapshot with each instance of
* SubjectObservation.
*
* Alternatively, if temporal continuity can be represented in one object (i.e. an n-dimensional vector
* image), then it would be more fitting to store that as a member of ScaleMediator
*
* @author luke
*
*/
protected class SubjectObservation {
final IObservationGraphNode agentStateNode;
final Map spatialIndexes = new HashMap();
final Map> outputStates = new HashMap>(); // the
// output
// cache(s)
SubjectObservation(IObservationGraphNode agentStateNode) {
this.agentStateNode = agentStateNode;
}
Object getValue(IProperty property, int index) {
return agentStateNode.getAgentState().getStates().get(property).getValue(index);
}
// TODO allow for arbitrary spatial dimensions (see notes on ISpatialExtent)
public RTree getSpatialIndex(IProperty property) {
RTree result = spatialIndexes.get(property);
if (result == null) {
result = new RTree();
IState state = agentStateNode.getAgentState().getStates().get(property);
ISpatialExtent spaceExtent = state.getSpace();
for (int i = 0; i < state.getValueCount(); i++) {
ISpatialExtent extent = spaceExtent.getExtent(i);
Rectangle rectangle = getRectangleFromEnvelope(extent);
result.add(rectangle, i);
}
spatialIndexes.put(property, result);
}
return result;
}
}
protected Rectangle getRectangleFromEnvelope(ISpatialExtent extent) {
// float[] min = { (float) extent.getMinX(), (float) extent.getMinY() };
// float[] max = { (float) extent.getMaxX(), (float) extent.getMaxY() };
Rectangle rectangle = new Rectangle((float) extent.getMinX(), (float) extent.getMinY(), (float) extent
.getMaxX(), (float) extent.getMaxY());
return rectangle;
}
/**
* TODO startTime is currently always null, because it's not possible to set through subclasses. If an
* interpolator is created @ runtime for a pre-existing agent, then it might lead to wasted computation...
* probably not a big deal at this point, but warrants re-thinking.
*
* @param subject
* @param controller
* @param startTime
* @throws KlabResourceNotFoundException
*/
public ScaleMediator(IActiveDirectObservation subject, ObservationController controller,
ITimeInstant startTime)
throws KlabResourceNotFoundException {
this.subject = subject;
this.controller = controller;
controller.subscribe(this, subject, startTime);
}
public ScaleMediator(IActiveDirectObservation subject, ObservationController controller)
throws KlabResourceNotFoundException {
this(subject, controller, null);
}
@Override
public void notify(IObservationGraphNode node) {
SubjectObservation observation = new SubjectObservation(node);
timeOrderedStateObservations.put(node.getAgentState().getTimePeriod(), observation);
}
@Override
public IState getTargetState(ITimeInstant time, IProperty property, IScale targetScale)
throws KlabException {
// TODO is this IConcept? -------------------^
// TODO record dependencies for each observation made by a caller
// TODO if the agent is reported "dead", then the caller will proceed without a causal link.
// But if a collision then causes the agent to stay alive, the original caller will not re-evaluate.
// Fix this by making "dead" be an official agent-state, with a null end time. (requires refactoring
// PeriodValue)
// see if the observation has already been made
SubjectObservation observation = timeOrderedStateObservations.getAtTime(time);
while (observation == null) {
// there is no observation result for this time instant yet. is the agent dead?
AgentStatus deadOrAlive = controller.getAgentStatus(subject, time);
if (deadOrAlive == AgentStatus.dead) {
return null;
} else if (deadOrAlive == AgentStatus.evaluated || deadOrAlive == AgentStatus.notYetEvaluated
|| deadOrAlive == AgentStatus.nonExistent) {
// either the agent is evaluated and we don't have the result yet, or nothing has changed.
// wait it out longer...
try {
Thread.sleep(SOME_ACCEPTABLE_SLEEP_WAIT_INTERVAL);
} catch (InterruptedException e) {
// probably not a big deal - just wake up and keep trying
}
if (deadOrAlive == AgentStatus.evaluated) {
observation = timeOrderedStateObservations.getAtTime(time);
}
} else {
// either the result was null or the enum has changed out of sync with this code. either way
// it's weird.
throw new KlabInternalErrorException("controller.getAgentStatus() either returned null or the enum has changed out of sync with this code");
}
}
// now we have a valid subject observation for the query time, so convert it to the target scale and
// return it.
IState result = getStateForTargetScale(observation, property, targetScale);
return result;
}
protected IState getStateForTargetScale(SubjectObservation observation, IProperty property, IScale targetScale)
throws KlabUnsupportedOperationException {
// has the output scale been generated for this observation yet?
Map result = observation.outputStates.get(targetScale);
if (result == null) {
// output scale does not yet exist. Generate it for the result, and also save the result in the
// output cache.
result = generateTargetScale(observation, targetScale);
observation.outputStates.put(targetScale, result);
}
return result.get(property);
}
/**
* This is the meat of the interpolator. It does the actual work of generating target scales from observed
* scales. It will render an intermediate (continuous) representation into a target (non-continuous)
* scale, or it will directly translate the subject's scale into the observer's scale, as appropriate for
* the ScaleMediator class.
*
* @param observation
* @param targetScale
* @return
* @throws KlabUnsupportedOperationException
*/
protected abstract Map generateTargetScale(SubjectObservation observation, IScale targetScale)
throws KlabUnsupportedOperationException;
@Override
public IMetadata getMetadata() {
return metadata;
}
/**
* the static version of this method is needed because the metadata represents *class*-level information;
* i.e. what the algorithm is capable of, rather than the state of an instantiated object
*
* @return metadata
*/
public static IMetadata getMetadataStatic() {
return metadata;
}
@Override
public void invalidate(ITimeInstant interruptTime) {
SubjectObservation lastObservation = timeOrderedStateObservations.getLast();
if (lastObservation == null) {
return;
}
while (lastObservation.agentStateNode.getAgentState().getTimePeriod().getEnd()
.compareTo(interruptTime) >= 0) {
timeOrderedStateObservations.remove(lastObservation.agentStateNode.getAgentState()
.getTimePeriod().getStart());
lastObservation = timeOrderedStateObservations.getLast();
if (lastObservation == null) {
return;
}
}
}
}