use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketOutcome {
pub outcome_id: String,
pub label: String,
pub shares: f64,
}
impl MarketOutcome {
pub fn new(outcome_id: &str, label: &str) -> Self {
Self {
outcome_id: outcome_id.to_string(),
label: label.to_string(),
shares: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LMSRMarketMaker {
pub outcomes: Vec<MarketOutcome>,
pub b: f64,
pub price_history: Vec<serde_json::Value>,
}
impl LMSRMarketMaker {
pub fn new(b: f64) -> Self {
Self {
outcomes: Vec::new(),
b,
price_history: Vec::new(),
}
}
pub fn cost_function(&self, quantities: &[f64]) -> f64 {
if quantities.is_empty() {
return 0.0;
}
let max_q = quantities.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let sum_exp: f64 = quantities
.iter()
.map(|q| ((q - max_q) / self.b).exp())
.sum();
self.b * (max_q / self.b + sum_exp.ln())
}
pub fn price(&self, outcome_idx: usize) -> f64 {
let quantities: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
if quantities.is_empty() {
return 0.0;
}
let max_q = quantities.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let exp_vals: Vec<f64> = quantities
.iter()
.map(|q| ((q - max_q) / self.b).exp())
.collect();
let total: f64 = exp_vals.iter().sum();
if total > 0.0 {
exp_vals[outcome_idx] / total
} else {
0.0
}
}
pub fn buy(&mut self, outcome_idx: usize, shares: f64) -> f64 {
let quantities_before: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
let cost_before = self.cost_function(&quantities_before);
self.outcomes[outcome_idx].shares += shares;
let quantities_after: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
let cost_after = self.cost_function(&quantities_after);
let trade_cost = cost_after - cost_before;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64();
let prices: Vec<f64> = (0..self.outcomes.len()).map(|i| self.price(i)).collect();
self.price_history.push(serde_json::json!({
"timestamp": now,
"outcome_idx": outcome_idx,
"shares": shares,
"cost": trade_cost,
"prices": prices,
}));
trade_cost
}
pub fn entropy(&self) -> f64 {
let n = self.outcomes.len();
if n == 0 {
return 0.0;
}
let prices: Vec<f64> = (0..n).map(|i| self.price(i)).collect();
-prices
.iter()
.map(|p| {
let safe_p = p + 1e-10;
safe_p * safe_p.ln()
})
.sum::<f64>()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebateArgument {
pub agent_id: String,
pub position: String,
pub evidence: Vec<String>,
pub confidence: f64,
pub timestamp: f64,
pub argument_id: String,
}
impl DebateArgument {
pub fn new(agent_id: &str, position: &str, evidence: Vec<String>, confidence: f64) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64();
let id = format!("{:012x}", rand::random::<u64>() & 0xFFFF_FFFF_FFFF);
Self {
agent_id: agent_id.to_string(),
position: position.to_string(),
evidence,
confidence,
timestamp: now,
argument_id: id,
}
}
}
pub struct DialecticDebateRound {
pub topic: String,
pub arguments: Vec<DebateArgument>,
pub market: LMSRMarketMaker,
pub round_id: String,
}
impl DialecticDebateRound {
pub fn new(topic: &str, b: f64) -> Self {
let id = format!("{:012x}", rand::random::<u64>() & 0xFFFF_FFFF_FFFF);
Self {
topic: topic.to_string(),
arguments: Vec::new(),
market: LMSRMarketMaker::new(b),
round_id: id,
}
}
pub fn submit_argument(
&mut self,
agent_id: &str,
position: &str,
evidence: Vec<String>,
confidence: f64,
) -> DebateArgument {
let arg = DebateArgument::new(agent_id, position, evidence, confidence);
let position_idx = self
.market
.outcomes
.iter()
.position(|o| o.label == position);
let idx = match position_idx {
Some(i) => i,
None => {
let i = self.market.outcomes.len();
self.market
.outcomes
.push(MarketOutcome::new(&arg.argument_id, position));
i
}
};
self.market.buy(idx, confidence * 10.0);
self.arguments.push(arg.clone());
arg
}
pub fn score_arguments(&self) -> Vec<serde_json::Value> {
let mut scores: Vec<serde_json::Value> = self
.arguments
.iter()
.map(|arg| {
let position_idx = self
.market
.outcomes
.iter()
.position(|o| o.label == arg.position);
let market_price = position_idx.map(|i| self.market.price(i)).unwrap_or(0.0);
let evidence_weight = arg.evidence.len() as f64 * 0.1;
let score = (arg.confidence * 0.4) + (market_price * 0.4) + (evidence_weight * 0.2);
serde_json::json!({
"argument_id": arg.argument_id,
"agent_id": arg.agent_id,
"position": arg.position,
"score": (score * 10000.0).round() / 10000.0,
"market_price": (market_price * 10000.0).round() / 10000.0,
})
})
.collect();
scores.sort_by(|a, b| {
let sa = a["score"].as_f64().unwrap_or(0.0);
let sb = b["score"].as_f64().unwrap_or(0.0);
sb.partial_cmp(&sa).unwrap_or(std::cmp::Ordering::Equal)
});
scores
}
pub fn resolve_consensus(&self) -> serde_json::Value {
let scores = self.score_arguments();
if scores.is_empty() {
return serde_json::json!({"consensus": null, "confidence": 0.0});
}
let winning = &scores[0];
serde_json::json!({
"consensus_position": winning["position"],
"consensus_confidence": winning["score"],
"market_entropy": self.market.entropy(),
"total_arguments": self.arguments.len(),
"price_history": self.market.price_history,
})
}
}
pub struct ConsensusMetrics;
impl ConsensusMetrics {
pub fn kl_divergence(p: &[f64], q: &[f64]) -> f64 {
logp::kl_divergence(p, q, 1e-6).unwrap_or(0.0)
}
pub fn jensen_shannon_divergence(beliefs: &[Vec<f64>]) -> f64 {
if beliefs.len() < 2 {
return 0.0;
}
let n = beliefs.len();
let dim = beliefs[0].len();
let m: Vec<f64> = (0..dim)
.map(|i| beliefs.iter().map(|b| b[i]).sum::<f64>() / n as f64)
.collect();
let total_jsd: f64 = beliefs
.iter()
.filter_map(|b| logp::jensen_shannon_divergence(b, &m, 1e-6).ok())
.sum();
total_jsd / n as f64
}
pub fn entropy(distribution: &[f64]) -> f64 {
logp::entropy_nats(distribution, 1e-6).unwrap_or(0.0)
}
pub fn consensus_score(
agent_beliefs: &std::collections::HashMap<String, Vec<f64>>,
) -> serde_json::Value {
let beliefs_owned: Vec<Vec<f64>> = agent_beliefs.values().cloned().collect();
let jsd = Self::jensen_shannon_divergence(&beliefs_owned);
let avg_entropy: f64 = beliefs_owned.iter().map(|b| Self::entropy(b)).sum::<f64>()
/ beliefs_owned.len() as f64;
serde_json::json!({
"jensen_shannon_divergence": (jsd * 1000000.0).round() / 1000000.0,
"average_entropy": (avg_entropy * 1000000.0).round() / 1000000.0,
"consensus_strength": ((1.0 - jsd.min(1.0)) * 10000.0).round() / 10000.0,
"num_agents": agent_beliefs.len(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lmsr_market_maker_cost_function() {
let market = LMSRMarketMaker::new(100.0);
let cost = market.cost_function(&[0.0, 0.0]);
assert!((cost - 69.31).abs() < 0.1);
}
#[test]
fn test_lmsr_equal_shares_equal_prices() {
let mut market = LMSRMarketMaker::new(100.0);
market.outcomes.push(MarketOutcome::new("a", "Option A"));
market.outcomes.push(MarketOutcome::new("b", "Option B"));
let p0 = market.price(0);
let p1 = market.price(1);
assert!((p0 - 0.5).abs() < 1e-10);
assert!((p1 - 0.5).abs() < 1e-10);
}
#[test]
fn test_lmsr_buy_increases_price() {
let mut market = LMSRMarketMaker::new(100.0);
market.outcomes.push(MarketOutcome::new("a", "Option A"));
market.outcomes.push(MarketOutcome::new("b", "Option B"));
let price_before = market.price(0);
market.buy(0, 50.0);
let price_after = market.price(0);
assert!(price_after > price_before);
}
#[test]
fn test_prices_sum_to_one() {
let mut market = LMSRMarketMaker::new(100.0);
market.outcomes.push(MarketOutcome::new("a", "Option A"));
market.outcomes.push(MarketOutcome::new("b", "Option B"));
market.outcomes.push(MarketOutcome::new("c", "Option C"));
market.buy(0, 30.0);
market.buy(2, 10.0);
let total: f64 = (0..3).map(|i| market.price(i)).sum();
assert!((total - 1.0).abs() < 1e-10);
}
#[test]
fn test_kl_divergence_identical() {
let p = vec![0.5, 0.5];
let q = vec![0.5, 0.5];
let kl = ConsensusMetrics::kl_divergence(&p, &q);
assert!(kl.abs() < 1e-6);
}
#[test]
fn test_jsd_identical_beliefs() {
let beliefs = vec![vec![0.5, 0.5], vec![0.5, 0.5]];
let jsd = ConsensusMetrics::jensen_shannon_divergence(&beliefs);
assert!(jsd.abs() < 1e-6);
}
#[test]
fn test_jsd_divergent_beliefs() {
let beliefs = vec![vec![0.9, 0.1], vec![0.1, 0.9]];
let jsd = ConsensusMetrics::jensen_shannon_divergence(&beliefs);
assert!(jsd > 0.1); }
#[test]
fn test_debate_round() {
let mut debate = DialecticDebateRound::new("Should we use Rust?", 100.0);
debate.submit_argument("agent_1", "yes", vec!["performance".to_string()], 0.9);
debate.submit_argument("agent_2", "no", vec![], 0.3);
debate.submit_argument(
"agent_3",
"yes",
vec!["safety".to_string(), "speed".to_string()],
0.8,
);
let consensus = debate.resolve_consensus();
assert_eq!(consensus["consensus_position"], "yes");
assert!(consensus["consensus_confidence"].as_f64().unwrap() > 0.0);
}
}