post-app-website-new/lib/flow-funding/targeted.ts

149 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Targeted Funding - Add money to specific accounts and watch propagation
*/
import type { Account, DistributionResult, IterationResult } from './types'
/**
* Run distribution starting from current account balances
* (Skips initial distribution phase - just runs overflow redistribution)
*/
export function runTargetedDistribution(
accounts: Account[],
config: {
maxIterations?: number
epsilon?: number
verbose?: boolean
} = {}
): DistributionResult {
const { maxIterations = 100, epsilon = 0.01, verbose = false } = config
// Store initial state
const initialBalances = new Map(accounts.map(a => [a.id, a.balance]))
if (verbose) {
console.log('\n📍 Targeted Distribution (from current balances)')
accounts.forEach(a => {
console.log(` ${a.id}: $${a.balance.toFixed(2)}`)
})
}
// Run overflow redistribution iterations
const iterations: IterationResult[] = []
let converged = false
for (let i = 0; i < maxIterations; i++) {
if (verbose) {
console.log(`\n--- Iteration ${i} ---`)
}
// Calculate overflow
const overflows = new Map<string, number>()
let totalOverflow = 0
for (const account of accounts) {
const overflow = Math.max(0, account.balance - account.maxThreshold)
if (overflow > 0) {
overflows.set(account.id, overflow)
totalOverflow += overflow
// Adjust balance to maximum
account.balance = account.maxThreshold
if (verbose) {
console.log(` ${account.id}: overflow $${overflow.toFixed(2)}`)
}
}
}
// Record iteration state
const flows = new Map<string, number>()
const iteration: IterationResult = {
iteration: i,
balances: new Map(accounts.map(a => [a.id, a.balance])),
overflows,
totalOverflow,
flows,
converged: totalOverflow < epsilon,
}
// Check convergence
if (totalOverflow < epsilon) {
if (verbose) {
console.log(`✓ Converged (overflow < ${epsilon})`)
}
converged = true
iterations.push(iteration)
break
}
// Redistribute overflow
const accountMap = new Map(accounts.map(a => [a.id, a]))
for (const [sourceId, overflow] of overflows.entries()) {
const source = accountMap.get(sourceId)
if (!source) continue
// Normalize allocations
let totalAllocation = 0
for (const percentage of source.allocations.values()) {
totalAllocation += percentage
}
if (totalAllocation === 0) {
if (verbose) {
console.log(` ${sourceId}: no allocations - overflow lost`)
}
continue
}
// Distribute overflow
for (const [targetId, percentage] of source.allocations.entries()) {
const target = accountMap.get(targetId)
if (!target) continue
const normalizedPercentage = percentage / totalAllocation
const amount = overflow * normalizedPercentage
target.balance += amount
flows.set(`${sourceId}->${targetId}`, amount)
if (verbose) {
console.log(
` ${sourceId}${targetId}: $${amount.toFixed(2)} (${percentage}%)`
)
}
}
}
iteration.flows = flows
iterations.push(iteration)
}
if (!converged && verbose) {
console.log(`\n⚠ Did not converge within ${maxIterations} iterations`)
}
const finalBalances = new Map(accounts.map(a => [a.id, a.balance]))
if (verbose) {
console.log('\n🎯 Final State:')
accounts.forEach(a => {
const initial = initialBalances.get(a.id) || 0
const change = a.balance - initial
console.log(
` ${a.id}: $${a.balance.toFixed(2)} ` +
`(${change >= 0 ? '+' : ''}$${change.toFixed(2)})`
)
})
}
return {
initialBalances,
finalBalances,
iterations,
converged,
totalFunding: 0, // Not applicable for targeted
iterationCount: iterations.length,
}
}