Skip to content

Instantly share code, notes, and snippets.

@btmxh
Created July 22, 2025 17:09
Show Gist options
  • Save btmxh/ac82621ee1980bb87a2e04d40d1ca7f9 to your computer and use it in GitHub Desktop.
Save btmxh/ac82621ee1980bb87a2e04d40d1ca7f9 to your computer and use it in GitHub Desktop.
Exact new-generation NRS solver
import { lusolve } from "npm:mathjs";
const path = "/home/torani/dev/nrs-impl/output/bulk.json";
const data = JSON.parse(await Deno.readTextFile(path));
const ids = Object.keys(data.entries);
const factors = [
"AU",
"AP",
"MU",
"MP",
"CU",
"CP",
"AL",
"AV",
"AM",
"B",
"A",
];
const weights = [
0.3,
0.4,
0.35,
0.35,
0.4,
0.5,
0.4,
0.1,
0.3,
0.05,
1.0,
];
const embed = (vector) => {
return vector.map((score, i) => Math.pow(score, 1 / weights[i]));
};
const unembed = (vector) => {
return vector.map((score, i) => Math.pow(score, weights[i]));
};
const vecsum = (vectors) => {
return vectors.reduce(
(a, b) => a.map((value, i) => value + b[i]),
factors.map(() => 0.0),
);
};
const vecmul = (a, b) => {
return a.map((av, i) => av * b[i]);
};
const vec = (a) => {
if (typeof a === "number") {
return factors.map(() => a);
}
return [...a];
};
const overall = (vector) => {
// lp norm of vector[:6] with p = 1/0.6
let emotion = 0.0;
for (let i = 0; i < 6; ++i) {
if (vector[i] > 1e-4) {
emotion += Math.pow(vector[i], 1 / 0.6);
}
}
if (emotion > 1e-4) emotion = Math.pow(emotion, 0.6);
// p = 0.7 for the next 3 elems
let art = 0.0;
for (let i = 6; i < 9; ++i) {
if (vector[i] > 1e-4) {
art += Math.pow(vector[i], 1 / 0.7);
}
}
if (art > 1e-4) {
art = Math.pow(art, 0.7);
}
return emotion + art + vector[9] + vector[10];
};
const calc = (cb) => {
console.log("Started");
const processVector = (matrix) => {
// if matrix is number then return matrix
if (typeof matrix === "number") {
return factors.map(() => matrix);
}
const diagonal = factors.map((factor) => matrix[factor] ?? 0.0);
if (
Object.keys(matrix).filter((key) => factors.indexOf(key) < 0).length > 0
) {
console.debug(matrix);
throw new Error("Dense matrices are not supported");
}
return diagonal;
};
const readjustImpact = (impact) => {
impact.score = Object.fromEntries(
Object.entries(impact.score).map((
[key, value],
) => [key, cb(value)]),
);
};
for (const id of ids) {
const entry = data.entries[id];
for (const [childId, weight] of Object.entries(entry.children)) {
const diagWeight = processVector(weight);
}
if (Object.keys(entry.children).length > 0) {
const relation = {
contributors: { [id]: 1 },
references: entry.children,
DAH_meta: {},
};
data.relations.push(relation);
}
}
const impacts = [];
for (const impact of data.impacts) {
readjustImpact(impact);
const contributors = impact.contributors;
const score = processVector(impact.score);
const processedImpact = {
contributors,
score,
};
impacts.push(processedImpact);
}
const relations = [];
for (const relation of data.relations) {
const contributors = relation.contributors;
const references = relation.references;
const processedRelation = {
contributors,
references,
};
relations.push(processedRelation);
}
console.log("Done processing");
function tarjansSCC(graph) {
let index = 0;
const stack = [];
const indices = new Map();
const lowlink = new Map();
const onStack = new Set();
const sccs = [];
function dfs(v) {
indices.set(v, index);
lowlink.set(v, index);
index++;
stack.push(v);
onStack.add(v);
for (const w of graph[v] || []) {
if (!indices.has(w)) {
dfs(w);
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
} else if (onStack.has(w)) {
lowlink.set(v, Math.min(lowlink.get(v), indices.get(w)));
}
}
if (lowlink.get(v) === indices.get(v)) {
const scc = [];
let w;
do {
w = stack.pop();
onStack.delete(w);
scc.push(w);
} while (w !== v);
sccs.push(scc);
}
}
for (const v in graph) {
if (!indices.has(v)) {
dfs(v);
}
}
return sccs;
}
function topoSort(graph) {
const visited = new Set();
const temp = new Set(); // for cycle detection (optional)
const result = [];
function dfs(v) {
if (visited.has(v)) return;
if (temp.has(v)) throw new Error("Graph is not a DAG (contains cycle)");
temp.add(v);
for (const neighbor of graph[v] || []) {
dfs(neighbor);
}
temp.delete(v);
visited.add(v);
result.push(v);
}
for (const v in graph) {
if (!visited.has(v)) {
dfs(v);
}
}
result.reverse(); // reverse post-order
return result;
}
const graph = {};
for (const id of ids) {
graph[id] = [];
}
for (const relation of relations) {
for (const contributor of Object.keys(relation.contributors)) {
for (const reference of Object.keys(relation.references)) {
graph[reference]?.push(contributor);
}
}
}
for (const id of ids) {
graph[id] = [...new Set(graph[id])];
}
let sccs = tarjansSCC(graph);
console.debug(
ids.length,
sccs.length,
);
const idsSet = new Set(ids);
for (let i = 0; i < sccs.length; i++) {
sccs[i] = sccs[i].filter((id) => idsSet.has(id));
}
sccs = sccs.filter((scc) => scc.length > 0);
const condensedGraph = {};
for (let i = 0; i < sccs.length; i++) {
condensedGraph[i.toString()] = [];
}
const idToScc = new Map();
for (let i = 0; i < sccs.length; i++) {
for (const id of sccs[i]) {
idToScc.set(id, i.toString());
}
}
for (const id of ids) {
for (const neighbor of graph[id]) {
const fromScc = idToScc.get(id);
const toScc = idToScc.get(neighbor);
if (fromScc !== toScc && toScc !== undefined) {
condensedGraph[fromScc].push(toScc);
}
}
}
for (let i = 0; i < sccs.length; i++) {
condensedGraph[i] = [...new Set(condensedGraph[i])];
}
const idxOrder = topoSort(condensedGraph);
const order = idxOrder.map((i) => sccs[parseInt(i)]);
const scored = new Map();
for (const scc of order) {
const sccConstantScores = [];
// constant pass
for (const id of scc) {
const affectedImpacts = [];
const affectedRelations = [];
for (const impact of impacts) {
if (impact.contributors[id] !== undefined) {
affectedImpacts.push(impact);
}
}
for (const relation of relations) {
if (
relation.contributors[id] !== undefined
) {
affectedRelations.push(relation);
for (const ref of Object.keys(relation.references)) {
if (!scored.has(ref) && scc.indexOf(ref) < 0) {
throw new Error("hello");
}
}
}
}
// score
const constantScores = [];
for (const affectedImpact of affectedImpacts) {
let score = vec(affectedImpact.score);
let weight = vec(affectedImpact.contributors[id]);
constantScores.push(vecmul(embed(score), weight));
}
for (const affectedRelation of affectedRelations) {
for (
const [ref, weight] of Object.entries(affectedRelation.references)
) {
if (scored.has(ref)) {
const refScore = scored.get(ref);
const weightVec = vecmul(
vec(processVector(weight)),
vec(processVector(affectedRelation.contributors[id])),
);
constantScores.push(vecmul(refScore, weightVec));
}
}
}
const constantScore = vecsum(constantScores);
sccConstantScores.push(constantScore);
}
const idToIdx = new Map();
for (let i = 0; i < scc.length; i++) {
idToIdx.set(scc[i], i);
}
const mat = [];
const n = scc.length;
for (let j = 0; j < factors.length; j++) {
mat[j] = [];
for (let i = 0; i < n; i++) {
mat[j].push(new Array(n).fill(0));
}
}
for (const id of scc) {
const affectedRelations = [];
for (const relation of relations) {
if (
relation.contributors[id] !== undefined
) {
affectedRelations.push(relation);
}
}
const i = idToIdx.get(id);
for (let k = 0; k < factors.length; ++k) {
mat[k][i][i] = 1;
}
if (n > 2) {
}
for (const affectedRelation of affectedRelations) {
for (
const [ref, weight] of Object.entries(affectedRelation.references)
) {
if (!scored.has(ref)) {
const weightVec = vecmul(
vec(processVector(weight)),
vec(processVector(affectedRelation.contributors[id])),
);
const j = idToIdx.get(ref);
for (let k = 0; k < factors.length; ++k) {
mat[k][i][j] = -weightVec[k];
}
}
}
}
}
const scores = [];
for (const id of scc) {
scores.push(factors.map(() => 0.0));
}
for (let k = 0; k < factors.length; ++k) {
const c = sccConstantScores.map((cs) => cs[k]);
const x = lusolve(mat[k], c).flatMap((r) => r);
for (let i = 0; i < scc.length; ++i) {
scores[i][k] = x[i];
}
}
// console.debug(scores);
for (const id of scc) {
scored.set(id, scores[idToIdx.get(id)]);
}
}
for (const id of ids) {
scored.set(id, unembed(scored.get(id)));
}
return Object.fromEntries(scored);
};
const positive = calc((value) => Math.max(value, 0.0));
const negative = calc((value) => -Math.min(value, 0.0));
const total = {};
for (const id of ids) {
total[id] = positive[id].map((value, i) => value - negative[id][i]);
}
console.debug(
Object.entries(total).map(([a, b]) => [a, overall(b)]).toSorted((a, b) =>
b[1] - a[1]
),
);
console.log("Done");
@btmxh
Copy link
Author

btmxh commented Jul 23, 2025

absolute cinema

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment