org.integratedmodelling.engine.modelling.runtime.Context 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.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IObservation;
import org.integratedmodelling.api.metadata.IReport;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.IDirectObserver;
import org.integratedmodelling.api.modelling.IEvent;
import org.integratedmodelling.api.modelling.IExtent;
import org.integratedmodelling.api.modelling.IKnowledgeObject;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.IModelBean;
import org.integratedmodelling.api.modelling.IModelObject;
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.IState;
import org.integratedmodelling.api.modelling.ISubject;
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.monitoring.Messages;
import org.integratedmodelling.api.provenance.IProvenance;
import org.integratedmodelling.api.runtime.IContext;
import org.integratedmodelling.api.runtime.ISession;
import org.integratedmodelling.api.runtime.ITask;
import org.integratedmodelling.api.runtime.ITask.Status;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.interfaces.NetworkSerializable;
import org.integratedmodelling.common.kim.KIMDirectObserver;
import org.integratedmodelling.common.knowledge.Definition;
import org.integratedmodelling.common.knowledge.Observation;
import org.integratedmodelling.common.knowledge.ObservationGroup;
import org.integratedmodelling.common.model.runtime.AbstractContext;
import org.integratedmodelling.common.model.runtime.Session;
import org.integratedmodelling.common.model.runtime.Space;
import org.integratedmodelling.common.monitoring.Monitor;
import org.integratedmodelling.common.provenance.Provenance;
import org.integratedmodelling.common.reporting.Report;
import org.integratedmodelling.common.states.State;
import org.integratedmodelling.common.utils.NameGenerator;
import org.integratedmodelling.common.utils.Path;
import org.integratedmodelling.common.vocabulary.GeoNS;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.common.vocabulary.Observables;
import org.integratedmodelling.engine.ModelFactory;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.literals.ShapeValue;
import org.integratedmodelling.engine.modelling.TemporalCausalGraph;
import org.integratedmodelling.engine.modelling.resolver.ResolutionScope;
import org.integratedmodelling.engine.visualization.VisualizationFactory;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import org.integratedmodelling.exceptions.KlabValidationException;
import com.ibm.icu.text.NumberFormat;
/**
* Manages observation and resolution. Initialized with a subject observer (and a
* session/monitor). Holds the root subject and the correspondent resolution context.
* Allows contextualization for all the dependent observations.
*
* @author Ferd
*
*/
public class Context extends AbstractContext implements NetworkSerializable {
// values for _status
final static int EMPTY = 0;
final static int DEFINED = 1;
final static int INITIALIZED = 2;
final static int RUNNING = 3;
final static int CONTEXTUALIZED = 4;
final static int ERROR = -1;
final static int INTERRUPTED = -2;
// this is set only when the object is created and is passed along to copies
int status = EMPTY;
int currentTime = -1;
volatile Boolean paused = false;
IResolutionScope resolutionContext = null;
String deferredObservableId = null;
Object deferredObservable = null;
boolean isDeferred = false;
Set newObservations = new HashSet<>();
Provenance provenance;
List deltas = new ArrayList<>();
IMonitor monitor;
List scenarioIds = new ArrayList<>();
String name;
Set breakpoints = new HashSet<>();
IReport report = new Report();
private long timeOfLastChange = new Date().getTime();
private IDirectObserver rootObserver;
// forcings to apply to scale if deferred initialization is requested and
// not null.
private Collection forcings = null;
private double[] locationOfInterest;
@Override
public IReport getReport() {
return report;
}
public double[] getLocationOfInterest() {
return locationOfInterest;
}
class RunThread implements Runnable {
@Override
public void run() {
status = RUNNING;
try {
/**
* TODO install listener to be notified of structural changes
*/
((IActiveSubject) subject).contextualize();
} catch (KlabException e) {
monitor.error(e);
status = ERROR;
return;
}
status = monitor.getTask().getStatus() == Status.INTERRUPTED ? INTERRUPTED
: CONTEXTUALIZED;
if (status == CONTEXTUALIZED) {
monitor.info("coverage from observation of " + subject.getName() + " is "
+ coverage.getCoverage(), Messages.INFOCLASS_MODEL);
}
}
}
/**
* @param session
* @param monitor
* @param observer
* @throws KlabException
*/
public Context(ISession session, IMonitor monitor, IDirectObserver observer)
throws KlabException {
this.name = observer.getId();
this.monitor = ((Monitor) monitor).get(this);
this.id = NameGenerator.shortUUID();
this.rootObserver = observer;
this.subject = (IActiveSubject) KLAB.MFACTORY
.createSubject(observer, this, null, monitor);
this.resolutionContext = ResolutionScope
.root((IActiveSubject) subject, null, monitor, null);
this.provenance = new Provenance(this);
this.monitor.send(this);
}
/**
* @param session
* @param monitor
* @param observerId
* @param forcings
* @throws KlabException
*/
public Context(ISession session, IMonitor monitor, Object observable,
Collection forcings)
throws KlabException {
/*
* TODO may be a concept, direct observer or other. Forcings may contain spatial
* context and drop location.
*/
if (observable instanceof IDirectObserver) {
this.name = Path.getLast(((IDirectObserver) observable).getId(), '.');
this.deferredObservableId = ((IDirectObserver) observable).getName();
} else {
this.name = "UserContext";
this.deferredObservable = observable;
this.deferredObservableId = getName(observable);
}
this.monitor = ((Monitor) monitor).get(this);
this.id = NameGenerator.shortUUID();
this.isDeferred = true;
this.forcings = (forcings == null || forcings.size() == 0) ? null : forcings;
this.provenance = new Provenance(this);
this.monitor.send(this);
}
private String getName(Object observable) {
if (observable instanceof IModelObject) {
return ((IModelObject) observable).getName();
}
return observable == null ? "(null)" : observable.toString();
}
public void resolveObservable(IMonitor monitor) throws KlabException {
/*
*
*/
KLAB.PMANAGER.load(false, KLAB.MFACTORY.getRootParsingContext());
IDirectObserver observer = null;
IConcept concept = null;
boolean isROIonly = false;
if (deferredObservable instanceof IDirectObserver) {
observer = (IDirectObserver) deferredObservable;
} else if (deferredObservable instanceof IConcept) {
concept = (IConcept) deferredObservable;
} else if (deferredObservableId != null || deferredObservable instanceof String) {
String tofind = deferredObservableId == null ? getName(deferredObservable)
: deferredObservableId;
isROIonly = (deferredObservable != null
&& deferredObservable.toString().equals("region-of-interest"));
IModelObject obs = KLAB.MMANAGER.findModelObject(tofind);
if (obs == null) {
concept = KLAB.KM.getConcept(tofind);
}
if (obs instanceof IDirectObserver) {
observer = (IDirectObserver) obs;
} else if (obs instanceof IModel) {
/* TODO */
} else if (obs instanceof IKnowledgeObject) {
concept = ((IKnowledgeObject) obs).getConcept();
} else if (concept == null && !isROIonly) {
throw new KlabValidationException("cannot find observable "
+ tofind);
}
}
if (observer != null) {
rootObserver = observer;
if (forcings != null) {
rootObserver = ModelFactory.forceScale(rootObserver, monitor, forcings);
}
initializeContextWith((IActiveSubject) KLAB.MFACTORY
.createSubject(rootObserver, this, null, this.monitor));
// addDelta(subject);
// resolutionContext = ResolutionScope
// .root((IActiveSubject) subject, null, monitor, null);
// ((Observation) subject).defineObservable();
// isDeferred = false;
} else if (concept != null) {
/*
* TODO
*/
if (subject == null) {
/*
* must have a ROI in the spatial forcing
*/
Pair roi = findROI(forcings);
if (roi.getFirst() == null) {
throw new KlabValidationException("cannot observe a concept without a region of interest or a context "
+ deferredObservableId);
} else {
locationOfInterest = roi.getSecond();
rootObserver = new KIMDirectObserver(GeoNS.PLAIN_REGION, "region-of-interest", new Scale(Collections
.singleton(new ShapeValue(roi.getFirst()).asExtent())));
rootObserver = ModelFactory.forceScale(rootObserver, monitor, forcings);
initializeContextWith((IActiveSubject) KLAB.MFACTORY
.createSubject(rootObserver, this, null, monitor));
coverage = ((Subject) this.subject)
.initialize(resolutionContext, null /* userAction */, monitor);
status = Context.INITIALIZED;
if (coverage.isEmpty()) {
monitor.error("initialization of implicit region of interest failed: empty coverage");
}
}
if (subject != null && !coverage.isEmpty() && !isROIonly) {
observeConcept(concept, monitor);
}
/*
* there may be a point too, which we can use as the intersection point to
* find compatible context observations.
*/
/*
* workflow of finding objects for a distributed observation must be in
* independent call as it can apply to all observe calls
*/
}
}
}
/**
* @throws KlabException
*/
public void resolveObservable() throws KlabException {
resolveObservable(this.monitor);
}
private void observeConcept(IConcept concept, IMonitor monitor) throws KlabException {
IConcept context = Observables.getContextType(concept);
IConcept inherent = Observables.getInherentType(concept);
boolean distributed = false;
/*
* if subject is incompatible with context, observe context and distribute concept
* over results.
*/
List subjects = new ArrayList<>();
if (context != null && !checkContextCompatibility(subject.getObservable().getType(), context)) {
distributed = true;
coverage = ((Subject) subject).observe(Task
.makeObservable(context), scenarioIds, false, /* userAction */ null, monitor);
for (IObservation o : getNewObservations()) {
if (o instanceof ISubject && o.getObservable().getType().is(context)) {
subjects.add((ISubject) o);
}
}
} else {
subjects.add(this.subject);
}
/*
* observe the concept in each of the resulting subjects
*/
for (ISubject s : subjects) {
coverage = ((Subject) s).observe(Task
.makeObservable(concept), scenarioIds, false, /* userAction */ null, monitor);
if (!coverage.isEmpty()) {
monitor.info("observation of " + NS.getDisplayName(concept) + " in "
+ subject.getName()
+ " covers " + NumberFormat.getPercentInstance()
.format(coverage.getCoverage()), Messages.INFOCLASS_MODEL);
} else {
monitor.warn("observation of " + NS.getDisplayName(concept) + " in "
+ subject.getName()
+ " is empty");
}
}
/*
* If observation of context produced only one result and result is subject, swap
* subject with context and move from there.
*/
if (distributed && subjects.size() == 1
&& subjects.get(0).getObservable().getType().is(this.subject.getObservable().getType())) {
/*
* TODO merge
*/
distributed = false;
}
/*
* If concept was a quality and distribution happened, add a state to the main
* subject with the merge of the quality inherently to the distributing subjects.
*/
if (NS.isQuality(concept) && distributed) {
}
}
private boolean checkContextCompatibility(IConcept type, IConcept context) {
// FIXME this is the proper check, as long as isCompatible can go and observe
// missing
// traits (such as whether a region is terrestrial) by running a classifier:
// return Observables.isCompatible(subject.getType(), context);
// weak check to use until we get smarter. For now assume users aren't monkeys and
// can make some calls themselves, not observing watersheds in the open sea etc.
IConcept coretype = Observables.getCoreObservable(type);
IConcept corecont = Observables.getCoreObservable(context);
return coretype.is(corecont);
}
private void initializeContextWith(IActiveSubject subject) throws KlabException {
this.subject = subject;
addDelta(subject);
resolutionContext = ResolutionScope
.root(subject, null, monitor, null);
((Observation) subject).defineObservable();
isDeferred = false;
}
private Pair findROI(Collection forcings) {
ReferencedEnvelope ret = null;
double[] loi = null;
for (IExtent e : forcings) {
if (e instanceof ISpatialExtent) {
if (!Double.isNaN(((ISpatialExtent) e).getMinX())) {
ret = new ReferencedEnvelope(((ISpatialExtent) e)
.getMinX(), ((ISpatialExtent) e)
.getMaxX(), ((ISpatialExtent) e)
.getMinY(), ((ISpatialExtent) e)
.getMaxY(), Geospace.get()
.getDefaultCRS());
if (e instanceof Space) {
loi = ((Space) e).getLocationOfInterest();
}
}
break;
}
}
return new Pair<>(ret, loi);
}
/**
* @param c
*/
public Context(Context c) {
/**
* TODO some of these probably must be deep copies - particularly the observer.
*/
super(c);
this.name = c.name;
this.coverage = c.coverage;
this.monitor = ((Monitor) c.monitor).get(this);
this.status = c.status;
this.currentTime = c.currentTime;
this.rootObserver = c.rootObserver;
this.resolutionContext = c.resolutionContext;
this.breakpoints.addAll(c.breakpoints);
this.isDeferred = c.isDeferred;
this.deferredObservableId = c.deferredObservableId;
this.locationOfInterest = c.locationOfInterest;
}
private boolean isPaused() {
synchronized (paused) {
return paused;
}
}
/**
* @param status
*/
public void pause(boolean status) {
synchronized (paused) {
paused = status;
}
}
IMonitor getMonitor() {
return monitor;
}
/**
* Use to create the main subject or a single contextual observation in an existing
* one.
*
* @return the running task that is creating the subject.
*/
public ITask observeAsynchronous() {
Task ret = null;
ret = observeInternal();
ret.start();
// FIXME move to task
timeOfLastChange = new Date().getTime();
return ret;
}
/**
* @param observable
* @param targetSubject
* @return task observing subject within target subject.
* @throws KlabValidationException
*/
public ITask observeAsynchronous(Object observable, ISubject targetSubject)
throws KlabValidationException {
newObservations.clear();
Task ret = null;
ret = observeInternal(observable, targetSubject);
ret.start();
// FIXME move to task
timeOfLastChange = new Date().getTime();
return ret;
}
/**
* Call after critical operations to decide whether to continue.
*
* @return true if errors
*/
public boolean hasErrors() {
return monitor.hasErrors();
}
@Override
public Collection getNewObservations() {
Collection ret = new HashSet<>(newObservations);
newObservations.clear();
return ret;
}
public Context withScenario(Object... scenarios) throws KlabValidationException {
scenarioIds.clear();
for (Object o : scenarios) {
if (o instanceof String) {
scenarioIds.add((String) o);
} else if (o instanceof INamespace && ((INamespace) o).isScenario()) {
scenarioIds.add(((INamespace) o).getId());
} else {
throw new KlabValidationException("cannot use " + o + " as a scenario");
}
}
return this;
}
/**
* Observe anything either in the current context or as a main observation. Pass only
* one subject generator (or its ID), or however many things to observe in the current
* subject; if doing the latter, you should call this indirectly with the with()
* semantics.
*
* @return
*/
private Task observeInternal() {
return new Task(this, monitor, scenarioIds);
}
private Task observeInternal(Object observable, ISubject target) {
return new Task(this, monitor, scenarioIds, observable, target);
}
public static String describeObservable(Object observable) {
if (observable instanceof IModel) {
return "model " + ((IModel) observable).getName();
} else if (observable instanceof IConcept) {
return "concept " + observable;
} else if (observable instanceof IDirectObserver) {
return "object " + ((IDirectObserver) observable).getName();
} else if (observable instanceof IObservableSemantics) {
return ((IObservableSemantics) observable).getFormalName();
}
return observable.toString();
}
public List getScenarios() {
return scenarioIds;
}
public TemporalCausalGraph getCausalGraph() {
return ((Subject) subject).getCausalGraph();
}
public IState getState(Object o) {
/**
* TODO allow to search state by observer, model or anything else useful. TODO
* ensure things work properly if called during run - must return most recent
* state without conflicts.
*/
IConcept c = o instanceof IConcept ? (IConcept) o : KLAB.c(o.toString());
for (IState s : subject.getStates()) {
if (s.getObservable().getSemantics().is(c))
return s;
}
return null;
}
public static Context create(IDirectObserver sg, ISession session, IMonitor monitor)
throws KlabException {
return new Context(session, monitor, sg);
}
/**
* Create a context that will defer resolution of the observable to the task started
* by observeAsynchronous().
*
* @param sg the subject observer ID. The project manager will be refreshed before the
* name is resolved.
* @param session
* @param forcings
* @return deferring context
* @throws KlabException
*/
public static Context createDeferred(Object observable, ISession session, Collection forcings)
throws KlabException {
return new Context(session, ((Session) session)
.getMonitor(), observable, forcings);
}
@Override
public boolean isEmpty() {
return status == EMPTY;
}
@Override
public boolean isFinished() {
return status == CONTEXTUALIZED;
}
@Override
public boolean isRunning() {
return status == RUNNING;
}
@Override
public IContext inScenario(String... scenarios) {
if (scenarios == null || scenarios.length == 0) {
return this;
}
// Context ret = new Context(this);
for (String s : scenarios) {
scenarioIds.add(s);
// ret.scenarioIds.add(s);
}
// return ret;
return this;
}
@Override
public ITask run() {
Task ret = Task.newTemporalTransitionTask(this, monitor);
ret.start();
return ret;
}
/**
* @return timeslice we're currently on.
*/
public int getCurrentTimeIndex() {
return currentTime;
}
/**
* @return timestamp of last change.
*/
public long getLastChangeTimestamp() {
return timeOfLastChange;
}
/**
* Return the observation indexed by path, either a IState or ISubject. If not found
* return null with no error.
*
* @param path
* @return observation for given path.
*/
@Override
public IObservation get(String path) {
if (path.startsWith("G|")) {
/*
* make folder and return it
*/
String[] def = path.split("\\|");
if (def.length != 3) {
throw new KlabRuntimeException("internal: wrong folder ID: " + path);
}
ISubject parent = (ISubject) get(def[1]);
IConcept type = null;
try {
type = Definition.parse(def[2]).reconstruct(KLAB.REASONER.getOntology());
} catch (KlabValidationException e) {
throw new KlabRuntimeException(e);
}
if (parent == null || type == null) {
return null;
}
ObservationGroup ret = new ObservationGroup(type, parent);
ret.collectObservations();
return ret;
}
return subject == null ? null : ((Subject) subject).get(path);
}
// @Override
public IContext observe() {
if (isEmpty()) {
return this;
}
ITask task = observeAsynchronous();
return task.finish();
}
@Override
public void persist(File file, String path, Object... options) throws KlabException {
if (path != null) {
IObservation obs = this.get(path);
VisualizationFactory.get().persist(obs, file, options);
}
}
public IDirectObserver getRootSubjectGenerator() {
return rootObserver;
}
public void setBreakpoint(IObservation obs) {
if (obs instanceof State) {
breakpoints.add(((State) obs).getInternalId());
}
}
public boolean breakpointReached(Collection modifiedObservations) {
if (breakpoints.size() > 0) {
for (IObservation o : modifiedObservations) {
if (o instanceof State
&& breakpoints.contains(((State) o).getInternalId())) {
return true;
}
}
}
return false;
}
public void waitForResume() {
pause(true);
for (;;) {
if (!isPaused()) {
return;
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// nah
}
}
}
/**
* Record a new observation made in this context. Deltas always go in the root
* context.
*
* @param observation
*/
public void addDelta(IObservation observation) {
newObservations.add(observation);
getRootContext().deltas.add(observation);
}
private Context getRootContext() {
if (parent == null) {
return this;
}
return ((Context) parent).getRootContext();
}
@SuppressWarnings("unchecked")
@Override
public T serialize(Class desiredClass) {
if (!desiredClass
.isAssignableFrom(org.integratedmodelling.common.beans.Context.class)) {
throw new KlabRuntimeException("cannot serialize a Context to a "
+ desiredClass.getCanonicalName());
}
org.integratedmodelling.common.beans.Context ret = new org.integratedmodelling.common.beans.Context();
ret.setId(id);
ret.setFinished(this.isFinished);
// ret.setSubject(KLAB.MFACTORY.adapt(subject,
// org.integratedmodelling.common.beans.Subject.class));
if (coverage != null) {
ret.setCoverage(coverage.getCoverage());
}
KLAB.info("got " + deltas.size() + " deltas to send");
/*
* send along any new observation. Must send all direct observations first, so
* that the states are certain to have a defined parent when they reach the
* client.
*/
for (IObservation obs : deltas) {
if (obs instanceof ISubject) {
ret.addSubject(KLAB.MFACTORY
.adapt(obs, org.integratedmodelling.common.beans.Subject.class));
} else if (obs instanceof IState) {
ret.addState(KLAB.MFACTORY
.adapt(obs, org.integratedmodelling.common.beans.State.class));
} else if (obs instanceof IEvent) {
ret.addEvent(KLAB.MFACTORY
.adapt(obs, org.integratedmodelling.common.beans.Event.class));
} else if (obs instanceof IProcess) {
ret.addProcess(KLAB.MFACTORY
.adapt(obs, org.integratedmodelling.common.beans.Process.class));
} else if (obs instanceof IRelationship) {
ret.addRelationship(KLAB.MFACTORY
.adapt(obs, org.integratedmodelling.common.beans.Relationship.class));
}
}
deltas.clear();
return (T) ret;
}
/**
*
* @return true if any new observation has been added since the last serialization.
*/
public boolean hasDeltas() {
return deltas.size() > 0;
}
@Override
public IContext focus(ISubject observation) {
if (subject.equals(observation)) {
return this;
}
Context ret = new Context(this);
ret.subject = observation;
((DirectObservation) observation).setContext(ret);
return ret;
}
@Override
public ITask observe(Object observable) throws KlabException {
return observeAsynchronous(observable, subject);
}
@Override
public ISession getSession() {
return monitor.getSession();
}
@Override
public ITransition getLastTemporalTransition() {
// TODO Auto-generated method stub
return null;
}
@Override
public IProvenance getProvenance() {
return provenance;
}
public void resetScenarios() {
scenarioIds.clear();
if (subject != null) {
((Subject) subject).resetExplicitRoles();
}
}
@Override
public IResolutionScope getScope() {
return resolutionContext;
}
}