Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.integratedmodelling.engine.modelling.resolver.ModelPrioritizer Maven / Gradle / Ivy
/*******************************************************************************
* 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.collections.comparators.ComparatorChain;
import org.apache.commons.lang.ObjectUtils;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IKnowledge;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.metadata.IModelMetadata;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.resolution.IModelPrioritizer;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.beans.Model;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.metadata.Metadata;
import org.integratedmodelling.common.space.IGeometricShape;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.common.vocabulary.Traits;
import com.vividsolutions.jts.geom.Geometry;
/**
* Implements the multi-criteria algorithm to prioritize models according to the strategy
* defined for the namespace.All criteria return doubles between 0 and 100, if necessary
* imposing (and checking) reasonable limits on the scope of the models expected.
*
* @author Ferd
*
*/
public class ModelPrioritizer implements IModelPrioritizer {
ResolutionScope _context;
ComparatorChain _comparator = null;
HashMap> _ranks = new HashMap>();
HashMap _idxss = new HashMap();
List _criteriaOrder = new ArrayList();
List _subjectiveCriteria = new ArrayList();
private static IMetadata defaultStrategy = null;
private static HashSet rankingCriteria = new HashSet();
static {
rankingCriteria.add(NS.LEXICAL_SCOPE);
rankingCriteria.add(NS.TRAIT_CONCORDANCE);
rankingCriteria.add(NS.SCALE_COVERAGE);
rankingCriteria.add(NS.SCALE_SPECIFICITY);
rankingCriteria.add(NS.INHERENCY);
rankingCriteria.add(NS.EVIDENCE);
rankingCriteria.add(NS.NETWORK_REMOTENESS);
rankingCriteria.add(NS.SCALE_COHERENCY);
rankingCriteria.add(NS.SUBJECTIVE_CONCORDANCE);
}
class FieldComparator implements Comparator> {
String _field;
FieldComparator(String field) {
_field = field;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public int compare(Map o1, Map o2) {
Comparable n1 = (Comparable) o1.get(_field);
Comparable n2 = (Comparable) o2.get(_field);
/*
* comparator is icky if it gets an int and a double.
*/
if (n1 instanceof Number) {
n1 = ((Number) n1).doubleValue();
}
if (n2 instanceof Number) {
n2 = ((Number) n2).doubleValue();
}
// TODO restore the null acceptance after testing. Should not get nulls.
return ObjectUtils.compare(n1, n2/* , false */);
}
}
public ModelPrioritizer(IResolutionScope context) {
_context = (ResolutionScope) context;
buildComparisonStrategy();
}
@Override
public int compare(IModelMetadata o1, IModelMetadata o2) {
return _comparator.compare(getRanks(o1), getRanks(o2));
}
@Override
public Map getRanks(IModelMetadata md) {
if (!_ranks.containsKey(md)) {
_ranks.put((Model) md, computeCriteria(md, _context));
}
return _ranks.get(md);
}
@SuppressWarnings("unchecked")
private void buildComparisonStrategy() {
IMetadata strategy = _context.getResolutionNamespace() == null ? null
: _context.getResolutionNamespace()
.getResolutionCriteria();
if (strategy == null) {
strategy = getDefaultRankingStrategy();
}
/*
* Resolution strategy contains the ordering of sequentially compared criteria and
* any subjective traits we want to rank. This scans the strategy and builds the
* comparator using the specified ordering, plus the definition of the aggregated
* subjective ranking function.
*/
ArrayList criteria = new ArrayList();
for (String s : strategy.getKeys()) {
if (rankingCriteria.contains(s)) {
criteria.add(s);
} else {
_subjectiveCriteria.add(s);
}
}
/*
* REDO - 1. scan once using a int->string map
*/
final BidiMap tmp = new DualHashBidiMap();
for (String c : criteria) {
int ord = strategy.getInt(c);
if (ord == 0)
continue;
if (tmp.get(ord) == null) {
tmp.put(ord, c);
} else {
tmp.put(ord, tmp.get(ord) + "," + c);
}
}
/*
* 2. sort
*/
_criteriaOrder.addAll(tmp.values());
Collections.sort(_criteriaOrder, new Comparator() {
@Override
public int compare(String arg0, String arg1) {
Integer r0 = (Integer) tmp.getKey(arg0);
return r0.compareTo((Integer) tmp.getKey(arg1));
}
});
/*
* build comparator for the desired criteria
*/
_comparator = new ComparatorChain();
for (String cr : _criteriaOrder) {
_comparator.addComparator(new FieldComparator(cr), true);
}
}
private IMetadata getDefaultRankingStrategy() {
/*
* use a default ordering that may be overridden in thinklab.properties.
*/
if (defaultStrategy == null) {
defaultStrategy = new Metadata();
String dstrat = KLAB.CONFIG.getProperties()
.getProperty(DEFAULT_STRATEGY_PROPERTY_NAME, DEFAULT_RANKING_STRATEGY);
String[] ranking = dstrat.split("\\s+");
for (int i = 0; i < ranking.length; i++) {
defaultStrategy.put(ranking[i], Integer.parseInt(ranking[++i]));
}
}
return defaultStrategy;
}
/**
* Compute the standard criterion identified by cr. For aggregated criteria, use
*
* @param cr
* @param model
* @param context
* @return criterion value
*/
public double computeStandardCriterion(String cr, Model model, IResolutionScope context) {
if (cr.equals(NS.EVIDENCE)) {
return computeEvidence(model, context);
} else if (cr.equals(NS.SUBJECTIVE_CONCORDANCE)) {
return computeSubjectiveConcordance(model, context, _subjectiveCriteria);
} else if (cr.equals(NS.SCALE_COVERAGE)) {
return computeScaleCoverage(model, context);
} else if (cr.equals(NS.SCALE_COHERENCY)) {
return computeScaleCoherency(model, context);
} else if (cr.equals(NS.INHERENCY)) {
return computeInherency(model, context);
} else if (cr.equals(NS.NETWORK_REMOTENESS)) {
return computeNetworkRemoteness(model, context);
} else if (cr.equals(NS.SCALE_SPECIFICITY)) {
return computeScaleSpecificity(model, context);
} else if (cr.equals(NS.TRAIT_CONCORDANCE)) {
return computeTraitConcordance(model, context);
} else if (cr.equals(NS.LEXICAL_SCOPE)) {
return computeLexicalScope(model, context);
}
return 0;
}
@Override
public Map computeCriteria(IModelMetadata model, IResolutionScope context) {
Map ret = new HashMap();
for (String cr : _criteriaOrder) {
if (cr.contains(",")) {
ret.put(cr, computeCustomAggregation(cr, (Model) model, context));
} else {
ret.put(cr, computeStandardCriterion(cr, (Model) model, context));
}
}
ret.put(IModelPrioritizer.OBJECT_NAME, ((Model) model).getName());
ret.put(IModelPrioritizer.PROJECT_NAME, ((Model) model).getProjectUrn());
ret.put(IModelPrioritizer.NAMESPACE_ID, ((Model) model).getNamespaceId());
ret.put(IModelPrioritizer.SERVER_ID, ((Model) model).getServerId());
_ranks.put((Model) model, ret);
return ret;
}
/*
* Compute a customized aggregation of two or more standard criteria. Aggregation is
* equally weighted. These criteria have been given the same order in the strategy
* specifications.
*/
private double computeCustomAggregation(String def, Model model, IResolutionScope context) {
String[] ddef = def.split(",");
ArrayList> vals = new ArrayList>();
Map dt = getRanks(model);
for (String cr : ddef) {
vals.add(new Pair(dt.containsKey(cr) ? ((Number) (dt.get(cr))).intValue()
: 50, 100));
}
return aggregate(vals);
}
/*
* lexical scope -> locality wrt context 100 = in observation scenario 75 = in same
* namespace as context 50-26 closer to same namespace as context 25 = in same project
* as context 0 = non-private in other visible namespace
*/
public static double computeLexicalScope(Model model, IResolutionScope context) {
// Scenarios always win.
// second check should not really be necessary, as the query should only have
// gotten
// the relevant scenarios or none.
if (model.isInScenario() && context.getScenarios().contains(model.getNamespaceId())) {
return 100;
}
INamespace ns = KLAB.MMANAGER.getNamespace(model.getNamespaceId());
if (ns == null) {
KLAB.warn("found model " + model.getName() + " referencing unknown namespace: ignoring");
return 0;
}
INamespace rns = context.getResolutionNamespace();
if (rns != null && rns.getId().equals(ns.getId())) {
return 75;
}
/*
* between 25 and 50 is attributed to namespace being traceable in dependency
* chain.
*/
int nsDistance = ((ResolutionScope) context).getNamespaceDistance(ns);
if (nsDistance >= 0) {
return 50 - (nsDistance > 24 ? 24 : nsDistance);
}
/*
* between 1 and 25 is attributed to project being traceable.
*/
int prDistance = ((ResolutionScope) context).getProjectDistance(ns == null ? null : ns.getProject());
if (prDistance >= 0) {
return 25 - (prDistance > 24 ? 24 : prDistance);
}
/*
* nothing in common.
*/
return 0;
}
/*
* trait concordance wrt context n = # of traits shared / #. of traits possible,
* normalized to 100 TODO REIMPLEMENT AS APPROPRIATE - a compatibility rank
*/
public static double computeTraitConcordance(Model model, IResolutionScope context) {
/*
* list of traits in common. Don't check the trait value - assumed the same
* because of the search strategy.
*/
try {
IKnowledge c = model.getObservableConcept(); // getWantedObservable(model,
// context);
if (c == null) {
// TODO issues here - just a hack, should not happen
return 0;
}
Collection attrs = Traits.separateAttributes(c).getSecond();
Collection wanted = ((ResolutionScope) context).getTraits();
int common = 0;
if (attrs.size() == 0 && wanted.size() == 0)
return 100.0;
for (IConcept zio : wanted) {
if (attrs.contains(zio)) {
common++;
}
}
if (wanted.size() > 0) {
return 100.0 * ((double) common / (double) wanted.size());
}
} catch (Exception e) {
}
return 0;
}
// private static IKnowledge getWantedObservable(Model model, IResolutionScope
// context) {
// for (Observable o : model.observables) {
// IKnowledge wanted = NS.getBaseIdentity(((ResolutionScope)
// context).observable.getType());
// if (wanted == null)
// continue;
// if (NS.getBaseIdentity(o.mainType).is(wanted)) {
// return o.mainType;
// }
// }
// return null;
// }
/*
* scale specificity -> total coverage of object wrt context (minimum of all extents?)
* = scale / (object coverage) * 100
*/
public double computeScaleSpecificity(Model model, IResolutionScope context) {
return computeScaleCriteria(model, context)[1];
}
/*
* return the (possibly cached) array of coverage, specificity and resolution.
*/
private double[] computeScaleCriteria(Model model, IResolutionScope context) {
double specificityS = -1;
double coverageS = -1;
double resolutionS = -1;
double specificityT = -1;
double coverageT = -1;
double resolutionT = -1;
if (!_idxss.containsKey(model)) {
if (model.getShape() != null) {
/*
* compute intersection if we're spatial
*/
ISpatialExtent space = context.getScale().getSpace();
if (space != null) {
Geometry cspace = ((IGeometricShape) space).getStandardizedGeometry();
Geometry intersection = cspace.intersection(model.getShape()
.getStandardizedGeometry());
specificityS = 100.0 * (intersection.getArea() / model.getShape()
.getStandardizedGeometry()
.getArea());
coverageS = 100.0 * (intersection.getArea() / cspace.getArea());
}
}
if (model.getTimeEnd() != model.getTimeStart()) {
/*
* TODO do the same with time and take the minimum - or should this be the
* separate currency value ?
*/
}
_idxss.put(model, new double[] {
getMin(coverageS, coverageT),
getMin(specificityS, specificityT),
getMin(resolutionS, resolutionT)
});
}
return _idxss.get(model);
}
private double getMin(double a, double b) {
if (a < 0 && b < 0)
return 0.0;
if (a < 0)
return b;
if (b < 0)
return a;
return Math.min(a, b);
}
/*
* network remoteness -> whether coming from remote KBox (added by kbox
* implementation) 100 -> local 0 -> remote
*/
public static double computeNetworkRemoteness(Model model, IResolutionScope context) {
return model.getServerId() == null ? 100 : 0;
}
/*
* inherency -> level wrt observable: 100 = same thing-ness, specific inherency (model
* and context are inherent to same thing) 66 = same thing-ness, non-specific
* inherency 33 = different thing-ness, mediatable inherency
*
* TODO level of inherency at highest level should be modulated by semantic distance
* between object and context, with 100 reserved for inherent to exactly same object
* type.
*
*/
public static double computeInherency(Model model, IResolutionScope context) {
return 0.0;
}
/*
* scale coherency -> coherency of domains adopted by context vs. the object n = # of
* domains shared (based on the isSpatial/isTemporal fields) normalize to 100
*/
public static double computeScaleCoherency(Model model, IResolutionScope context) {
// TODO Auto-generated method stub
return 0.0;
}
/*
* scale coverage -> of scale in context (minimum of all extents? or one per extent?)
* 0 = not scale-specific (outside scale will not be returned) (1, 100] = (scale ^
* object context) / scale
*/
public double computeScaleCoverage(Model model, IResolutionScope context) {
return computeScaleCriteria(model, context)[0];
}
/*
* subjective concordance = multi-criteria ranking of user-defined metadata wrt
* default or namespace priorities
*
* @returns chosen concordance metric normalized to 100
*/
public static double computeSubjectiveConcordance(Model model, IResolutionScope context, List subjectiveCriteria) {
ArrayList> vals = new ArrayList>();
IMetadata nm = context.getResolutionNamespace().getResolutionCriteria();
for (String s : subjectiveCriteria) {
int val = (model.getMetadata() != null && model.getMetadata().getData().containsKey(s))
? ((Number) model.getMetadata().getData().get(s)).intValue() : 50;
int wei = 100;
if (nm != null && nm.get(s) != null) {
wei = context.getResolutionNamespace().getResolutionCriteria().getInt(s);
} else if (defaultStrategy.get(s) != null) {
wei = defaultStrategy.getInt(s);
}
vals.add(new Pair(val, wei));
}
return aggregate(vals) * 100;
}
/*
* evidence -> resolved/unresolved 100 = resolved from data or object source 75 =
* resolved but requires dereification 50 = computed, no dependencies 0 = unresolved
*/
public static double computeEvidence(Model model, IResolutionScope context) {
if ((model.isHasDirectData() || model.isHasDirectObjects())
&& model.getDereifyingAttribute() == null) {
return 100.0;
}
if ((model.isHasDirectData() || model.isHasDirectObjects())
&& model.getDereifyingAttribute() != null) {
return 75.0;
}
if (model.isResolved()) {
return 50.0;
}
return 0.0;
}
/**
* Implement the weighted multiplicative aggregation for subjective criteria expressed
* numerically with a 0-100 value. Each pair of value and weight is interpreted
* as a "benefit" criterion that contributes to the overall score in a proportion that
* depends on its weight - the higher the weight, the higher the contribution. All
* scores are multiplied when still in the [0 1] range. The combination of low values
* is guaranteed to make the value lower.
*
* It is meant for "benefit" criteria that correlate directly to value, and the weight
* of each criterion defines the amount of change it can produce in the final index -
* higher weight, higher importance (the criterion value only changes the (100 -
* w)/100 proportion of the [0 1] interval). For the index to be meaningful, only
* indices that refer to exactly the same criteria can be compared - the safe bet for
* missing values is to make them "average" by giving them value 50.
*
* @param values
* collection of pairs for each criterion. Both value and
* weight must be integers in the 0-100 range.
* @return aggregated criterion value
*/
public static double aggregate(Collection> values) {
double ret = Double.NaN;
for (Pair vp : values) {
double intProportion = 100.0 * ((double) vp.getSecond() / 100.0);
double base = 100.0 - (double) vp.getSecond();
double c = (base + intProportion * ((double) vp.getFirst() / 100.0)) / 100.0;
ret = Double.isNaN(ret) ? c : ret * c;
}
return ret;
}
@Override
public List listCriteria() {
return _criteriaOrder;
}
public Map getCriteria() {
Map ret = new HashMap<>();
IMetadata strategy = _context.getResolutionNamespace() == null ? null
: _context.getResolutionNamespace()
.getResolutionCriteria();
if (strategy == null) {
strategy = getDefaultRankingStrategy();
}
for (String s : _criteriaOrder) {
ret.put(s, (strategy.get(s) == null ? 50.0 : ((Number) strategy.get(s)).doubleValue()));
}
for (String s : _subjectiveCriteria) {
ret.put(s, (strategy.get(s) == null ? 50.0 : ((Number) strategy.get(s)).doubleValue()));
}
return ret;
}
@Override
public String asText() {
String ret = "";
IMetadata strategy = _context.getResolutionNamespace() == null ? null
: _context.getResolutionNamespace()
.getResolutionCriteria();
if (strategy == null) {
strategy = getDefaultRankingStrategy();
}
for (String s : _criteriaOrder) {
if (!ret.isEmpty()) {
ret += ",";
}
ret += s + "=" + ((strategy.get(s) == null ? 50 : strategy.get(s)));
}
for (String s : _subjectiveCriteria) {
if (!ret.isEmpty()) {
ret += ",";
}
ret += s + "=" + ((strategy.get(s) == null ? 50 : strategy.get(s)));
}
return ret;
}
/*
* call to register ranks that were computed outside this object. Used for model data
* coming from the remote search service.
*/
public void registerRanks(Model md) {
_ranks.put(md, md.getRanks());
}
}