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) 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);