Render multiple edge labels

WIP: Forum propagation
This commit is contained in:
Ladd Hoffman 2023-01-29 23:43:30 -06:00
parent 721718ac13
commit 3cf24c6fa1
7 changed files with 173 additions and 18 deletions

View File

@ -50,3 +50,9 @@ At a minimum this can be a list where each item includes the identifier of the c
Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post. Each validation pool can then keep a record of the reputation staked by each voter, and the identifier of the corresponding post.
--- ---
So: If some incoming reputation cant be propagated due to a constraint like this, the question is what should happen with that unspent power. I think right now, I have it so when that limit is reached on leaching from PostA, the extra reputation accrues evenly (because of +0.5 citation) equally to PostX and PostZ.
Intuitively, we might want less value accruing to PostX, as it seems intended as a small utility post to transfer power.
However, we can combine these mechanisms. PostX

View File

@ -53,7 +53,7 @@ export class Forum extends ReputationHolder {
this.posts = new WDAG(scene); this.posts = new WDAG(scene);
this.actions = { this.actions = {
addPost: new Action('add post', scene), addPost: new Action('add post', scene),
propagateValue: new Action('propagate value', this.scene), propagateValue: new Action('propagate', this.scene),
transfer: new Action('transfer', this.scene), transfer: new Action('transfer', this.scene),
}; };
} }
@ -122,7 +122,7 @@ export class Forum extends ReputationHolder {
bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId); bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId);
const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey); const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey);
const value = bench.reputation.valueOf(post.tokenId); const value = bench.reputation.valueOf(post.tokenId);
this.actions.transfer.log(this, toActor, `(value: ${value})`); this.actions.transfer.log(this, toActor, `(${value})`);
this.deactivate(); this.deactivate();
} }
@ -146,13 +146,12 @@ export class Forum extends ReputationHolder {
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) { if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
let totalOutboundAmount = 0; let totalOutboundAmount = 0;
let totalRefundFromOutbound = 0;
for (const citationEdge of postVertex.getEdges(CITATION, true)) { for (const citationEdge of postVertex.getEdges(CITATION, true)) {
const { to: citedPostVertex, weight } = citationEdge; const { to: citedPostVertex, weight } = citationEdge;
let outboundAmount = weight * increment; let outboundAmount = weight * increment;
const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.weight || 0; const balanceEdge = this.posts.getEdge(BALANCE, postVertex, citedPostVertex);
console.log('Citation', { const balance = balanceEdge?.weight ?? 0;
citationEdge, outboundAmount, balance,
});
// We need to ensure that we propagate no more reputation than we leached // We need to ensure that we propagate no more reputation than we leached
if (depth > 0 && weight < 0) { if (depth > 0 && weight < 0) {
outboundAmount = outboundAmount < 0 outboundAmount = outboundAmount < 0
@ -164,10 +163,34 @@ export class Forum extends ReputationHolder {
increment: outboundAmount, increment: outboundAmount,
depth: depth + 1, depth: depth + 1,
}); });
totalRefundFromOutbound += Math.abs(refundFromOutbound);
outboundAmount -= refundFromOutbound; outboundAmount -= refundFromOutbound;
this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount); this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
totalOutboundAmount += outboundAmount; totalOutboundAmount += outboundAmount;
} }
// Now that we know what value could not be propagated as negative citations,
// Let's see what happens if we redistribute it among the positive citations :)
const positiveCitations = postVertex.getEdges(CITATION, true).filter(({ weight }) => weight > 0);
const totalPositiveCitationWeight = positiveCitations.reduce((total, { weight }) => total += weight, 0);
for (const citationEdge of positiveCitations) {
const { to: citedPostVertex, weight } = citationEdge;
const outboundAmount = totalRefundFromOutbound * (weight / totalPositiveCitationWeight);
const balance = this.posts.getEdgeWeight(BALANCE, postVertex, citedPostVertex) ?? 0;
await this.propagateValue(citationEdge, {
rewardsAccumulator,
increment: outboundAmount,
depth: depth + 1,
});
this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
totalOutboundAmount += outboundAmount;
}
// And what if they don't have any positive citations? The value should just accrue to this post.
increment -= totalOutboundAmount * params.leachingValue; increment -= totalOutboundAmount * params.leachingValue;
} }

View File

