diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/digitaltwin/DigitalTwin.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/digitaltwin/DigitalTwin.java
index 9261c53d1..c21ba096a 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/digitaltwin/DigitalTwin.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/digitaltwin/DigitalTwin.java
@@ -3,18 +3,22 @@
import org.integratedmodelling.klab.api.collections.Identifier;
import org.integratedmodelling.klab.api.data.KnowledgeGraph;
import org.integratedmodelling.klab.api.data.Metadata;
+import org.integratedmodelling.klab.api.data.Mutable;
import org.integratedmodelling.klab.api.geometry.Geometry;
import org.integratedmodelling.klab.api.knowledge.Observable;
import org.integratedmodelling.klab.api.knowledge.Urn;
import org.integratedmodelling.klab.api.knowledge.observation.Observation;
import org.integratedmodelling.klab.api.knowledge.observation.impl.ObservationImpl;
import org.integratedmodelling.klab.api.knowledge.observation.scale.time.TimeInstant;
+import org.integratedmodelling.klab.api.lang.kim.KimConcept;
import org.integratedmodelling.klab.api.lang.kim.KimModel;
+import org.integratedmodelling.klab.api.lang.kim.KimObservable;
import org.integratedmodelling.klab.api.lang.kim.KimSymbolDefinition;
import org.integratedmodelling.klab.api.provenance.Provenance;
import org.integratedmodelling.klab.api.scope.ContextScope;
import org.integratedmodelling.klab.api.scope.Scope;
import org.integratedmodelling.klab.api.services.Reasoner;
+import org.integratedmodelling.klab.api.services.resolver.ResolutionConstraint;
import org.integratedmodelling.klab.api.services.runtime.Dataflow;
import java.util.Map;
@@ -93,22 +97,31 @@ enum Relationship {
* Accepts all the needed elements for the observation, including geometry, observable and the like. If
* two geometries are passed, the second is the observer's (FIXME that should be deprecated). In dependent
* observations, the geometry may be omitted and the geometry of the owning substantial will be used.
+ *
+ * If the observation is an observer definition, the geometry ends up as a constraint in the scope. If an
+ * observer's own geometry is unspecified, a default scalar one is attributed.
*
+ * @param scope a scope. If a context scope and we use an observer definition, the scope's resolution constraints
+ * will include an observer geometry after the call.
* @param resolvables
* @return
*/
- static ObservationImpl createObservation(Scope scope, Object... resolvables) {
+ static ObservationImpl createObservation(@Mutable Scope scope, Object... resolvables) {
final Set knownKeys = Set.of("observation", "semantics", "space", "time");
String name = null;
Geometry geometry = null;
+ Geometry observerGeometry = null;
Observable observable = null;
String resourceUrn = null;
String modelUrn = null;
String defaultValue = null;
Metadata metadata = Metadata.create();
+ boolean isObserver = false;
+
+ Geometry ogeom = null;
if (resolvables != null) {
for (Object o : resolvables) {
if (o instanceof Observable obs) {
@@ -129,6 +142,8 @@ static ObservationImpl createObservation(Scope scope, Object... resolvables) {
if (("observation".equals(symbol.getDefineClass()) || "observer".equals(symbol.getDefineClass())) && symbol.getValue() instanceof Map
, ?> definition) {
+ isObserver = "observer".equals(symbol.getDefineClass());
+
name = symbol.getName();
if (definition.containsKey("semantics")) {
observable = scope.getService(Reasoner.class).resolveObservable(definition.get(
@@ -139,36 +154,18 @@ static ObservationImpl createObservation(Scope scope, Object... resolvables) {
}
}
if (definition.containsKey("space") || definition.containsKey("time")) {
- var geometryBuilder = Geometry.builder();
- if (definition.containsKey("space")) {
- var spaceBuilder = geometryBuilder.space();
- if (definition.get("space") instanceof Map, ?> spaceDefinition) {
- if (spaceDefinition.containsKey("shape")) {
- spaceBuilder.shape(spaceDefinition.get("shape").toString());
- }
- if (spaceDefinition.containsKey("grid")) {
- spaceBuilder.resolution(spaceDefinition.get("grid").toString());
- }
- // TODO add bounding box etc
- }
- geometryBuilder = spaceBuilder.build();
- }
- if (definition.containsKey("time")) {
- var timeBuilder = geometryBuilder.time();
- if (definition.get("time") instanceof Map, ?> timeDefinition) {
- if (timeDefinition.containsKey("year")) {
- var year = timeDefinition.get("year");
- if (year instanceof Number number) {
- timeBuilder.year(number.intValue());
- } else if (year instanceof Identifier identifier && "default".equals(
- identifier.getValue())) {
- timeBuilder.year(TimeInstant.create().getYear());
- }
- }
- }
- geometryBuilder = timeBuilder.build();
- }
- geometry = geometryBuilder.build();
+ geometry = defineGeometry(definition);
+ }
+
+ if (definition.containsKey("geometry") && definition.get("geometry") instanceof Map, ?>) {
+ ogeom = defineGeometry((Map, ?>) definition.get("geometry"));
+ }
+
+ if (isObserver) {
+ observerGeometry = geometry;
+ geometry = ogeom == null ? Geometry.builder().build() : ogeom;
+ } else if (geometry == null && ogeom != null) {
+ geometry = ogeom;
}
for (var key : definition.keySet()) {
@@ -178,19 +175,25 @@ static ObservationImpl createObservation(Scope scope, Object... resolvables) {
}
}
} else if (o instanceof KimModel model) {
- // send the model URN and extract the observable
+ // send the model URN and extract the observable. The modelUrn should become a
+ // constraint within the requesting scope upstream.
observable =
- scope.getService(Reasoner.class).declareObservable(model.getObservables().get(0));
+ scope.getService(Reasoner.class).declareObservable(model.getObservables().getFirst());
modelUrn = model.getUrn();
} else if (o instanceof Map, ?> map) {
// metadata
metadata.putAll((Map extends String, ?>) map);
+ } else if (o instanceof KimConcept concept) {
+ observable = scope.getService(Reasoner.class).resolveObservable(concept.getUrn());
+ } else if (o instanceof KimObservable obs) {
+ observable = scope.getService(Reasoner.class).resolveObservable(obs.getUrn());
}
}
}
/*
- least requisite is having an observable
+ least requisite is having an observable. A quality observation doesn't need to specify
+ a geometry.
*/
if (observable != null) {
ObservationImpl ret = new ObservationImpl();
@@ -199,11 +202,50 @@ static ObservationImpl createObservation(Scope scope, Object... resolvables) {
ret.setObservable(observable);
ret.setValue(defaultValue);
ret.setName(name);
+
+ if (observerGeometry != null && scope instanceof ContextScope contextScope) {
+ contextScope.getResolutionConstraints().add(ResolutionConstraint.of(ResolutionConstraint.Type.ObserverGeometry, observerGeometry));
+ }
+
+
return ret;
}
return null;
}
+ static Geometry defineGeometry(Map, ?> definition) {
+ var geometryBuilder = Geometry.builder();
+ if (definition.containsKey("space")) {
+ var spaceBuilder = geometryBuilder.space();
+ if (definition.get("space") instanceof Map, ?> spaceDefinition) {
+ if (spaceDefinition.containsKey("shape")) {
+ spaceBuilder.shape(spaceDefinition.get("shape").toString());
+ }
+ if (spaceDefinition.containsKey("grid")) {
+ spaceBuilder.resolution(spaceDefinition.get("grid").toString());
+ }
+ // TODO add bounding box etc
+ }
+ geometryBuilder = spaceBuilder.build();
+ }
+ if (definition.containsKey("time")) {
+ var timeBuilder = geometryBuilder.time();
+ if (definition.get("time") instanceof Map, ?> timeDefinition) {
+ if (timeDefinition.containsKey("year")) {
+ var year = timeDefinition.get("year");
+ if (year instanceof Number number) {
+ timeBuilder.year(number.intValue());
+ } else if (year instanceof Identifier identifier && "default".equals(
+ identifier.getValue())) {
+ timeBuilder.year(TimeInstant.create().getYear());
+ }
+ }
+ }
+ geometryBuilder = timeBuilder.build();
+ }
+ return geometryBuilder.build();
+ }
+
}
diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryBuilder.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryBuilder.java
index e06c40087..5606b6cfd 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryBuilder.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryBuilder.java
@@ -320,6 +320,11 @@ public GeometryImpl build() {
GeometryImpl ret = new GeometryImpl();
+ if (space == null && time == null) {
+ ret.setScalar(true);
+ ret.setEmpty(false);
+ }
+
if (space != null || time != null) {
ret.setScalar(false);
ret.setEmpty(false);
diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryImpl.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryImpl.java
index 47a61fc6b..eb9cc9370 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryImpl.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/geometry/impl/GeometryImpl.java
@@ -14,7 +14,6 @@
import org.integratedmodelling.klab.api.utils.Utils;
import java.util.*;
-import java.util.function.BiFunction;
public class GeometryImpl implements Geometry {
@@ -873,7 +872,10 @@ public GeometryImpl withBoundingBox(double minX, double maxX, double minY, doubl
if (space == null) {
throw new KlabIllegalStateException("cannot set spatial parameters on a geometry without space");
}
- space.getParameters().put(PARAMETER_SPACE_BOUNDINGBOX, GeometryImpl.encodeVal(new double[]{minX, maxX, minY, maxY}));
+ space.getParameters().put(PARAMETER_SPACE_BOUNDINGBOX, GeometryImpl.encodeVal(new double[]{minX,
+ maxX,
+ minY,
+ maxY}));
return this;
}
diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/scope/ContextScope.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/scope/ContextScope.java
index 05d5b0f07..75d254167 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/scope/ContextScope.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/scope/ContextScope.java
@@ -413,7 +413,6 @@ static String getScopeId(ContextScope scope) {
return ret.toString();
}
-
static Geometry getResolutionGeometry(ContextScope scope) {
var resolutionGeometry = scope.getConstraint(ResolutionConstraint.Type.Geometry, Geometry.class);
@@ -437,7 +436,7 @@ default Geometry getObservationGeometry(Observation observation) {
}
// override if collective and substantial
if (observation.getObservable().isCollective() && getObserver() != null && getObserver().getGeometry() != null) {
- geometry = getObserver().getGeometry();
+ geometry = getObservedGeometry();
}
}
@@ -448,6 +447,21 @@ default Geometry getObservationGeometry(Observation observation) {
return geometry;
}
+ /**
+ * The geometry currently observed by the current observer, which may be null if the observer is
+ * null, and may NOT otherwise. This is set when an observer is defined, but is independent
+ * of the observer's own geometry, and may change through calls independent of the observer as long
+ * as an observer is there.
+ *
+ * Observer geometry is set by adding a {@link ResolutionConstraint} to the scope.
+ *
+ * @return
+ */
+ default Geometry getObservedGeometry() {
+ var ret = getConstraint(ResolutionConstraint.Type.ObserverGeometry, Geometry.class);
+ return ret == null ? Geometry.EMPTY : ret;
+ }
+
/**
* Parse a scope token into the corresponding data structure
*
diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/RuntimeService.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/RuntimeService.java
index 76cae6eec..200c82d7c 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/RuntimeService.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/RuntimeService.java
@@ -12,7 +12,6 @@
import org.integratedmodelling.klab.api.services.resources.ResourceSet;
import org.integratedmodelling.klab.api.services.runtime.Dataflow;
import org.integratedmodelling.klab.api.services.runtime.objects.SessionInfo;
-import org.integratedmodelling.klab.api.utils.Utils;
import java.util.List;
import java.util.Map;
diff --git a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/resolver/ResolutionConstraint.java b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/resolver/ResolutionConstraint.java
index 36a2f1f07..ac2053c38 100644
--- a/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/resolver/ResolutionConstraint.java
+++ b/klab.core.api/src/main/java/org/integratedmodelling/klab/api/services/resolver/ResolutionConstraint.java
@@ -52,6 +52,7 @@ enum Type {
Scenarios(String.class, false /* debatable */),
Geometry(Geometry.class, false),
+ ObserverGeometry(Geometry.class, false),
ResolutionNamespace(String.class, false),
ResolutionProject(String.class, false),
UsingModel(String.class, false),
diff --git a/klab.core.common/src/main/java/org/integratedmodelling/common/services/client/scope/ClientContextScope.java b/klab.core.common/src/main/java/org/integratedmodelling/common/services/client/scope/ClientContextScope.java
index 256465123..618155dd3 100644
--- a/klab.core.common/src/main/java/org/integratedmodelling/common/services/client/scope/ClientContextScope.java
+++ b/klab.core.common/src/main/java/org/integratedmodelling/common/services/client/scope/ClientContextScope.java
@@ -5,6 +5,7 @@
import org.integratedmodelling.klab.api.data.RuntimeAsset;
import org.integratedmodelling.klab.api.data.Storage;
import org.integratedmodelling.klab.api.exceptions.KlabInternalErrorException;
+import org.integratedmodelling.klab.api.geometry.Geometry;
import org.integratedmodelling.klab.api.knowledge.Observable;
import org.integratedmodelling.klab.api.knowledge.SemanticType;
import org.integratedmodelling.klab.api.knowledge.observation.Observation;
@@ -301,5 +302,4 @@ public List getConstraints(ResolutionConstraint.Type type, Class resul
public Storage getStorage(Observation observation) {
return null;
}
-
}
diff --git a/klab.modeler/src/main/java/org/integratedmodelling/klab/modeler/ModelerImpl.java b/klab.modeler/src/main/java/org/integratedmodelling/klab/modeler/ModelerImpl.java
index 2cc792955..889410550 100644
--- a/klab.modeler/src/main/java/org/integratedmodelling/klab/modeler/ModelerImpl.java
+++ b/klab.modeler/src/main/java/org/integratedmodelling/klab/modeler/ModelerImpl.java
@@ -272,10 +272,10 @@ public void observe(Object asset, boolean adding) {
}
} else if (statement instanceof KimConceptStatement conceptStatement) {
// TODO check observable vs. context (qualities w/ their context etc.)
- resolvables.add(conceptStatement.getNamespace() + ":" + conceptStatement.getUrn());
+ resolvables.add(conceptStatement);
} else if (statement instanceof KimObservable conceptStatement) {
// TODO check observable vs. context (qualities w/ their context etc.)
- resolvables.add(conceptStatement.getUrn());
+ resolvables.add(conceptStatement);
}
} else if (asset instanceof String || asset instanceof Urn) {
resolvables.add(asset.toString());
diff --git a/klab.services.runtime/src/main/java/org/integratedmodelling/klab/services/runtime/RuntimeService.java b/klab.services.runtime/src/main/java/org/integratedmodelling/klab/services/runtime/RuntimeService.java
index 0d6750554..fa24b7d66 100644
--- a/klab.services.runtime/src/main/java/org/integratedmodelling/klab/services/runtime/RuntimeService.java
+++ b/klab.services.runtime/src/main/java/org/integratedmodelling/klab/services/runtime/RuntimeService.java
@@ -12,6 +12,7 @@
import org.integratedmodelling.klab.api.exceptions.KlabIllegalStateException;
import org.integratedmodelling.klab.api.exceptions.KlabInternalErrorException;
import org.integratedmodelling.klab.api.exceptions.KlabResourceAccessException;
+import org.integratedmodelling.klab.api.knowledge.SemanticType;
import org.integratedmodelling.klab.api.knowledge.observation.Observation;
import org.integratedmodelling.klab.api.knowledge.observation.impl.ObservationImpl;
import org.integratedmodelling.klab.api.lang.Contextualizable;
@@ -287,6 +288,22 @@ public long submit(Observation observation, ContextScope scope) {
"knowledge graph for now");
}
+ if (observation.getObservable().is(SemanticType.QUALITY) && scope.getContextObservation() == null) {
+ throw new KlabIllegalStateException("Cannot observe a quality without a context observation");
+ }
+
+ /**
+ * Only situation when we accept an observation w/o geometry
+ */
+ if (observation.getGeometry() == null &&
+ observation instanceof ObservationImpl observation1) {
+ if (observation.getObservable().is(SemanticType.QUALITY) && scope.getContextObservation() != null) {
+ observation1.setGeometry(scope.getContextObservation().getGeometry());
+ } else if (observation.getObservable().is(SemanticType.COUNTABLE) && observation.getObservable().isCollective() && scope.getObserver() != null) {
+ observation1.setGeometry(scope.getObservedGeometry());
+ }
+ }
+
if (scope instanceof ServiceContextScope serviceContextScope) {
var digitalTwin = getDigitalTwin(scope);