diff --git a/app/imports/custom/vq/client/templates/VQ_DSS_schema.html b/app/imports/custom/vq/client/templates/VQ_DSS_schema.html
index aa5c8d42..040910aa 100644
--- a/app/imports/custom/vq/client/templates/VQ_DSS_schema.html
+++ b/app/imports/custom/vq/client/templates/VQ_DSS_schema.html
@@ -311,6 +311,9 @@
DSS schema {{info_schema}} has {{classCountAll}}
{{#if showFragmentBlock}}
+ {{#if showCentralityButton}}
+
+ {{/if}}
{{/if}}
{{#if showFragmentBlock}}
@@ -361,6 +364,7 @@ DSS schema {{info_schema}} has {{classCountAll}}
0.3
+
@@ -378,6 +382,33 @@ DSS schema {{info_schema}} has {{classCountAll}}
-->
+
+
+
+
+
+
+
+
+
+
+
+
{{/if}}
diff --git a/app/imports/custom/vq/client/templates/VQ_DSS_schema.js b/app/imports/custom/vq/client/templates/VQ_DSS_schema.js
index df0eb97a..a3abb389 100644
--- a/app/imports/custom/vq/client/templates/VQ_DSS_schema.js
+++ b/app/imports/custom/vq/client/templates/VQ_DSS_schema.js
@@ -2,7 +2,7 @@ import { Template } from 'meteor/templating';
import { Interpreter } from '../../../../client/lib/interpreter.js'
import { dataShapes } from '../../../../custom/vq/client/js/DataShapes.js'
import './VQ_DSS_schema.html'
-import { runFragmentAlgorithm, compareFragmentAlgorithmsIntersection, compareFragmentAlgorithmsSizeIncrease, compareFragmentAlgorithmsRank } from './fragments.js';
+import { runFragmentAlgorithm, computeBRPRelevance, compareFragmentAlgorithmsIntersection, compareFragmentAlgorithmsSizeIncrease, compareFragmentAlgorithmsRank, exportCSVBRPandPPRComparison, exportBRPAnalysisCSV, exportClassDatasetCSV } from './fragments.js';
Template.VQ_DSS_schema.SchemaName = new ReactiveVar('');
Template.VQ_DSS_schema.Classes = new ReactiveVar([]);
@@ -42,6 +42,8 @@ Template.VQ_DSS_schema.HasClasses = new ReactiveVar('');
Template.VQ_DSS_schema.fragmentForm = new ReactiveVar('');
Template.VQ_DSS_schema.HasCPC = new ReactiveVar('');
Template.VQ_DSS_schema.ShowFragmentBlock = new ReactiveVar('');
+Template.VQ_DSS_schema.ShowCentralityButton = new ReactiveVar(false);
+Template.VQ_DSS_schema.CentralityButtonDisabled = new ReactiveVar(false);
Interpreter.customMethods({
VQ_DSS_schema: function(){
@@ -106,6 +108,70 @@ const STANDARD_PROP_COVERAGE_THRESHOLD = 0.5;
const fragmentStdPropEditing = new ReactiveVar(false);
let fragmentStdPropIds = new Set();
+// BRP relevance state
+let brpCentralityData = null; // { cpcListSimple } when pre-calculated
+let brpRelevanceMap = null; // Map currently applied to display_names
+const brpOriginalNames = new Map(); // classId → original display_name before R-prefix
+
+function getBRPConfig() {
+ const cwIn = parseFloat(document.getElementById("brp-cw-incoming").value);
+ const pwSt = parseFloat(document.getElementById("brp-pw-standart").value);
+ const beta = parseFloat(document.getElementById("brp-beta").value);
+ const edgesInTriples = document.getElementById("brp-edgesInTriples").value === "true";
+ const useInstanceCount = document.getElementById("brp-useInstanceCount").value === "true";
+ const closenessMode = document.getElementById("brp-closenessMode").value;
+ const cntTransformName = edgesInTriples ? document.getElementById("brp-cntTransform").value : null;
+ const cntTransformFn = cntTransformName === "log2" ? Math.log2
+ : cntTransformName === "log10" ? Math.log10
+ : cntTransformName === "sqrt" ? Math.sqrt
+ : cntTransformName === "full" ? x => x
+ : null;
+ return {
+ standardProperties: fragmentStdPropIds.size > 0 ? [...fragmentStdPropIds] : null,
+ classWeightIncoming: cwIn,
+ propWeightStandart: pwSt,
+ beta,
+ edgesInTriples,
+ useInstanceCount,
+ closenessMode,
+ cntTransform: cntTransformFn,
+ };
+}
+
+function applyRelevancePrefixes(relevanceMap) {
+ brpRelevanceMap = relevanceMap;
+ dataShapes.schema.diagram.filteredClassList.forEach(cl => {
+ if (!brpOriginalNames.has(cl.id)) brpOriginalNames.set(cl.id, cl.display_name);
+ const r = relevanceMap.get(cl.id);
+ cl.display_name = (r !== undefined ? `R${r.toFixed(3)} - ` : '') + brpOriginalNames.get(cl.id);
+ });
+}
+
+function revertBRPMode() {
+ brpRelevanceMap = null;
+ brpCentralityData = null;
+ brpOriginalNames.forEach((orig, id) => {
+ const cl = dataShapes.schema.diagram.filteredClassList.find(c => c.id === id);
+ if (cl) cl.display_name = orig;
+ });
+ brpOriginalNames.clear();
+ Template.VQ_DSS_schema.Classes.set([...Template.VQ_DSS_schema.Classes.get()]);
+ Template.VQ_DSS_schema.RestClasses.set([...Template.VQ_DSS_schema.RestClasses.get()]);
+ sortClassList();
+}
+
+function sortAndApplyBRPRelevance(relevanceMap) {
+ applyRelevancePrefixes(relevanceMap);
+ const byRel = (a, b) => (relevanceMap.get(b.id) ?? 0) - (relevanceMap.get(a.id) ?? 0);
+ Template.VQ_DSS_schema.Classes.set([...Template.VQ_DSS_schema.Classes.get()].sort(byRel));
+ Template.VQ_DSS_schema.RestClasses.set([...Template.VQ_DSS_schema.RestClasses.get()].sort(byRel));
+}
+
+function resetCentralityPreCalc() {
+ brpCentralityData = null;
+ Template.VQ_DSS_schema.CentralityButtonDisabled.set(false);
+}
+
let stdPropSelectedBackup = null;
let stdPropRestBackup = null;
const delay = ms => new Promise(res => setTimeout(res, ms));
@@ -135,7 +201,7 @@ async function buildFragmentStdPropList(forceReload = false) {
fragmentStdPropIds = new Set();
const properties = (dataShapes.schema.diagram && dataShapes.schema.diagram.properties) || [];
properties.forEach(p => {
- if (propCoverage(p, classCount) >= STANDARD_PROP_COVERAGE_THRESHOLD) {
+ if (Number(p.type_1 || 0) > 0 && Number(p.type_2 || 0) > 0 && propCoverage(p, classCount) >= STANDARD_PROP_COVERAGE_THRESHOLD) {
fragmentStdPropIds.add(p.id);
}
});
@@ -349,6 +415,12 @@ Template.VQ_DSS_schema.helpers({
fragmentForm: function() {
return Template.VQ_DSS_schema.fragmentForm.get();
},
+ showCentralityButton: function() {
+ return Template.VQ_DSS_schema.ShowCentralityButton.get();
+ },
+ centralityApplied: function() {
+ return Template.VQ_DSS_schema.CentralityButtonDisabled.get();
+ },
});
function getParams() {
@@ -931,18 +1003,38 @@ Template.VQ_DSS_schema.events({
const fragAlgorithm = document.getElementById("fragment-algorithm").value;
const fragEdgeWeightContext = document.getElementById("fragment-edge-weight-context").value;
- let brpConfig = {};
+ let brpConfig = null;
if (fragAlgorithm === "brp") {
- const cwIn = parseFloat(document.getElementById("brp-cw-incoming").value);
- const pwSt = parseFloat(document.getElementById("brp-pw-standart").value);
- const beta = parseFloat(document.getElementById("brp-beta").value);
- brpConfig = {
- standardProperties: [...fragmentStdPropIds],
- classWeightIncoming: cwIn,
- propWeightStandart: pwSt,
- beta: beta,
- };
- }
+ brpConfig = getBRPConfig();
+ if (Template.VQ_DSS_schema.CentralityButtonDisabled.get() && brpCentralityData) {
+ brpConfig.preCalcAdj = brpCentralityData.cpcListSimple;
+ }
+ }
+
+ // Example usage of exportCSVBRPandPPRComparison
+ // let ctxs = ["src-tgt-conn", "no-ctx"];
+ // let sizes = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60];
+ // await exportCSVBRPandPPRComparison(mainClasses, ctxs, {
+ // edgesInTriples: [true],
+ // cntTransform: [{name: "log2", fn: Math.log2}, {name: "log10", fn: Math.log10}],
+ // useInstanceCount: [false, true],
+ // classWeightIncoming: [0.3],
+ // beta: [0.7],
+ // propWeightStandart: [0.3],
+ // standardProperties: fragmentStdPropIds.size > 0 ? [...fragmentStdPropIds] : null,
+ // closenessMode: ['centralityBased', 'weightBased', 'unweighted'],
+ // }, sizes);
+
+ // await exportCSVBRPandPPRComparison(mainClasses, ctxs, {
+ // edgesInTriples: [false],
+ // useInstanceCount: [false, true],
+ // classWeightIncoming: [0.3],
+ // beta: [0.7],
+ // propWeightStandart: [0.3],
+ // standardProperties: fragmentStdPropIds.size > 0 ? [...fragmentStdPropIds] : null,
+ // closenessMode: ['centralityBased', 'weightBased', 'unweighted'],
+ // }, sizes);
+
// Calculate fragment
const [fragmentClasses, rank] = await runFragmentAlgorithm(fragAlgorithm, fragEdgeWeightContext, mainClasses, fragSize, undefined, brpConfig);
@@ -953,10 +1045,20 @@ Template.VQ_DSS_schema.events({
else cl.sel = 0;
});
makeClassLists();
- //const classes = dataShapes.schema.diagram.filteredClassList.filter(function(c){return fragmentClasses.includes(c.id)});
- //const restClasses = dataShapes.schema.diagram.filteredClassList.filter(function(c){ return !fragmentClasses.includes(c.id)});
- //setClassListInfo(classes, restClasses);
- //clearData();
+ if (fragAlgorithm === "brp") sortAndApplyBRPRelevance(rank);
+ },
+ 'click #calculateRelevance': async function() {
+ if (Template.VQ_DSS_schema.CentralityButtonDisabled.get()) {
+ revertBRPMode();
+ Template.VQ_DSS_schema.CentralityButtonDisabled.set(false);
+ return;
+ }
+ const mainClasses = Template.VQ_DSS_schema.Classes.get().map(c => c.id);
+ const brpConfig = getBRPConfig();
+ const { cpcListSimple, relevanceMap } = await computeBRPRelevance(mainClasses, brpConfig);
+ brpCentralityData = { cpcListSimple };
+ Template.VQ_DSS_schema.CentralityButtonDisabled.set(true);
+ sortAndApplyBRPRelevance(relevanceMap);
},
'click #getFragment2': async function() {
// Get parameters
@@ -989,7 +1091,9 @@ Template.VQ_DSS_schema.events({
cl.sel = 0;
});
makeClassLists();
+ if (brpRelevanceMap) sortAndApplyBRPRelevance(brpRelevanceMap);
}
+ resetCentralityPreCalc();
clearData();
},
'click #moveR': function() {
@@ -1044,7 +1148,9 @@ Template.VQ_DSS_schema.events({
cl.sel = 1;
});
makeClassLists();
+ if (brpRelevanceMap) sortAndApplyBRPRelevance(brpRelevanceMap);
}
+ resetCentralityPreCalc();
clearData();
},
'click #removeSelectedProp': function() {
@@ -1123,27 +1229,47 @@ Template.VQ_DSS_schema.events({
const wcWrap = document.getElementById("fragment-weight-context-wrap");
const spWrap = document.getElementById("fragment-std-prop-wrap");
const sliders = document.getElementById("fragment-brp-sliders");
+ const opts = document.getElementById("fragment-brp-options");
if (wcWrap) wcWrap.style.display = isBRP ? "none" : "inline-flex";
if (spWrap) spWrap.style.display = isBRP ? "inline-flex" : "none";
if (sliders) sliders.style.display = isBRP ? "grid" : "none";
+ if (opts) opts.style.display = isBRP ? "flex" : "none";
+ Template.VQ_DSS_schema.ShowCentralityButton.set(isBRP);
if (isBRP) {
+ Template.VQ_DSS_schema.CentralityButtonDisabled.set(false);
void buildFragmentStdPropList();
paintSplitSlider(document.getElementById("brp-cw-incoming"), "brp-cw-incoming-val", "brp-cw-outgoing-val");
paintSplitSlider(document.getElementById("brp-pw-standart"), "brp-pw-standart-val", "brp-pw-user-val");
paintSplitSlider(document.getElementById("brp-beta"), "brp-beta-val", "brp-alpha-val");
+ const edgesEl = document.getElementById("brp-edgesInTriples");
+ const cntWrap = document.getElementById("brp-cntTransform-wrap");
+ if (edgesEl && cntWrap) cntWrap.style.display = edgesEl.value === "true" ? "inline-flex" : "none";
+ } else {
+ revertBRPMode();
}
},
'input #brp-cw-incoming': function(e) {
paintSplitSlider(e.target, "brp-cw-incoming-val", "brp-cw-outgoing-val");
+ resetCentralityPreCalc();
},
'input #brp-pw-standart': function(e) {
paintSplitSlider(e.target, "brp-pw-standart-val", "brp-pw-user-val");
+ resetCentralityPreCalc();
},
'input #brp-beta': function(e) {
// Clamp: alpha = 1 - beta, beta=0 would break the closeness term.
if (parseFloat(e.target.value) < 0.1) e.target.value = "0.1";
paintSplitSlider(e.target, "brp-beta-val", "brp-alpha-val");
+ resetCentralityPreCalc();
+ },
+ 'change #brp-edgesInTriples': function(e) {
+ const wrap = document.getElementById("brp-cntTransform-wrap");
+ if (wrap) wrap.style.display = e.target.value === "true" ? "inline-flex" : "none";
+ resetCentralityPreCalc();
},
+ 'change #brp-useInstanceCount': function() { resetCentralityPreCalc(); },
+ 'change #brp-closenessMode': function() { resetCentralityPreCalc(); },
+ 'change #brp-cntTransform': function() { resetCentralityPreCalc(); },
'click #editStandardProps': async function() {
if (!fragmentStdPropEditing.get()) {
setFragmentStdPropEditing(true);
@@ -1163,6 +1289,7 @@ Template.VQ_DSS_schema.events({
fragmentStdPropIds.delete(id);
renderFragmentStdPropList();
applyStdPropHighlight();
+ resetCentralityPreCalc();
}
},
'keyup #stdPropSearch': async function(e) {
@@ -1196,6 +1323,7 @@ Template.VQ_DSS_schema.events({
renderFragmentStdPropList();
applyStdPropHighlight();
["selectedProperties", "restProperties"].forEach(id => { const sel = document.getElementById(id); if (sel) sel.selectedIndex = -1; });
+ resetCentralityPreCalc();
},
'click #hideFragment': function() {
if (Template.VQ_DSS_schema.ShowFragmentBlock.get() ) {
@@ -3386,74 +3514,71 @@ function makeAssociations() {
// Savelk asociācijas
for (const clId of Object.keys(rezFull.classes)) {
const classInfo = rezFull.classes[clId];
- if ( classInfo.used) {
- for ( const atr of classInfo.atr_list) {
- if ( atr.type == 'out' && atr.cnt > 0 && atr.cnt_full > hideSmall && atr.object_cnt > classInfo.cnt*showEssent ) {
+ if (classInfo.used) {
+ for (const atr of classInfo.atr_list) {
+ if (atr.type == 'out' && atr.cnt > 0 && atr.cnt_full > hideSmall && atr.object_cnt > classInfo.cnt * showEssent) {
let hasAssoc = false;
- if ( has_cpc ) {
- if ( classInfo.type == 'PropertyTarget' || classInfo.type == 'PropertySource') {
- atr.object_cnt_dgr = atr.object_cnt; // TODO šis arī ir drusku šaubīgs
- }
- else {
- const cpc_info_full = cpc_info.filter(function(i){
- return i.property_id == atr.p_id && i.type_id == 2 && classInfo.c_list_id.includes(i.class_id) && atr.class_list.includes(i.other_class_id)});
- atr.object_cnt_dgr = cpc_info_full.map( v => v.cnt).reduce((a, b) => a + b, 0);
- }
+ if (has_cpc) {
+ if (classInfo.type == 'PropertyTarget' || classInfo.type == 'PropertySource') {
+ atr.object_cnt_dgr = atr.object_cnt; // TODO šis arī ir drusku šaubīgs
+ } else {
+ const cpc_info_full = cpc_info.filter(function (i) {
+ return i.property_id == atr.p_id && i.type_id == 2 && classInfo.c_list_id.includes(i.class_id) && atr.class_list.includes(i.other_class_id)
+ });
+ atr.object_cnt_dgr = cpc_info_full.map(v => v.cnt).reduce((a, b) => a + b, 0);
+ }
- }
- else {
+ } else {
atr.object_cnt_dgr = atr.object_cnt; // TODO te varētu būt arī savādāk, kā darīt, ja nav cpc_rels
}
for (const to_id of atr.class_list2) {
const aId = `${clId}_${to_id}_${atr.p_name}`;
- const is_range = ( atr.range_id == to_id ) ? 'R':'';
- const p_name = ( params.addIds ) ? `${atr.p_name}(ID-${atr.p_id})`: atr.p_name;
- if ( !has_cpc) {
- rezFull.assoc[aId] = {string:`${p_name} ${atr.is_domain}${is_range}`, cnt:0, p_name:atr.p_name, p_id:`p_${atr.p_id}`, from:clId, to:to_id, removed:false };
+ const is_range = (atr.range_id == to_id) ? 'R' : '';
+ const p_name = (params.addIds) ? `${atr.p_name}(ID-${atr.p_id})` : atr.p_name;
+ if (!has_cpc) {
+ rezFull.assoc[aId] = { string: `${p_name} ${atr.is_domain}${is_range}`, cnt: 0, p_name: atr.p_name, p_id: `p_${atr.p_id}`, from: clId, to: to_id, removed: false };
hasAssoc = true;
- }
- else {
- let aCnt = 0;
- if ( classInfo.type == 'PropertyTarget' || classInfo.type == 'PropertySource') {
- aCnt = atr.object_cnt;
- }
- else {
- const cpc_info_a = cpc_info.filter(function(i){
- return i.property_id == atr.p_id && i.type_id == 2 && classInfo.c_list_id.includes(i.class_id) && rezFull.classes[to_id].c_list_id.includes(i.other_class_id);
- });
- aCnt = cpc_info_a.map( v => v.cnt).reduce((a, b) => a + b, 0);
- }
- if ( aCnt > 0 ) {
- rezFull.assoc[aId] = {string:`${p_name} (${roundCount(aCnt)}) ${atr.is_domain}${is_range}`,cnt:aCnt, p_name:atr.p_name, p_id:`p_${atr.p_id}`, from:clId, to:to_id, removed:false };
+ } else {
+ let aCnt = 0;
+ if (classInfo.type == 'PropertyTarget' || classInfo.type == 'PropertySource') {
+ aCnt = atr.object_cnt;
+ } else {
+ const cpc_info_a = cpc_info.filter(function (i) {
+ return i.property_id == atr.p_id && i.type_id == 2 && classInfo.c_list_id.includes(i.class_id) && rezFull.classes[to_id].c_list_id.includes(i.other_class_id);
+ });
+ aCnt = cpc_info_a.map(v => v.cnt).reduce((a, b) => a + b, 0);
+ }
+ if (aCnt > 0) {
+ rezFull.assoc[aId] = { string: `${p_name} (${roundCount(aCnt)}) ${atr.is_domain}${is_range}`, cnt: aCnt, p_name: atr.p_name, p_id: `p_${atr.p_id}`, from: clId, to: to_id, removed: false };
hasAssoc = true;
}
}
}
- if ( atr.class_list2 == undefined || atr.class_list2.length == 0 ) {
- if ( rezFull.classes[`pt_${atr.p_id}`] != undefined ) {
- let to_id = `pt_${atr.p_id}`;
- if ( rezFull.classes[to_id].G_id != undefined ) {
- to_id = rezFull.classes[to_id].G_id[rezFull.classes[to_id].G_id.length-1];
- }
- rezFull.assoc[`${clId}_${to_id}_${atr.p_name}`] = {string:`${atr.p_name} (${roundCount(atr.cnt)})`, cnt:atr.cnt, p_name:atr.p_name, p_id:`p_${atr.p_id}`, from:clId, to:to_id, removed:false };
- atr.class_list2 = [to_id];
- }
- }
+ if (atr.class_list2 == undefined || atr.class_list2.length == 0) {
+ if (rezFull.classes[`pt_${atr.p_id}`] != undefined) {
+ let to_id = `pt_${atr.p_id}`;
+ if (rezFull.classes[to_id].G_id != undefined) {
+ to_id = rezFull.classes[to_id].G_id[rezFull.classes[to_id].G_id.length - 1];
+ }
+ rezFull.assoc[`${clId}_${to_id}_${atr.p_name}`] = { string: `${atr.p_name} (${roundCount(atr.cnt)})`, cnt: atr.cnt, p_name: atr.p_name, p_id: `p_${atr.p_id}`, from: clId, to: to_id, removed: false };
+ atr.class_list2 = [to_id];
+ }
+ }
atr.hasAssoc = hasAssoc;
}
- if ( atr.type == 'data' && atr.object_cnt > 0 && atr.cnt > 0 && atr.cnt_full > hideSmall && atr.object_cnt > classInfo.cnt*showEssent) {
- if ( rezFull.classes[`pt_${atr.p_id}`] != undefined ) {
- let to_id = `pt_${atr.p_id}`;
- if ( rezFull.classes[to_id].G_id != undefined ) {
- to_id = rezFull.classes[to_id].G_id[rezFull.classes[to_id].G_id.length-1];
- }
- rezFull.assoc[`${clId}_${to_id}_${atr.p_name}`] = {string:`${atr.p_name} (${roundCount(atr.cnt)})`, cnt:atr.cnt, p_name:atr.p_name, p_id:`p_${atr.p_id}`, from:clId, to:to_id, removed:false };
- atr.type = 'out';
- atr.object_cnt_dgr = atr.object_cnt;
- atr.hasAssoc = true;
- atr.class_list2 = [to_id];
- }
- }
+ if (atr.type == 'data' && atr.object_cnt > 0 && atr.cnt > 0 && atr.cnt_full > hideSmall && atr.object_cnt > classInfo.cnt * showEssent) {
+ if (rezFull.classes[`pt_${atr.p_id}`] != undefined) {
+ let to_id = `pt_${atr.p_id}`;
+ if (rezFull.classes[to_id].G_id != undefined) {
+ to_id = rezFull.classes[to_id].G_id[rezFull.classes[to_id].G_id.length - 1];
+ }
+ rezFull.assoc[`${clId}_${to_id}_${atr.p_name}`] = { string: `${atr.p_name} (${roundCount(atr.cnt)})`, cnt: atr.cnt, p_name: atr.p_name, p_id: `p_${atr.p_id}`, from: clId, to: to_id, removed: false };
+ atr.type = 'out';
+ atr.object_cnt_dgr = atr.object_cnt;
+ atr.hasAssoc = true;
+ atr.class_list2 = [to_id];
+ }
+ }
}
}
}
diff --git a/app/imports/custom/vq/client/templates/fragments.js b/app/imports/custom/vq/client/templates/fragments.js
index d762fcd0..42349bdc 100644
--- a/app/imports/custom/vq/client/templates/fragments.js
+++ b/app/imports/custom/vq/client/templates/fragments.js
@@ -18,7 +18,7 @@ function makeIsStandardProperty(standardProperties) {
}
// Creates an adjacency list (a list of relevant cpc_rels for each class)
-export async function getCPCAdj(weightByCPCsum, useBothClasses) {
+export async function getCPCAdj(weightByCPCsum, useBothClasses, SCHEMA_LEVEL_EDGE_WEIGHT = null) {
if (weightByCPCsum !== true && weightByCPCsum !== false) {console.error("getCPCAdj: weightByCPCsum is not true or false");}
if (useBothClasses !== true && useBothClasses !== false) {console.error("getCPCAdj: useBothClasses is not true or false");}
const CPCs = await dataShapes.callServerFunction("xx_getCPCInfo", {main: {}});
@@ -54,15 +54,14 @@ export async function getCPCAdj(weightByCPCsum, useBothClasses) {
const cnt = parseFloat(cpc.cnt);
// Calculate edge weight. Do not allow a weight greater than 0.9. Important in cases where class size is used and cpc count is high; especially if one of the classes is small -- weight can become > 1000.
if (useBothClasses === true) {
- let cpcWeight = cnt / classSizes.get(otherC) * cnt / classSizes.get(c);
- cpcWeight = Math.min(0.9, cpcWeight);
+ let cpcWeight = SCHEMA_LEVEL_EDGE_WEIGHT !== null ? SCHEMA_LEVEL_EDGE_WEIGHT : Math.min(0.9, cnt / classSizes.get(otherC) * cnt / classSizes.get(c));
adj.get(otherC).push({class: c, property: cpc.property_id, weight: cpcWeight, propDirection: "out"});
adj.get(c).push({class: otherC, property: cpc.property_id, weight: cpcWeight, propDirection: "in"});
}
// For one-directional weight (swapping the weights is also worth considering)
else {
- adj.get(otherC).push({class: c, property: cpc.property_id, weight: Math.min(0.9, cnt / classSizes.get(otherC)), propDirection: "out"});
- adj.get(c).push({class: otherC, property: cpc.property_id, weight: Math.min(0.9, cnt / classSizes.get(c)), propDirection: "in"});
+ adj.get(otherC).push({class: c, property: cpc.property_id, weight: SCHEMA_LEVEL_EDGE_WEIGHT !== null ? SCHEMA_LEVEL_EDGE_WEIGHT : Math.min(0.9, cnt / classSizes.get(otherC)), propDirection: "out"});
+ adj.get(c).push({class: otherC, property: cpc.property_id, weight: SCHEMA_LEVEL_EDGE_WEIGHT !== null ? SCHEMA_LEVEL_EDGE_WEIGHT : Math.min(0.9, cnt / classSizes.get(c)), propDirection: "in"});
}
});
@@ -164,20 +163,32 @@ async function getAdjFromCP(weightByCPCsum, useBothClasses) {
}
// Like getCPCAdj but unweighted, each (otherClass, direction) pair appears at most once per class
-// adj value: {neighbors: [{class, propDirection, property}], inCount, outCount, properties, standardRelCount, userDefinedRelCount}
-// standardRelCount/userDefinedRelCount split neighbors by whether the connecting property is in standardProperties (null -> all standard)
-export async function getCPCAdjSimple(standardProperties = null) {
+// adj value: {neighbors: [{class, propDirection, properties}], inCount, outCount, properties, standardRelCount, userDefinedRelCount}
+// standardRelCount/userDefinedRelCount split neighbors by whether the connecting property is in standardProperties (null -> all standard);
+// a neighbor with both standard and user-defined properties is counted in both.
+export async function getCPCAdjSimple(standardProperties = null, edgesInTriples = true, cntTransform = null, useInstanceCount = false) {
const CPCs = await dataShapes.callServerFunction("xx_getCPCInfo", {main: {}});
console.log("Returned CPCs from DSS");
const getProp = buildPropMetaGetter();
const isStandardProperty = makeIsStandardProperty(standardProperties);
+ const cpcPropIds = [...new Set(CPCs.data.map(r => r.property_id))];
+ console.log("getCPCAdjSimple: standardProperties =", standardProperties, "| sample CPC property_ids =", cpcPropIds.slice(0, 10), "| std matches =", standardProperties ? cpcPropIds.filter(id => standardProperties.map(Number).includes(Number(id))).length : "all (null)");
+
+ let classSizes = null;
+ if (useInstanceCount) {
+ const xxClasses = await dataShapes.callServerFunction("xx_getClassesSimple", {main: {}});
+ classSizes = new Map(xxClasses.data.map(obj => [obj.id, Number(obj.cnt) || 0]));
+ }
const adj = new Map();
- const seen = new Map();
+ const seen = new Map(); // cls -> Map
const ensure = (cls) => {
- if (!adj.has(cls)) { adj.set(cls, {neighbors: [], inCount: 0, outCount: 0, properties: new Map(), standardRelCount: 0, userDefinedRelCount: 0}); seen.set(cls, new Set()); }
+ if (!adj.has(cls)) { adj.set(cls, {neighbors: [], inCount: 0, outCount: 0, properties: new Map(), standardRelCount: 0, userDefinedRelCount: 0, instanceCount: classSizes ? (classSizes.get(cls) ?? 0) : 0}); seen.set(cls, new Map()); }
};
+ const transform = cntTransform ?? Math.log10;
+ const relInc = (cnt) => edgesInTriples ? transform(Math.max(1, Number(cnt) || 1)) : 1;
+
CPCs.data.forEach(cpc => {
const c = cpc.class_id;
const otherC = cpc.other_class_id;
@@ -191,18 +202,32 @@ export async function getCPCAdjSimple(standardProperties = null) {
const std = isStandardProperty(prop);
const outKey = `${c}|out`;
- if (!seen.get(otherC).has(outKey)) {
- seen.get(otherC).add(outKey);
- adj.get(otherC).neighbors.push({class: c, propDirection: "out", property: prop});
- adj.get(otherC).outCount++;
- if (std) adj.get(otherC).standardRelCount++; else adj.get(otherC).userDefinedRelCount++;
+ const outEntry = seen.get(otherC).get(outKey);
+ if (!outEntry) {
+ const nb = {class: c, propDirection: "out", properties: [prop]};
+ seen.get(otherC).set(outKey, {nb, hasStd: std, hasUserDef: !std});
+ adj.get(otherC).neighbors.push(nb);
+ adj.get(otherC).outCount += useInstanceCount ? (adj.get(c).instanceCount || 1) : 1;
+ if (std) adj.get(otherC).standardRelCount += relInc(cpc.cnt);
+ else adj.get(otherC).userDefinedRelCount += relInc(cpc.cnt);
+ } else if (!outEntry.nb.properties.some(p => p.id === prop.id)) {
+ outEntry.nb.properties.push(prop);
+ if (std && !outEntry.hasStd) { outEntry.hasStd = true; adj.get(otherC).standardRelCount += relInc(cpc.cnt); }
+ if (!std && !outEntry.hasUserDef) { outEntry.hasUserDef = true; adj.get(otherC).userDefinedRelCount += relInc(cpc.cnt); }
}
const inKey = `${otherC}|in`;
- if (!seen.get(c).has(inKey)) {
- seen.get(c).add(inKey);
- adj.get(c).neighbors.push({class: otherC, propDirection: "in", property: prop});
- adj.get(c).inCount++;
- if (std) adj.get(c).standardRelCount++; else adj.get(c).userDefinedRelCount++;
+ const inEntry = seen.get(c).get(inKey);
+ if (!inEntry) {
+ const nb = {class: otherC, propDirection: "in", properties: [prop]};
+ seen.get(c).set(inKey, {nb, hasStd: std, hasUserDef: !std});
+ adj.get(c).neighbors.push(nb);
+ adj.get(c).inCount += useInstanceCount ? (adj.get(otherC).instanceCount || 1) : 1;
+ if (std) adj.get(c).standardRelCount += relInc(cpc.cnt);
+ else adj.get(c).userDefinedRelCount += relInc(cpc.cnt);
+ } else if (!inEntry.nb.properties.some(p => p.id === prop.id)) {
+ inEntry.nb.properties.push(prop);
+ if (std && !inEntry.hasStd) { inEntry.hasStd = true; adj.get(c).standardRelCount += relInc(cpc.cnt); }
+ if (!std && !inEntry.hasUserDef) { inEntry.hasUserDef = true; adj.get(c).userDefinedRelCount += relInc(cpc.cnt); }
}
});
@@ -225,9 +250,9 @@ export async function getAdjFromCPSimple(standardProperties = null) {
});
const adj = new Map();
- const seen = new Map();
+ const seen = new Map(); // cls -> Map
const ensure = (cls) => {
- if (!adj.has(cls)) { adj.set(cls, {neighbors: [], inCount: 0, outCount: 0, properties: new Map(), standardRelCount: 0, userDefinedRelCount: 0}); seen.set(cls, new Set()); }
+ if (!adj.has(cls)) { adj.set(cls, {neighbors: [], inCount: 0, outCount: 0, properties: new Map(), standardRelCount: 0, userDefinedRelCount: 0}); seen.set(cls, new Map()); }
};
// For each property look at all possible triples c2->p->c1; only when both ends of the property are observed
@@ -245,18 +270,30 @@ export async function getAdjFromCPSimple(standardProperties = null) {
const std = isStandardProperty(prop);
const outKey = `${c1}|out`;
- if (!seen.get(c2).has(outKey)) {
- seen.get(c2).add(outKey);
- adj.get(c2).neighbors.push({class: c1, propDirection: "out", property: prop});
+ const outEntry = seen.get(c2).get(outKey);
+ if (!outEntry) {
+ const nb = {class: c1, propDirection: "out", properties: [prop]};
+ seen.get(c2).set(outKey, {nb, hasStd: std, hasUserDef: !std});
+ adj.get(c2).neighbors.push(nb);
adj.get(c2).outCount++;
if (std) adj.get(c2).standardRelCount++; else adj.get(c2).userDefinedRelCount++;
+ } else if (!outEntry.nb.properties.some(p => p.id === prop.id)) {
+ outEntry.nb.properties.push(prop);
+ if (std && !outEntry.hasStd) { outEntry.hasStd = true; adj.get(c2).standardRelCount++; }
+ if (!std && !outEntry.hasUserDef) { outEntry.hasUserDef = true; adj.get(c2).userDefinedRelCount++; }
}
const inKey = `${c2}|in`;
- if (!seen.get(c1).has(inKey)) {
- seen.get(c1).add(inKey);
- adj.get(c1).neighbors.push({class: c2, propDirection: "in", property: prop});
+ const inEntry = seen.get(c1).get(inKey);
+ if (!inEntry) {
+ const nb = {class: c2, propDirection: "in", properties: [prop]};
+ seen.get(c1).set(inKey, {nb, hasStd: std, hasUserDef: !std});
+ adj.get(c1).neighbors.push(nb);
adj.get(c1).inCount++;
if (std) adj.get(c1).standardRelCount++; else adj.get(c1).userDefinedRelCount++;
+ } else if (!inEntry.nb.properties.some(p => p.id === prop.id)) {
+ inEntry.nb.properties.push(prop);
+ if (std && !inEntry.hasStd) { inEntry.hasStd = true; adj.get(c1).standardRelCount++; }
+ if (!std && !inEntry.hasUserDef) { inEntry.hasUserDef = true; adj.get(c1).userDefinedRelCount++; }
}
});
});
@@ -502,6 +539,22 @@ function fmeasure(path, graph, alpha) {
return denom > 0 ? (RD * RC) / denom : 0;
}
+// Export class_id, class_name, weight for every class in the current filtered dataset.
+// Weight field matches the active sort parameter in #sortPar (cnt_sum / cnt / in_props / order).
+export function exportClassDatasetCSV() {
+ const classList = dataShapes.schema?.diagram?.filteredClassList ?? [];
+ const sortP = parseInt(document.getElementById("sortPar")?.value ?? "1");
+ const weightField = sortP === 3 ? "cnt" : sortP === 4 ? "in_props" : sortP === 2 ? "order" : "cnt_sum";
+
+ const rows = [["class_id", "class_name", "weight"]];
+ classList.forEach(cl => {
+ const name = cl.prefix ? `${cl.prefix}:${cl.display_name}` : (cl.display_name ?? cl.name ?? "");
+ rows.push([cl.id, name, cl[weightField] ?? ""]);
+ });
+ const schemaName = dataShapes.schema?.schemaName ?? "dataset";
+ downloadCSV(`class_dataset_${schemaName}.csv`, rows);
+}
+
// Build a CSV string from an array of rows and trigger a browser download, used only for analysis and testing
function downloadCSV(filename, rows) {
const csv = rows.map(row => row.map(cell => {
@@ -577,7 +630,7 @@ function subgraphConnectivity(fragClassIds, simpleAdj) {
return { components, largestSize, isolatedCount };
}
-export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, config = {}) {
+export async function computeBRPRelevance(mainClasses, config = {}, rawAdj = undefined) {
const mainClassBoost = 1;
const classWeightIncoming = config.classWeightIncoming ?? 0.3;
const classWeightOutgoing = 1 - classWeightIncoming;
@@ -585,27 +638,31 @@ export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, c
const propWeightUserDefined = 1 - propWeightStandart;
const beta = config.beta ?? 0.8;
const alpha = 1 - beta;
- const alphaF = 0.7;
+ const cntTransform = config.cntTransform ?? (x => x);
+ const useInstanceCount = config.useInstanceCount ?? false;
- let cpcListSimple = simpleAdj;
- if (cpcListSimple === undefined) {
- cpcListSimple = await getCPCAdjSimple(config.standardProperties ?? null);
+ let cpcListSimple = rawAdj;
+ if (!cpcListSimple) {
+ cpcListSimple = await getCPCAdjSimple(config.standardProperties ?? null, config.edgesInTriples, cntTransform, useInstanceCount);
if (cpcListSimple.size == 0) cpcListSimple = await getAdjFromCPSimple(config.standardProperties ?? null);
}
// Guard: with 0 or 1 classes, centrality formula divides by zero.
if (cpcListSimple.size <= 1) {
- const classes = [...cpcListSimple.keys()].slice(0, fragmentClassCount);
- return [classes, cpcListSimple];
+ const relevanceMap = new Map([...cpcListSimple].map(([k]) => [k, 0]));
+ return { cpcListSimple, relevanceMap };
}
+ // Max rels is the max amount one class has in the entire schema
let maxStandardRels = 0, maxUserDefinedRels = 0;
cpcListSimple.forEach((obj) => {
if (obj.standardRelCount > maxStandardRels) maxStandardRels = obj.standardRelCount;
if (obj.userDefinedRelCount > maxUserDefinedRels) maxUserDefinedRels = obj.userDefinedRelCount;
});
+ console.log("Max standart rels are ", maxStandardRels, "Max user defined rels are ", maxUserDefinedRels);
// Centrality(Cn) = (WI*CI + WO*CO) * (ns*ws/maxs + nud*wud/maxud) / (|C| - 1)
+ const totalInstances = [...cpcListSimple.values()].reduce((sum, obj) => sum + (obj.instanceCount ?? 0), 0);
cpcListSimple.forEach((obj, classId) => {
if (mainClasses.includes(classId)) {
obj.centralityMeasure = mainClassBoost;
@@ -613,27 +670,98 @@ export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, c
const propTerm =
(maxStandardRels > 0 ? (obj.standardRelCount * propWeightStandart) / maxStandardRels : 0) +
(maxUserDefinedRels > 0 ? (obj.userDefinedRelCount * propWeightUserDefined) / maxUserDefinedRels : 0);
+ const divisor = totalInstances > 0
+ ? (totalInstances - (obj.instanceCount ?? 0))
+ : (cpcListSimple.size - 1);
obj.centralityMeasure =
- ((classWeightIncoming * obj.inCount) + (classWeightOutgoing * obj.outCount)) * propTerm / (cpcListSimple.size - 1);
+ ((classWeightIncoming * obj.inCount) + (classWeightOutgoing * obj.outCount)) * propTerm / divisor;
}
});
- // Closeness: weighted average of others' centrality, weighted by 1/distance
+ const closenessMode = config.closenessMode ?? 'centralityBased';
+ const N = cpcListSimple.size;
+ let nodeWeightMap = null;
+ if (closenessMode === 'weightBased') {
+ const classList = (dataShapes.schema?.diagram?.classList) ?? [];
+ nodeWeightMap = new Map(classList.map(obj => [obj.id, Number(obj.cnt_sum ?? 0)]));
+ }
+
+ // Closeness
cpcListSimple.forEach((objCn, Cn) => {
const dist = bfsUndirected(Cn, cpcListSimple);
- let num = 0, den = 0;
- dist.forEach((d, n) => {
- if (n === Cn || d === 0) return;
- num += cpcListSimple.get(n).centralityMeasure / (d * d);
- den += 1 / d;
- });
- objCn.closeness = den > 0 ? num / den : 0;
+ if (closenessMode === 'unweighted') {
+ let sumD = 0;
+ dist.forEach((d, n) => {
+ if (n === Cn || d === 0) return;
+ sumD += d;
+ });
+ objCn.closeness = sumD > 0 ? (N - 1) / sumD : 0;
+ } else {
+ let num = 0, den = 0;
+ dist.forEach((d, n) => {
+ if (n === Cn || d === 0) return;
+ const w = closenessMode === 'weightBased'
+ ? (nodeWeightMap?.get(n) ?? 0)
+ : cpcListSimple.get(n).centralityMeasure;
+ num += w / d;
+ den += 1 / d;
+ });
+ objCn.closeness = den > 0 ? num / den : 0;
+ }
});
+ // weightBased closeness is a weighted avg of raw cnt_sum values → can be huge; normalize to [0,1]
+ if (closenessMode === 'weightBased') {
+ let maxCloseness = 0;
+ cpcListSimple.forEach(obj => { if (obj.closeness > maxCloseness) maxCloseness = obj.closeness; });
+ if (maxCloseness > 0) cpcListSimple.forEach(obj => { obj.closeness /= maxCloseness; });
+ }
+
// Relevance: weighted combination of centrality and closeness; alpha + beta = 1
cpcListSimple.forEach((obj) => {
obj.relevance = beta * obj.centralityMeasure + alpha * obj.closeness;
});
+ console.log("CPC Adjacency list for BRP: ", cpcListSimple);
+
+ const relevanceMap = new Map([...cpcListSimple].map(([k, v]) => [k, v.relevance]));
+ return { cpcListSimple, relevanceMap };
+}
+
+/**
+ * config (all fields optional):
+ *
+ * classWeightIncoming {number} 0.3 Weight of incoming rels in centrality; outgoing = 1 - classWeightIncoming
+ * propWeightStandart {number} 0.2 Weight of standard properties in centrality; user-defined = 1 - propWeightStandart
+ * beta {number} 0.8 Centrality share of relevance score; closeness share = 1 - beta
+ * closenessMode {string} 'centralityBased' How to weight BFS closeness:
+ * 'centralityBased' — weight neighbours by their centrality
+ * 'weightBased' — weight by class cnt_sum from the schema
+ * 'unweighted' — plain shortest-path distance sums
+ *
+ * The following are ignored when simpleAdj is provided:
+ * cntTransform {Function} Math.log10 Applied to triple counts when building the adj list;
+ * supported: Math.log10, Math.log2, Math.sqrt, x => x (full/identity)
+ * useInstanceCount {boolean} false Use class instance count instead of class count for adj list and centrality divisor
+ * standardProperties {number[]} null Property IDs treated as standard; null = all properties standard
+ * edgesInTriples {boolean} true Scale adj list edge weights by triple count; false = each unique (neighbour, direction) pair counts as 1
+ */
+export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, config = {edgesInTriples: true}) {
+ console.log("fragmentsBRP config:", {...config, cntTransform: config.cntTransform ? (config.cntTransform.name || 'anonymous') : null});
+ const alphaF = 0.5;
+ const log = true;
+
+ let cpcListSimple;
+ if (config.preCalcAdj) {
+ cpcListSimple = config.preCalcAdj;
+ } else {
+ ({ cpcListSimple } = await computeBRPRelevance(mainClasses, config, simpleAdj));
+ }
+
+ // Guard: BRP loop requires at least 2 nodes.
+ if (cpcListSimple.size <= 1) {
+ const classes = [...cpcListSimple.keys()].slice(0, fragmentClassCount);
+ return [classes, cpcListSimple];
+ }
// --- Broaden Relevant Paths (BRP) ---
@@ -652,24 +780,31 @@ export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, c
let resultPath = null;
while (true) {
- if (NodeSet.length === 0) break;
+ if (NodeSet.length === 0) {
+ if (log) console.log("Stopped because NODESet is empty");
+ break;
+ }
// Stop condition: the best path has reached the requested size, pathSet is kept
// sorted by f-measure desc, so PathSet[0] is both the largest-ready and best path
if (PathSet.length > 0 && PathSet[0].size >= fragmentClassCount) {
resultPath = PathSet[0];
+ if (log) console.log("Stopped because we have created a fragment with max size");
break;
}
// Pick Cr -- AdjacentNodes' head wins only if its relevance is strictly higher than NodeSet's head
let Cr;
- if (AdjacentNodes.length > 0 && AdjacentNodes[0][1].relevance > NodeSet[0][1].relevance) {
+ if (AdjacentNodes.length > 0 && AdjacentNodes[0][1].relevance >= NodeSet[0][1].relevance) {
Cr = AdjacentNodes.shift();
+ if (log) console.log("Cr from ADJACENTNodes is ", JSON.stringify(Cr[0]));
removeFromList(NodeSet, Cr[0]);
} else {
Cr = NodeSet.shift();
+ if (log) console.log("Cr from NODESet is ", JSON.stringify(Cr[0]));
removeFromList(AdjacentNodes, Cr[0]);
}
+ if (log) console.log("NodeSet ", NodeSet.length, "AdjacentNodes ", AdjacentNodes.length);
// Insert Cr into PathSet, if Cr connects to existing paths, merge them with Cr into one path
const connected = [];
@@ -686,6 +821,7 @@ export async function fragmentsBRP(mainClasses, fragmentClassCount, simpleAdj, c
PathSet.push(new Map([Cr]));
}
inPathSet.add(Cr[0]);
+ if (log) console.log("PathSet looks like ", PathSet);
// Keep PathSet ordered by f-measure desc so PathSet[0] is always the best path
const pathF = new Map(PathSet.map(p => [p, fmeasure(p, cpcListSimple, alphaF)]));
@@ -843,6 +979,136 @@ export async function exportBRPAnalysisCSV(paramRanges = {}, fragSizes = [10, 20
console.log(`Wrote brp_runs.csv (${runsRows.length - 1} rows) and brp_fragment_classes.csv (${classRows.length - 1} rows).`);
}
+// brpConfigs: each key may be a scalar or array. Arrays are expanded into a full cross-product of BRP variants.
+// cntTransform values must be {name: string, fn: Function} objects so the name can appear in CSV output.
+// standardProperties is always a single value (not varied).
+export async function exportCSVBRPandPPRComparison(mainClasses, pprEdgeWeightContexts = [""], brpConfigs = {}, fragSizes = [10, 20, 30, 40, 50]) {
+ const pprAlpha = 0.85;
+ const pprTol = 1e-5;
+ const xxClasses = await dataShapes.callServerFunction("xx_getClassesSimple", {main: {}});
+ const localClassNames = new Map(xxClasses.data.map(obj => [obj.id, `${obj.ns_name}:${obj.class_name}`]));
+
+ const standardProperties = brpConfigs.standardProperties ?? null;
+ const standardPropCol = standardProperties ? standardProperties.join(";") : "";
+
+ // Normalize a config value (scalar or array) to an array, using def if absent.
+ const toArr = (val, def) => (val === undefined || val === null) ? [def] : (Array.isArray(val) ? val : [val]);
+
+ // Normalize a cntTransform value to {name, fn}. Accepts {name, fn} or a plain function.
+ const normTransform = (v) => {
+ if (v && typeof v === "object" && typeof v.fn === "function") return v;
+ if (typeof v === "function") return {name: "custom", fn: v};
+ return {name: "identity", fn: x => x};
+ };
+
+ const edgesInTriplesArr = toArr(brpConfigs.edgesInTriples, true);
+ const cntTransformArr = toArr(brpConfigs.cntTransform, null).map(normTransform);
+ const useInstanceCountArr = toArr(brpConfigs.useInstanceCount, false);
+ const classWeightArr = toArr(brpConfigs.classWeightIncoming, 0.3);
+ const betaArr = toArr(brpConfigs.beta, 0.8);
+ const propWeightArr = toArr(brpConfigs.propWeightStandart, 0.2);
+ const closenessModeArr = toArr(brpConfigs.closenessMode, 'centralityBased');
+
+ // Full cross-product over all variant axes.
+ const cartesian = (...arrs) => arrs.reduce((acc, arr) => acc.flatMap(x => arr.map(y => [...x, y])), [[]]);
+ const brpVariants = cartesian(edgesInTriplesArr, cntTransformArr, useInstanceCountArr, classWeightArr, betaArr, propWeightArr, closenessModeArr)
+ .map(([edgesInTriples, cntTransform, useInstanceCount, classWeightIncoming, beta, propWeightStandart, closenessMode]) => ({
+ edgesInTriples, cntTransform, useInstanceCount, classWeightIncoming, beta, propWeightStandart, closenessMode, standardProperties,
+ }));
+
+ // Cache BRP adj by the parameters that affect graph construction.
+ const brpAdjCache = new Map();
+ const getBRPAdj = async (v) => {
+ const key = `${v.edgesInTriples}|${v.cntTransform.name}|${v.useInstanceCount}|${standardPropCol}`;
+ if (!brpAdjCache.has(key)) {
+ let adj = await getCPCAdjSimple(v.standardProperties, v.edgesInTriples, v.cntTransform.fn, v.useInstanceCount);
+ if (adj.size === 0) adj = await getAdjFromCPSimple(v.standardProperties);
+ brpAdjCache.set(key, adj);
+ }
+ return brpAdjCache.get(key);
+ };
+
+ // Cache PPR adj per edge-weight context.
+ const pprAdjCache = new Map();
+ const getPPRAdj = async (ctx) => {
+ if (!pprAdjCache.has(ctx)) {
+ if (ctx === "no-ctx") {
+ pprAdjCache.set(ctx, await getCPCAdj(true, true, 1));
+ } else {
+ const [w, ub] = getBoolsFromEdgeWeightContext(ctx);
+ pprAdjCache.set(ctx, await getCPCAdj(w, ub));
+ }
+ }
+ return pprAdjCache.get(ctx);
+ };
+
+ const getAllSubsets = (arr) => {
+ const result = [];
+ for (let mask = 1; mask < (1 << arr.length); mask++) {
+ result.push(arr.filter((_, i) => mask & (1 << i)));
+ }
+ return result.sort((a, b) => a.length - b.length);
+ };
+ const mainClassCombos = getAllSubsets(mainClasses);
+
+ const brpRunsRows = [["run_id", "edges_in_triples", "cnt_transform", "use_instance_count", "class_weight_incoming", "beta", "prop_weight_standart", "closeness_mode", "frag_size", "fragment_size_actual", "main_class_ids"]];
+ const brpClassRows = [["run_id", "class_id", "class_name", "is_main_class", "centrality", "closeness", "relevance", "relation_relevance", "standard_properties"]];
+ const pprRunsRows = [["run_id", "alpha", "edge_weight_context", "frag_size", "fragment_size_actual", "main_class_ids"]];
+ const pprClassRows = [["run_id", "class_id", "class_name", "is_main_class", "rank", "standard_properties"]];
+
+ // PPR runs are independent of BRP variants — run once per combo × fragSize × context.
+ for (let comboIdx = 0; comboIdx < mainClassCombos.length; comboIdx++) {
+ const mainClassCombo = mainClassCombos[comboIdx];
+ const mainClassesSet = new Set(mainClassCombo);
+ const mainClassIds = mainClassCombo.join(";");
+
+ for (const fragSize of fragSizes) {
+ for (let ctxIdx = 0; ctxIdx < pprEdgeWeightContexts.length; ctxIdx++) {
+ const edgeWeightContext = pprEdgeWeightContexts[ctxIdx];
+ const pprAdj = await getPPRAdj(edgeWeightContext);
+ const pprRunId = `ppr_c${comboIdx}_ctx${ctxIdx}_s${fragSize}`;
+ const [pprFrag, pprRank] = await fragmentsPPR(mainClassCombo, fragSize, pprAlpha, pprTol, pprAdj);
+ pprRunsRows.push([pprRunId, pprAlpha, edgeWeightContext, fragSize, pprFrag.length, mainClassIds]);
+ pprFrag.forEach(id => {
+ pprClassRows.push([pprRunId, id, localClassNames.get(id) ?? "", mainClassesSet.has(id) ? 1 : 0, pprRank.get(id) ?? 0, standardPropCol]);
+ });
+ console.log(`PPR c${comboIdx} ctx${ctxIdx} s${fragSize} (alpha=${pprAlpha}, ctx=${edgeWeightContext}): size ${pprFrag.length}`);
+ }
+ }
+ }
+
+ // BRP runs: one pass per variant × combo × fragSize.
+ for (let variantIdx = 0; variantIdx < brpVariants.length; variantIdx++) {
+ const v = brpVariants[variantIdx];
+ const simpleAdj = await getBRPAdj(v);
+ const variantConfig = {...v, cntTransform: v.cntTransform.fn};
+
+ for (let comboIdx = 0; comboIdx < mainClassCombos.length; comboIdx++) {
+ const mainClassCombo = mainClassCombos[comboIdx];
+ const mainClassesSet = new Set(mainClassCombo);
+ const mainClassIds = mainClassCombo.join(";");
+
+ for (const fragSize of fragSizes) {
+ const brpRunId = `brp_v${variantIdx}_c${comboIdx}_s${fragSize}`;
+ const [brpFrag, ] = await fragmentsBRP(mainClassCombo, fragSize, simpleAdj, variantConfig);
+ brpRunsRows.push([brpRunId, v.edgesInTriples, v.cntTransform.name, v.useInstanceCount, v.classWeightIncoming, v.beta, v.propWeightStandart, v.closenessMode, fragSize, brpFrag.length, mainClassIds]);
+ brpFrag.forEach(id => {
+ const entry = simpleAdj.get(id);
+ if (!entry) return;
+ brpClassRows.push([brpRunId, id, localClassNames.get(id) ?? "", mainClassesSet.has(id) ? 1 : 0, entry.centralityMeasure, entry.closeness, entry.relevance, entry.relationRelevance ?? "", standardPropCol]);
+ });
+ console.log(`BRP v${variantIdx} (edges=${v.edgesInTriples}, transform=${v.cntTransform.name}, instances=${v.useInstanceCount}) c${comboIdx} s${fragSize}: size ${brpFrag.length}`);
+ }
+ }
+ }
+
+ downloadCSV("brp_runs.csv", brpRunsRows);
+ downloadCSV("brp_fragment_classes.csv", brpClassRows);
+ downloadCSV("ppr_runs.csv", pprRunsRows);
+ downloadCSV("ppr_fragment_classes.csv", pprClassRows);
+ console.log(`Wrote brp_runs.csv (${brpRunsRows.length - 1} rows), brp_fragment_classes.csv (${brpClassRows.length - 1} rows), ppr_runs.csv (${pprRunsRows.length - 1} rows), ppr_fragment_classes.csv (${pprClassRows.length - 1} rows)`);
+}
+
// Compare fragments calculated by various algorithms by calculating the fraction of common classes; uses each of selected classes as a main class. Currently always uses CPC rels.
export async function compareFragmentAlgorithmsIntersection(edgeWeightContext) {
let [weightByCPCsum, useBothClasses] = getBoolsFromEdgeWeightContext(edgeWeightContext);