@ -114,6 +114,10 @@ export class Scene {
activationBorderColor: '#569595', activationBorderColor: '#569595',
}, },
}); });
this.options = {
edgeNodeColor: '#4d585c',
};
} }
withSequenceDiagram() { withSequenceDiagram() {
@ -131,6 +135,7 @@ export class Scene {
const logBox = this.box.addBox('Flowchart text').addClass('dim'); const logBox = this.box.addBox('Flowchart text').addClass('dim');
this.flowchart = new MermaidDiagram(box, logBox); this.flowchart = new MermaidDiagram(box, logBox);
this.flowchart.log(`graph ${direction}`, false); this.flowchart.log(`graph ${direction}`, false);
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
return this; return this;
} }
@ -143,6 +148,7 @@ export class Scene {
const logBox = container.addBox('Flowchart text').addClass('dim'); const logBox = container.addBox('Flowchart text').addClass('dim');
const flowchart = new MermaidDiagram(box, logBox); const flowchart = new MermaidDiagram(box, logBox);
flowchart.log(`graph ${direction}`, false); flowchart.log(`graph ${direction}`, false);
this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false);
this.flowcharts.set(id, flowchart); this.flowcharts.set(id, flowchart);
return this; return this;
} }

View File

@ -265,7 +265,7 @@ export class ValidationPool extends ReputationHolder {
// Transfer ownership of the minted token, from the pool to the forum // Transfer ownership of the minted token, from the pool to the forum
this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId); this.bench.reputation.transferFrom(this.id, this.forum.id, this.tokenId);
const value = this.bench.reputation.valueOf(this.tokenId); const value = this.bench.reputation.valueOf(this.tokenId);
this.actions.transfer.log(this, this.forum, `(value: ${value})`); this.actions.transfer.log(this, this.forum, `(${value})`);
// Recurse through forum to determine reputation effects // Recurse through forum to determine reputation effects
await this.forum.onValidate({ await this.forum.onValidate({

View File

@ -70,6 +70,19 @@ export class WDAG {
return Array.from(this.vertices.values()).map(({ data }) => data); return Array.from(this.vertices.values()).map(({ data }) => data);
} }
getEdgeLabel(label) {
return this.edgeLabels.get(label);
}
getOrCreateEdgeLabel(label) {
let edges = this.getEdgeLabel(label);
if (!edges) {
edges = new Map();
this.edgeLabels.set(label, edges);
}
return edges;
}
getEdge(label, from, to) { getEdge(label, from, to) {
from = from instanceof Vertex ? from : this.getVertex(from); from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to); to = to instanceof Vertex ? to : this.getVertex(to);
@ -77,16 +90,41 @@ export class WDAG {
return edges?.get(JSON.stringify([from.id, to.id])); return edges?.get(JSON.stringify([from.id, to.id]));
} }
setEdge(label, from, to, edge) { getEdgeWeight(label, from, to) {
return this.getEdge(label, from, to)?.weight;
}
static getEdgeKey({ from, to }) {
return btoa([from.id, to.id]).replaceAll(/[^A-Z]+/g, '');
}
getEdgeHtml({ from, to }) {
let html = '<table>';
for (const { label, weight } of this.getEdges(null, from, to)) {
html += `<tr><td>${label}</td><td>${weight}</td></tr>`;
}
html += '</table>';
return html;
}
getEdgeFlowchartNode(edge) {
const edgeKey = WDAG.getEdgeKey(edge);
return `${edgeKey}(${this.getEdgeHtml(edge)}):::edge`;
}
setEdgeWeight(label, from, to, weight) {
from = from instanceof Vertex ? from : this.getVertex(from); from = from instanceof Vertex ? from : this.getVertex(from);
to = to instanceof Vertex ? to : this.getVertex(to); to = to instanceof Vertex ? to : this.getVertex(to);
edge = typeof edge === 'number' ? new Edge(label, from, to, edge) : edge; const edge = new Edge(label, from, to, weight);
let edges = this.edgeLabels.get(label); let edges = this.edgeLabels.get(label);
if (!edges) { if (!edges) {
edges = new Map(); edges = new Map();
this.edgeLabels.set(label, edges); this.edgeLabels.set(label, edges);
} }
edges.set(JSON.stringify([from.id, to.id]), edge); const edgeKey = WDAG.getEdgeKey(edge);
edges.set(edgeKey, edge);
this.flowchart?.log(this.getEdgeFlowchartNode(edge));
return edge;
} }
addEdge(label, from, to, weight) { addEdge(label, from, to, weight) {
@ -95,11 +133,10 @@ export class WDAG {
if (this.getEdge(label, from, to)) { if (this.getEdge(label, from, to)) {
throw new Error(`Edge ${label} from ${from} to ${to} already exists`); throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
} }
const edge = new Edge(label, from, to, weight); const edge = this.setEdgeWeight(label, from, to, weight);
this.setEdge(label, from, to, edge);
from.edges.from.push(edge); from.edges.from.push(edge);
to.edges.to.push(edge); to.edges.to.push(edge);
this.flowchart?.log(`${from.id} -- ${weight} --> ${to.id}`); this.flowchart?.log(`${from.id} --- ${this.getEdgeFlowchartNode(edge)} --> ${to.id}`);
return this; return this;
} }

View File

@ -0,0 +1,65 @@
import { ForumTest } from './forum.test-util.js';
describe('Forum', () => {
const forumTest = new ForumTest();
before(async () => {
await forumTest.setup();
});
context('Redistribute power', async () => {
it('Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 20);
forum.getPost(posts[0]).value.should.equal(20);
});
it('Post2', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(20);
forum.getPost(posts[1]).value.should.equal(10);
});
it('Post3 cites Post2 and negatively cites Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10, [
{ postId: posts[0], weight: -0.5 },
{ postId: posts[1], weight: 0.5 },
]);
forum.getPost(posts[0]).value.should.equal(15);
forum.getPost(posts[1]).value.should.equal(15);
forum.getPost(posts[2]).value.should.equal(10);
});
it('Post4 copies Post3 to increase its power', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10, [
{ postId: posts[0], weight: -0.5 },
{ postId: posts[1], weight: 0.5 },
]);
forum.getPost(posts[0]).value.should.equal(10);
forum.getPost(posts[1]).value.should.equal(20);
forum.getPost(posts[2]).value.should.equal(10);
forum.getPost(posts[3]).value.should.equal(10);
});
it('Post5 cites Post3 and Post4 to increase their power', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 20, [
{ postId: posts[2], weight: 0.5 },
{ postId: posts[3], weight: 0.5 },
]);
forum.getPost(posts[0]).value.should.equal(0);
forum.getPost(posts[1]).value.should.equal(40);
forum.getPost(posts[2]).value.should.equal(40);
forum.getPost(posts[3]).value.should.equal(0);
});
});
});
// await addPost(experts[0], 10);
// await addPost(experts[0], 10, [{ postId: posts[3], weight: -1 }]);
// await addPost(experts[0], 10, [{ postId: posts[4], weight: -1 }]);
// await addPost(expert3, 'Post 4', 100, [{ postId: postId2, weight: -1 }]);
// await addPost(expert1, 'Post 5', 100, [{ postId: postId3, weight: -1 }]);

