From 3cf24c6fa19ad0c60732fee6becb305977d15244 Mon Sep 17 00:00:00 2001 From: Ladd Hoffman Date: Sun, 29 Jan 2023 23:43:30 -0600 Subject: [PATCH] Render multiple edge labels WIP: Forum propagation --- forum-network/notes/rep.md | 6 ++ forum-network/src/classes/forum.js | 37 +++++++++-- forum-network/src/classes/scene.js | 6 ++ forum-network/src/classes/validation-pool.js | 2 +- forum-network/src/classes/wdag.js | 49 ++++++++++++-- .../src/tests/scripts/forum3.test copy.js | 65 +++++++++++++++++++ .../src/tests/scripts/forum4.test.js | 26 ++++++-- 7 files changed, 173 insertions(+), 18 deletions(-) create mode 100644 forum-network/src/tests/scripts/forum3.test copy.js diff --git a/forum-network/notes/rep.md b/forum-network/notes/rep.md index 018ca2c..0574402 100644 --- a/forum-network/notes/rep.md +++ b/forum-network/notes/rep.md @@ -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. --- + +So: If some incoming reputation can’t 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 diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/forum.js index ec6da68..d5010e0 100644 --- a/forum-network/src/classes/forum.js +++ b/forum-network/src/classes/forum.js @@ -53,7 +53,7 @@ export class Forum extends ReputationHolder { this.posts = new WDAG(scene); this.actions = { 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), }; } @@ -122,7 +122,7 @@ export class Forum extends ReputationHolder { bench.reputation.transferFrom(this.id, post.authorPublicKey, post.tokenId); const toActor = this.scene.findActor((actor) => actor.reputationPublicKey === post.authorPublicKey); const value = bench.reputation.valueOf(post.tokenId); - this.actions.transfer.log(this, toActor, `(value: ${value})`); + this.actions.transfer.log(this, toActor, `(${value})`); this.deactivate(); } @@ -146,13 +146,12 @@ export class Forum extends ReputationHolder { if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) { let totalOutboundAmount = 0; + let totalRefundFromOutbound = 0; for (const citationEdge of postVertex.getEdges(CITATION, true)) { const { to: citedPostVertex, weight } = citationEdge; let outboundAmount = weight * increment; - const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.weight || 0; - console.log('Citation', { - citationEdge, outboundAmount, balance, - }); + const balanceEdge = this.posts.getEdge(BALANCE, postVertex, citedPostVertex); + const balance = balanceEdge?.weight ?? 0; // We need to ensure that we propagate no more reputation than we leached if (depth > 0 && weight < 0) { outboundAmount = outboundAmount < 0 @@ -164,10 +163,34 @@ export class Forum extends ReputationHolder { increment: outboundAmount, depth: depth + 1, }); + totalRefundFromOutbound += Math.abs(refundFromOutbound); outboundAmount -= refundFromOutbound; - this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount); + this.posts.setEdgeWeight(BALANCE, postVertex, citedPostVertex, balance + 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; } diff --git a/forum-network/src/classes/scene.js b/forum-network/src/classes/scene.js index 498e185..7afd507 100644 --- a/forum-network/src/classes/scene.js +++ b/forum-network/src/classes/scene.js @@ -114,6 +114,10 @@ export class Scene { activationBorderColor: '#569595', }, }); + + this.options = { + edgeNodeColor: '#4d585c', + }; } withSequenceDiagram() { @@ -131,6 +135,7 @@ export class Scene { const logBox = this.box.addBox('Flowchart text').addClass('dim'); this.flowchart = new MermaidDiagram(box, logBox); this.flowchart.log(`graph ${direction}`, false); + this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false); return this; } @@ -143,6 +148,7 @@ export class Scene { const logBox = container.addBox('Flowchart text').addClass('dim'); const flowchart = new MermaidDiagram(box, logBox); flowchart.log(`graph ${direction}`, false); + this.flowchart.log(`classDef edge fill:${this.options.edgeNodeColor}`, false); this.flowcharts.set(id, flowchart); return this; } diff --git a/forum-network/src/classes/validation-pool.js b/forum-network/src/classes/validation-pool.js index 5738882..ac2583d 100644 --- a/forum-network/src/classes/validation-pool.js +++ b/forum-network/src/classes/validation-pool.js @@ -265,7 +265,7 @@ export class ValidationPool extends ReputationHolder { // Transfer ownership of the minted token, from the pool to the forum this.bench.reputation.transferFrom(this.id, this.forum.id, 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 await this.forum.onValidate({ diff --git a/forum-network/src/classes/wdag.js b/forum-network/src/classes/wdag.js index 2aca682..ace5beb 100644 --- a/forum-network/src/classes/wdag.js +++ b/forum-network/src/classes/wdag.js @@ -70,6 +70,19 @@ export class WDAG { 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) { from = from instanceof Vertex ? from : this.getVertex(from); to = to instanceof Vertex ? to : this.getVertex(to); @@ -77,16 +90,41 @@ export class WDAG { 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 = ''; + for (const { label, weight } of this.getEdges(null, from, to)) { + html += ``; + } + html += '
${label}${weight}
'; + 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); 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); if (!edges) { edges = new Map(); 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) { @@ -95,11 +133,10 @@ export class WDAG { if (this.getEdge(label, from, to)) { throw new Error(`Edge ${label} from ${from} to ${to} already exists`); } - const edge = new Edge(label, from, to, weight); - this.setEdge(label, from, to, edge); + const edge = this.setEdgeWeight(label, from, to, weight); from.edges.from.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; } diff --git a/forum-network/src/tests/scripts/forum3.test copy.js b/forum-network/src/tests/scripts/forum3.test copy.js new file mode 100644 index 0000000..b41f6f0 --- /dev/null +++ b/forum-network/src/tests/scripts/forum3.test copy.js @@ -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 }]); diff --git a/forum-network/src/tests/scripts/forum4.test.js b/forum-network/src/tests/scripts/forum4.test.js index fbd56da..0d4074f 100644 --- a/forum-network/src/tests/scripts/forum4.test.js +++ b/forum-network/src/tests/scripts/forum4.test.js @@ -8,21 +8,28 @@ describe('Forum', () => { }); 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 () => { - const { forum, experts, posts } = forumTest; await forumTest.addPost(experts[0], 10); forum.getPost(posts[0]).value.should.equal(10); }); it('Post2', async () => { - const { forum, experts, posts } = forumTest; await forumTest.addPost(experts[0], 10); forum.getPost(posts[0]).value.should.equal(10); 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], 0, [ { postId: posts[0], weight: -0.5 }, { postId: posts[1], weight: 0.5 }, @@ -33,7 +40,6 @@ describe('Forum', () => { }); it('Post4 cites Post3 to strengthen its effect', async () => { - const { forum, experts, posts } = forumTest; await forumTest.addPost(experts[0], 20, [ { postId: posts[2], weight: 1 }, ]); @@ -42,6 +48,18 @@ describe('Forum', () => { forum.getPost(posts[2]).value.should.equal(20); 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); + }); }); });