StatedNavigationDeriver.java
package network.ike.knowledge.provider;
import dev.ikm.tinkar.common.id.IntIdSet;
import dev.ikm.tinkar.common.id.IntIds;
import dev.ikm.tinkar.common.id.PublicId;
import dev.ikm.tinkar.common.service.PrimitiveData;
import dev.ikm.tinkar.common.util.uuid.UuidT5Generator;
import dev.ikm.tinkar.coordinate.stamp.calculator.Latest;
import dev.ikm.tinkar.coordinate.view.ViewCoordinateRecord;
import dev.ikm.tinkar.coordinate.view.calculator.ViewCalculator;
import dev.ikm.tinkar.coordinate.view.calculator.ViewCalculatorWithCache;
import dev.ikm.tinkar.entity.EntityService;
import dev.ikm.tinkar.entity.PatternEntityVersion;
import dev.ikm.tinkar.entity.RecordListBuilder;
import dev.ikm.tinkar.entity.SemanticEntityVersion;
import dev.ikm.tinkar.entity.SemanticRecord;
import dev.ikm.tinkar.entity.SemanticRecordBuilder;
import dev.ikm.tinkar.entity.SemanticVersionRecord;
import dev.ikm.tinkar.entity.StampEntity;
import dev.ikm.tinkar.entity.graph.DiTreeEntity;
import dev.ikm.tinkar.entity.graph.EntityVertex;
import dev.ikm.tinkar.entity.transaction.Transaction;
import dev.ikm.tinkar.terms.ConceptFacade;
import dev.ikm.tinkar.terms.State;
import dev.ikm.tinkar.terms.TinkarTerm;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import static dev.ikm.tinkar.common.service.PrimitiveData.SCOPED_PATTERN_PUBLICID_FOR_NID;
/**
* Derives the stated navigation of every concept from its stated definition and writes
* it into the open store (IKE-Network/ike-issues#1123).
*
* <p>A stated navigation semantic records, for one concept, the concepts its stated
* definition names as parents and the concepts whose stated definitions name it as a
* parent. A knowledge file carries such semantics as they stood when the file was
* written; once a ledger re-declares a concept's definition, the file's navigation is
* stale, and nothing else rewrites it: the reasoner writes inferred navigation only.
* Browsers that navigate the stated view, such as Komet's login dialog, then show the
* old parents.
*
* <p>The derivation reads, under the given view, the latest stated definition of every
* concept and keeps those that are active. A parent is a concept named by a concept
* reference inside a necessary set or a sufficient set; a concept naming itself, the
* root's own encoding, is not its own parent. Children are the inverse. Every concept
* that has an active definition or is named as a parent gets its navigation written:
* a new version on the concept's existing stated navigation semantic when the latest
* fields differ, a new semantic with the single-semantic identity of the pattern when
* the concept has none, and nothing when the fields already agree, so a second
* derivation over the same store writes nothing. Written versions carry the view's
* derived-content stamp: the logic coordinate's classifier as author, the view's
* default module and path, the same identity the classifier's inferred results carry.
*/
public final class StatedNavigationDeriver {
private static final System.Logger LOG = System.getLogger(StatedNavigationDeriver.class.getName());
/**
* What one derivation did.
*
* @param conceptsConsidered the concepts with an active stated definition or named as a parent
* @param semanticsUpdated existing stated navigation semantics that received a new version
* @param semanticsCreated stated navigation semantics minted for concepts that had none
*/
public record Summary(int conceptsConsidered, int semanticsUpdated, int semanticsCreated) {
/** Whether the derivation wrote anything. */
public boolean wroteAnything() {
return semanticsUpdated > 0 || semanticsCreated > 0;
}
}
private StatedNavigationDeriver() {
}
/**
* Derives and writes the stated navigation of every concept in the open store.
*
* @param view the view the stated definitions are read under and whose classifier,
* default module, and default path stamp the written versions
* @return what the derivation did
*/
public static Summary derive(ViewCoordinateRecord view) {
ViewCalculator calculator = ViewCalculatorWithCache.getCalculator(view);
int statedPatternNid = view.logicCoordinate().statedAxiomsPatternNid();
int navigationPatternNid = TinkarTerm.STATED_NAVIGATION_PATTERN.nid();
Map<Integer, Set<Integer>> parentsByConcept = new HashMap<>();
Map<Integer, Set<Integer>> childrenByConcept = new HashMap<>();
calculator.forEachSemanticVersionOfPattern(statedPatternNid,
(semanticVersion, patternVersion) -> {
if (!semanticVersion.active()) {
return;
}
int concept = semanticVersion.referencedComponentNid();
Set<Integer> parents = statedParents(concept,
(DiTreeEntity) semanticVersion.fieldValues().get(0));
parentsByConcept.computeIfAbsent(concept, key -> new TreeSet<>()).addAll(parents);
for (int parent : parents) {
childrenByConcept.computeIfAbsent(parent, key -> new TreeSet<>()).add(concept);
}
});
Set<Integer> concepts = new TreeSet<>(parentsByConcept.keySet());
concepts.addAll(childrenByConcept.keySet());
Latest<PatternEntityVersion> navigationPattern = calculator.latest(navigationPatternNid);
if (navigationPattern.isAbsent()) {
throw new IllegalStateException("The stated navigation pattern has no version under the view");
}
FieldOrder order = FieldOrder.of(navigationPattern.get());
PublicId navigationPatternId = PrimitiveData.publicId(navigationPatternNid);
Transaction transaction = Transaction.make("Stated navigation derivation");
StampEntity<?> stamp = transaction.getStamp(State.ACTIVE,
view.logicCoordinate().classifierNid(),
view.getDefaultModuleNid(),
view.getDefaultPathNid());
int updated = 0;
int created = 0;
for (int concept : concepts) {
IntIdSet children = IntIds.set.of(toArray(childrenByConcept.get(concept)));
IntIdSet parents = IntIds.set.of(toArray(parentsByConcept.get(concept)));
ImmutableList<Object> fields = order.fields(children, parents);
List<Integer> existing = new ArrayList<>();
PrimitiveData.get().forEachSemanticNidForComponentOfPattern(concept, navigationPatternNid,
existing::add);
if (existing.isEmpty()) {
transaction.addComponent(mint(concept, navigationPatternId, navigationPatternNid,
stamp.nid(), fields));
created++;
} else {
int semanticNid = existing.getFirst();
if (agrees(calculator.latest(semanticNid), order, children, parents)) {
continue;
}
SemanticRecord record = calculator.updateFields(semanticNid, fields, stamp.nid());
transaction.addComponent(record);
EntityService.get().putEntity(record);
updated++;
}
}
if (updated > 0 || created > 0) {
transaction.commit();
} else {
transaction.cancel();
}
Summary summary = new Summary(concepts.size(), updated, created);
LOG.log(System.Logger.Level.INFO, "Stated navigation derived for {0} concepts: {1} semantics updated, {2} created",
summary.conceptsConsidered(), summary.semanticsUpdated(), summary.semanticsCreated());
return summary;
}
/**
* The parents a stated definition names: every concept reference inside a necessary
* set or a sufficient set, the concept itself excluded.
*/
static Set<Integer> statedParents(int concept, DiTreeEntity definition) {
Set<Integer> parents = new TreeSet<>();
EntityVertex root = definition.root();
for (EntityVertex set : definition.successors(root)) {
int setMeaning = set.getMeaningNid();
if (setMeaning != TinkarTerm.NECESSARY_SET.nid() && setMeaning != TinkarTerm.SUFFICIENT_SET.nid()) {
continue;
}
for (EntityVertex connective : definition.successors(set)) {
if (connective.getMeaningNid() != TinkarTerm.AND.nid()) {
continue;
}
for (EntityVertex atom : definition.successors(connective)) {
if (atom.getMeaningNid() != TinkarTerm.CONCEPT_REFERENCE.nid()) {
continue;
}
Object reference = atom.propertyFast(TinkarTerm.CONCEPT_REFERENCE);
if (reference instanceof ConceptFacade facade && facade.nid() != concept) {
parents.add(facade.nid());
}
}
}
}
return parents;
}
private static boolean agrees(Latest<SemanticEntityVersion> latest, FieldOrder order,
IntIdSet children, IntIdSet parents) {
if (latest.isAbsent() || !latest.get().active()) {
return false;
}
ImmutableList<Object> fields = latest.get().fieldValues();
return sameMembers((IntIdSet) fields.get(order.childrenIndex()), children)
&& sameMembers((IntIdSet) fields.get(order.parentsIndex()), parents);
}
private static boolean sameMembers(IntIdSet left, IntIdSet right) {
Set<Integer> leftMembers = new HashSet<>();
for (int nid : left.toArray()) {
leftMembers.add(nid);
}
Set<Integer> rightMembers = new HashSet<>();
for (int nid : right.toArray()) {
rightMembers.add(nid);
}
return leftMembers.equals(rightMembers);
}
private static SemanticRecord mint(int concept, PublicId patternId, int patternNid, int stampNid,
ImmutableList<Object> fields) {
UUID uuid = UuidT5Generator.singleSemanticUuid(patternId, PrimitiveData.publicId(concept));
int semanticNid = ScopedValue.where(SCOPED_PATTERN_PUBLICID_FOR_NID, patternId)
.call(() -> PrimitiveData.nid(uuid));
RecordListBuilder<SemanticVersionRecord> versions = RecordListBuilder.make();
SemanticRecord record = SemanticRecordBuilder.builder()
.nid(semanticNid)
.referencedComponentNid(concept)
.leastSignificantBits(uuid.getLeastSignificantBits())
.mostSignificantBits(uuid.getMostSignificantBits())
.patternNid(patternNid)
.versions(versions)
.build();
versions.add(new SemanticVersionRecord(record, stampNid, fields));
EntityService.get().putEntity(record);
return record;
}
private static int[] toArray(Set<Integer> nids) {
if (nids == null) {
return new int[0];
}
int[] array = new int[nids.size()];
int index = 0;
for (int nid : nids) {
array[index++] = nid;
}
return array;
}
/** Which field of the navigation pattern holds the children and which the parents. */
record FieldOrder(int childrenIndex, int parentsIndex) {
/**
* Reads the order off the pattern's field meanings: relationship destination
* for children, relationship origin for parents; the pattern's own declared order
* of the two fields otherwise.
*/
static FieldOrder of(PatternEntityVersion pattern) {
int fieldCount = pattern.fieldDefinitions().size();
if (fieldCount != 2) {
throw new IllegalStateException("A navigation pattern carries two fields, not " + fieldCount);
}
int children = 0;
int parents = 1;
for (int index = 0; index < fieldCount; index++) {
int meaning = pattern.fieldDefinitions().get(index).meaningNid();
if (meaning == TinkarTerm.RELATIONSHIP_DESTINATION.nid()) {
children = index;
} else if (meaning == TinkarTerm.RELATIONSHIP_ORIGIN.nid()) {
parents = index;
}
}
if (children == parents) {
throw new IllegalStateException("The navigation pattern's fields do not tell children from parents");
}
return new FieldOrder(children, parents);
}
ImmutableList<Object> fields(IntIdSet children, IntIdSet parents) {
Object[] values = new Object[2];
values[childrenIndex] = children;
values[parentsIndex] = parents;
return Lists.immutable.of(values);
}
}
}