View File

@ -8,21 +8,28 @@ describe('Forum', () => {
}); });
context('Redistribute power through subsequent support', async () => { context('Redistribute power through subsequent support', async () => {
let forum;
let experts;
let posts;
before(() => {
forum = forumTest.forum;
experts = forumTest.experts;
posts = forumTest.posts;
});
it('Post1', async () => { it('Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10); await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10); forum.getPost(posts[0]).value.should.equal(10);
}); });
it('Post2', async () => { it('Post2', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 10); await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10); forum.getPost(posts[0]).value.should.equal(10);
forum.getPost(posts[1]).value.should.equal(10); forum.getPost(posts[1]).value.should.equal(10);
}); });
it('Post3 cites Post2 and negatively cites Post1', async () => { it('Post3 cites Post2 and negatively cites Post1', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 0, [ await forumTest.addPost(experts[0], 0, [
{ postId: posts[0], weight: -0.5 }, { postId: posts[0], weight: -0.5 },
{ postId: posts[1], weight: 0.5 }, { postId: posts[1], weight: 0.5 },
@ -33,7 +40,6 @@ describe('Forum', () => {
}); });
it('Post4 cites Post3 to strengthen its effect', async () => { it('Post4 cites Post3 to strengthen its effect', async () => {
const { forum, experts, posts } = forumTest;
await forumTest.addPost(experts[0], 20, [ await forumTest.addPost(experts[0], 20, [
{ postId: posts[2], weight: 1 }, { postId: posts[2], weight: 1 },
]); ]);
@ -42,6 +48,18 @@ describe('Forum', () => {
forum.getPost(posts[2]).value.should.equal(20); forum.getPost(posts[2]).value.should.equal(20);
forum.getPost(posts[3]).value.should.equal(0); forum.getPost(posts[3]).value.should.equal(0);
}); });
it('Post5 cites Post3 to strengthen its effect', async () => {
await forumTest.addPost(experts[0], 20, [
{ postId: posts[2], weight: 1 },
]);
console.log('test5', { posts });
forum.getPost(posts[0]).value.should.equal(0);
forum.getPost(posts[1]).value.should.equal(20);
forum.getPost(posts[2]).value.should.equal(20);
forum.getPost(posts[3]).value.should.equal(0);
forum.getPost(posts[4]).value.should.equal(0);
});
}); });
}); });