org.integratedmodelling.engine.modelling.resolver.Resolver 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.resolver;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IExpression;
import org.integratedmodelling.api.knowledge.ISemantic;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.IConditionalObserver;
import org.integratedmodelling.api.modelling.ICoverage;
import org.integratedmodelling.api.modelling.IDependency;
import org.integratedmodelling.api.modelling.IDerivedObserver;
import org.integratedmodelling.api.modelling.IDirectObservation;
import org.integratedmodelling.api.modelling.IMediatingObserver;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.IObjectSource;
import org.integratedmodelling.api.modelling.IObservableSemantics;
import org.integratedmodelling.api.modelling.IObserver;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.ISubject;
import org.integratedmodelling.api.modelling.contextualization.IContextualizer;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.modelling.resolution.ISubjectResolver;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.monitoring.Messages;
import org.integratedmodelling.api.provenance.IProvenance;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.kim.KIMModel;
import org.integratedmodelling.common.kim.KIMObserver;
import org.integratedmodelling.common.model.Coverage;
import org.integratedmodelling.common.states.States;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.common.vocabulary.ObservableSemantics;
import org.integratedmodelling.engine.modelling.kbox.ModelKbox;
import org.integratedmodelling.engine.modelling.runtime.DirectObservation;
import org.integratedmodelling.engine.modelling.runtime.DirectSubjectInstantiator;
import org.integratedmodelling.engine.modelling.runtime.Subject;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.lang.LogicalConnector;
/**
* A resolver has a top-level resolution context, set to ROOT if it's a top resolver, or
* to the context that resolved the subject if it's called within one.
*
* @author Ferd
*
*/
public class Resolver extends BaseResolver implements ISubjectResolver {
ResolutionScope context;
Collection scenarios = null;
IProvenance.Action cause;
public Resolver(IResolutionScope context, IProvenance.Action cause) {
this.context = (ResolutionScope) context;
this.cause = cause;
}
/*
* this constructor overrides the scenarios in the original context.
*/
public Resolver(IResolutionScope context, Collection scenarios, IProvenance.Action cause) {
this.context = (ResolutionScope) context;
if (scenarios != null) {
this.scenarios = scenarios;
}
this.cause = cause;
}
/*
* Entry point when a subject is initialized from a context subject's resolution
* strategy. (non-Javadoc)
*
* @see org.integratedmodelling.api.modelling.resolution.ISubjectResolver#resolve
* (org. integratedmodelling.api.modelling.ISubject,
* org.integratedmodelling.api.monitoring.IMonitor)
*/
@Override
public ICoverage resolve(IActiveDirectObservation subject, IMonitor monitor) throws KlabException {
ResolutionScope ctx = context.forSubject(subject, scenarios);
ctx.setMonitor(monitor);
ctx.setCause(cause);
((DirectObservation) subject).setResolutionContext(ctx);
ICoverage ret = resolve(subject, ctx);
// if (ret != null) {
// ctx.execute(null);
// }
return ret;
}
/*
* Entry point when an observable is resolved from a subject's observe().
* (non-Javadoc)
*
* @see org.integratedmodelling.api.modelling.resolution.ISubjectResolver#resolve
* (org. integratedmodelling.api.modelling.IObservable,
* org.integratedmodelling.api.monitoring.IMonitor)
*/
@Override
public ICoverage resolve(IObservableSemantics observable, IMonitor monitor, IActiveSubject contextSubject)
throws KlabException {
ResolutionScope ctx = context.forObservable(observable, scenarios);
if (NS.isThing(observable)) {
ctx.contextSubject = contextSubject;
}
ctx.setMonitor(monitor);
ctx.setCause(cause);
ICoverage ret = resolve(observable, ctx);
if (ret.isRelevant()) {
ctx.finish();
}
return ret;
}
@Override
public IConcept resolve(IConcept abstractConcept, IActiveDirectObservation subject) throws KlabException {
// TODO Auto-generated method stub
return abstractConcept;
}
private ICoverage resolve(IModel model, ResolutionScope context) throws KlabException {
if (context.isUnsatisfiable)
return Coverage.EMPTY;
if (context.monitor.hasErrors()) {
return Coverage.EMPTY;
}
if (model.getObserver() != null) {
if (resolve(model.getObserver(), context
.forObserver(model.getObserver(), getDatasource(model, context.monitor))).isEmpty()) {
return Coverage.EMPTY;
}
}
/*
* resolve all model dependencies, contextualizing at every group if the model is
* a subject model. This will call finish() on the context passed.
*/
ICoverage ret = resolveDependencies(model, context, model.getNamespace());
if (ret.isEmpty())
return ret;
IObjectSource objectSource = model.getObjectSource(context.getMonitor());
if (objectSource != null) {
context.forObjectSource(objectSource).finish();
}
if (!context.monitor.hasErrors()) {
context.acceptModel();
}
return context.finish();
}
/*
* resolves dependencies for a model, subject or observer, also ensuring that any
* semantic-induced dependencies are in.
*/
private ICoverage resolveDependencies(ISemantic observation, ResolutionScope context, INamespace namespace)
throws KlabException {
if (context.isUnsatisfiable)
return Coverage.EMPTY;
ICoverage ret = Coverage.FULL(context.getScale());
for (List dg : groupDependencies(observation, context)) {
ResolutionScope cd = context.forGroup(LogicalConnector.INTERSECTION);
ret = cd.getCoverage();
for (IDependency d : dg) {
if (resolve(d, cd.forDependency(d, namespace)).isEmpty()) {
return Coverage.EMPTY;
}
}
ret = cd.finish();
}
return ret;
}
private ICoverage resolve(IObserver observer, ResolutionScope context) throws KlabException {
if (context.isUnsatisfiable)
return Coverage.EMPTY;
if (observer instanceof IMediatingObserver
&& ((IMediatingObserver) observer).getMediatedObserver() != null) {
if (resolve(((IMediatingObserver) observer).getMediatedObserver(), context
.forMediatedObserver(((IMediatingObserver) observer).getMediatedObserver())).isEmpty()) {
return Coverage.EMPTY;
}
} else if (observer instanceof IConditionalObserver) {
int i = 0;
ResolutionScope cd = context.forGroup(LogicalConnector.UNION);
for (Pair observedModelPair : ((IConditionalObserver) observer)
.getModels()) {
IModel model = observedModelPair.getFirst();
IExpression expression = observedModelPair.getSecond();
resolve(model, cd.forCondition(expression, i++));
}
cd.finish();
/*
* model dependencies are only relevant when we have conditional observers; we
* link them to the observer. FIXME: see
* https://integratedmodelling.org/jira/browse/KLAB-100
*/
IResolutionScope dc = context.forGroup(LogicalConnector.UNION);
for (IDependency d : ((KIMModel) context.model)
.getAllDependencies(context.getMonitor(), context)) {
if (resolve(d, ((ResolutionScope) dc).forDependency(d, observer.getModel().getNamespace()))
.isEmpty())
break;
}
((ResolutionScope) dc).finish();
} else if (context.datasource != null) {
if (context.forDatasource(context.datasource).finish().isEmpty()) {
return Coverage.EMPTY;
}
} else if (observer.needsResolution()) {
/*
* resolve the observer using the k.LAB network.
*/
IObservableSemantics observable = observer.getObservable();
if (resolve(observable, context.forObservable(observable)).isEmpty()) {
if (observer instanceof IDerivedObserver) {
/*
* try the indirect way.
*/
ICoverage indirectCoverage = resolveIndirect((IDerivedObserver) observer, context);
if (indirectCoverage.isEmpty()) {
return Coverage.EMPTY;
}
context.coverage = indirectCoverage;
} else {
return Coverage.EMPTY;
}
}
}
List alldeps = ((KIMObserver) observer)
.getAllDependencies(context.getMonitor(), context);
if (alldeps.size() > 0) {
/*
* FIXME/TODO: UNION is necessary to capture partial coverages, but it should
* be for COUNTABLE INHERENTS, which means separate groups should be defined
* for those. Qualities should use INTERSECTION.
* https://integratedmodelling.org/jira/browse/KLAB-100
*/
IResolutionScope dc = context.forGroup(LogicalConnector.UNION);
for (IDependency d : alldeps) {
if (resolve(d, ((ResolutionScope) dc).forDependency(d, observer.getNamespace())).isEmpty())
break;
}
((ResolutionScope) dc).finish();
}
return context.finish();
}
private ICoverage resolveIndirect(IDerivedObserver observer, ResolutionScope context)
throws KlabException {
ICoverage cov = Coverage.EMPTY;
int i = 0;
for (IObservableSemantics observable : observer.getAlternativeObservables()) {
context.monitor.info(((i == 0)
? ("no direct observations of " + observer.getObservable().getType() + " found :") : "")
+ "resolving " + observable.getType(), Messages.INFOCLASS_MODEL);
ResolutionScope childScope = context.forIndirectObservable(observable, observer);
ICoverage ocov = resolve(observable, childScope);
if (ocov.isRelevant()) {
IContextualizer contextualizer = childScope.observer == null
? (childScope.model == null ? null
: childScope.model.getContextualizer(childScope, context.monitor))
: childScope.observer.getContextualizer(childScope, context.monitor);
if (contextualizer == null) {
continue;
}
cov = cov.or(ocov);
if (NS.isThing(observable)) {
((Coverage)cov).forceRelevant();
}
ICoverage acov = observer.acceptAlternativeContextualizer(contextualizer, childScope.coverage);
if (acov.isComplete()) {
cov = acov;
}
}
i++;
}
return cov;
}
private ICoverage resolve(IDirectObservation subject, ResolutionScope context) throws KlabException {
if (context.isUnsatisfiable)
return Coverage.EMPTY;
if (!((DirectObservation) subject).isInitialized()) {
ICoverage cov = resolve(subject.getObservable().getSemantics(), context
.forObservable(subject.getObservable().getSemantics())
.forExplanatoryModel());
if (cov.isEmpty()) {
context.getMonitor().error("cannot resolve subject " + subject.getName());
return cov;
}
}
ICoverage ret = resolveDependencies(subject, context, subject.getNamespace());
if (ret.isEmpty())
return ret;
return context.finish();
}
private ICoverage resolve(IDependency dependency, ResolutionScope context) throws KlabException {
if (context.isUnsatisfiable)
return Coverage.EMPTY;
IObservableSemantics observable = dependency.getObservable();
ICoverage cov = null;
if (dependency.getContextModel() != null) {
// TODO - resolve the context model, then set the result in the
// context for
// the
// following resolution. The next step will necessarily be an object
// resolution.
}
if (observable.getModel() != null) {
cov = resolve(observable.getModel(), context.forModel(observable.getModel()));
} else {
cov = resolve(observable, context.forObservable(observable));
}
if (cov.isEmpty() && !context.isOptional()) {
context.getMonitor().error("mandatory dependency on " + NS.getDisplayName(observable.getType())
+ " is unresolved");
context.coverage = Coverage.EMPTY;
}
/*
* record dependency so that even optional outputs can be made mandatory.
* Dependencies that declare an internal concept record the observer's observable
* instead (deps can't do much with it, so the top concept is usually just a
* placeholder).
*/
if (!cov.isEmpty()) {
if (observable.getModel() != null && observable.getObserver() != null) {
context.requireOutput(observable.getModel().getObserver().getObservable());
} else {
context.requireOutput(observable);
}
}
return context.finish();
}
/*
* this will be passed a context.forObservable(), which automatically only merges in
* models that cover enough more context to be relevant, and merges them into one
* conditional model at finish().
*/
private ICoverage resolve(IObservableSemantics observable, ResolutionScope context) throws KlabException {
if (context.isUnsatisfiable) {
return Coverage.EMPTY;
}
ICoverage cov = Coverage.EMPTY;
if (observable.getModel() != null) {
cov = resolve(observable.getModel(), context.forModel(observable.getModel()));
} else {
IModel previous = context.getModelFor(observable, !context.isExplanatoryModel);
if (previous != null) {
/*
* TODO we must not re-accept this, but tell the workflow to use the
* results of its previous run.
*/
cov = resolve(previous, context.forModel(previous));
if (cov != null) {
cov = context.finish();
}
} else {
/*
* if we're resolving a subject, coverage is full if we don't find any
* model. Any other situation, including processes and events, must have a
* model.
*/
cov = (NS.isThing(observable) && context.isExplanatoryModel)
? Coverage.FULL(context.getScale())
: Coverage.EMPTY;
/*
* lookup model for this observable. ModelKbox will do all the ranking and
* sorting as long as its own iterator is used.
*/
boolean found = false;
/*
* we go through search only if we have not already seen the observable in
* a context where we have recorded that we can proceed with no model.
*/
if (!(context.isExplanatoryModel && context.hasNoModel(observable))) {
/*
* lookup state for same model in parent contexts. If found, create a
* state view, which may (some day) be intelligent enough to make
* further observations to adapt to different scales, and just resolve
* a state model using it.
*/
if (context.contextSubject != null
&& (NS.isQuality(observable) || NS.isTrait(observable))) {
// ensure we have one. Will not create NetCDFs unless
// there is
// raster space.
((Subject) context.subject).requireBackingDataset();
IState prevobs = States
.findView(context.contextSubject, observable, (IActiveSubject) context.subject);
if (prevobs != null) {
StateModel sm = new StateModel(observable, prevobs, context
.getResolutionNamespace());
cov = resolve(sm, context.forModel(sm));
if (cov.isComplete()) {
return finish(observable, context);
}
}
}
/*
* found or not, we want to also check the database and network for
* subjects if the observable is a subject. This will produce a custom
* instantiator that will be given back to the model to decide what to
* do with.
*/
if (NS.isThing(observable)
&& (!context.isExplanatoryModel
|| ((ObservableSemantics) observable).isInstantiator())) {
List observed = this.lookupSubjects(observable.getType(), context);
if (observed.size() > 0) {
IContextualizer instantiator = new DirectSubjectInstantiator(observed, context.monitor);
if (context.notifyContextualizer(instantiator)) {
context.monitor.info(observed.size()
+ " observations found on the network: suspending search for instantiators", Messages.INFOCLASS_DOWNLOAD);
return proceed(observable, context);
}
context.monitor
.info("observations found on the network. Continuing resolution.", Messages.INFOCLASS_DOWNLOAD);
// boolean stopResolving = true;
// if (context.model != null) {
// stopResolving = ((KIMModel)
// context.model).acceptContextualizer(instantiator);
// } else {
// IModel subjectModel = new KIMModel(observable,
// instantiator, context);
// cov = resolve(subjectModel,
// context.forModel(subjectModel));
// context.modelGraph.add(context.node);
// }
//
// if (stopResolving) {
// context.monitor.info(observed.size()
// + " observations found on the network: suspending search
// for instantiators", Messages.INFOCLASS_DOWNLOAD);
// return finish(observable, context);
// } else {
// context.monitor
// .info("observations found on the network. Continuing
// resolution.", Messages.INFOCLASS_DOWNLOAD);
// }
}
}
/*
* with abstract countable observables, we explore the whole model
* space and collect any different subtype instantiator until all
* represented types have complete coverage or there are no more
* models.
*/
boolean isAbstractCountable = context.isGeneric()
|| (observable.getType().isAbstract() && NS.isCountable(observable) && context.isForInstantiation());
Map, ICoverage>> modelCache = new HashMap<>();
for (IModel m : ModelKbox.get().query(observable, context)) {
if (m == null) {
continue;
}
found = true;
if (context.isResolving(m)) {
continue;
}
if (!m.isAvailable()) {
context.monitor.warn("model " + m.getName() + " is unavailable");
continue;
}
/*
* TODO if this is an abstract countable, we need to keep
* accumulating models for different subclasses even if coverage
* is complete. Also we should accumulate models with the
* "archetype" flag, which don't provide complete observations.
*/
ICoverage modCov = resolve(m, context.forModel(m));
cov = cov.or(modCov);
if (isAbstractCountable) {
/*
* coverage is always relevant; compute coverage for each
* individual sub-concept
*/
if (modelCache.containsKey(m.getObservable().getType())) {
Pair, ICoverage> mdata = modelCache
.get(m.getObservable().getType());
if (!mdata.getSecond().isComplete()) {
mdata.getFirst().add(m);
mdata.setSecond(mdata.getSecond().or(modCov));
}
} else {
modelCache.put(m.getObservable()
.getType(), new Pair<>(Collections.singletonList(m), modCov));
}
}
if (cov.isComplete() && !isAbstractCountable) {
return finish(observable, context);
}
}
if (!modelCache.isEmpty()) {
/*
* report on types and coverages
*/
context.monitor.info("query for abstract " + NS.getDisplayName(observable.getType())
+ " produced:", Messages.INFOCLASS_NETWORK);
for (IConcept c : modelCache.keySet()) {
Pair, ICoverage> mdata = modelCache.get(c);
context.monitor.info(" " + NS.getDisplayName(c) + " (" + mdata.getFirst().size()
+ " models, "
+ NumberFormat.getCurrencyInstance()
.format(mdata.getSecond().getCoverage())
+ "%)", Messages.INFOCLASS_NETWORK);
}
/*
* TODO create merging instantiator and finish context
*/
context.getMonitor().warn("MODELS NOT USED - MERGING BEING IMPLEMENTED");
}
if (!cov.isEmpty() && !cov.isComplete()) {
if (cov.isRelevant()) {
context.monitor
.warn("incomplete context coverage: total coverage for "
+ observable.getType()
+ " is "
+ NumberFormat.getCurrencyInstance().format(cov.getCoverage()));
return finish(observable, context);
} else {
return fail(observable, context);
}
}
/*
* if we have not found a model but we're continuing, record the
* incident so we don't have to query again for the next. We record
* this in the parent or it won't be transferred, as finish() is only
* called when we have a complete context.
*/
if (!found && !cov.isEmpty()) {
context.parent.proceedWithNoModel(observable);
return proceed(observable, context);
}
} else if (context.hasNoModel(observable)) {
return proceed(observable, context);
}
}
}
return finish(observable, context);
}
private ICoverage proceed(IObservableSemantics observable, ResolutionScope scope) throws KlabException {
ICoverage ret = Coverage.FULL(scope.scale);
((Subject) scope.subject).addPreviousObservation(observable, ret);
scope.coverage = ret;
scope.finish();
return ret;
}
private ICoverage finish(IObservableSemantics observable, ResolutionScope scope) throws KlabException {
ICoverage ret = scope.finish();
((Subject) scope.subject).addPreviousObservation(observable, ret);
return ret;
}
private ICoverage fail(IObservableSemantics observable, ResolutionScope scope) {
ICoverage ret = Coverage.EMPTY;
((Subject) scope.subject).addPreviousObservation(observable, ret);
return ret;
}
}