coreason_runtime_rust/execution_plane/
lmsr_consensus.rs1use serde::{Deserialize, Serialize};
16use std::time::{SystemTime, UNIX_EPOCH};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct MarketOutcome {
23 pub outcome_id: String,
24 pub label: String,
25 pub shares: f64,
26}
27
28impl MarketOutcome {
29 pub fn new(outcome_id: &str, label: &str) -> Self {
30 Self {
31 outcome_id: outcome_id.to_string(),
32 label: label.to_string(),
33 shares: 0.0,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LMSRMarketMaker {
44 pub outcomes: Vec<MarketOutcome>,
45 pub b: f64,
46 pub price_history: Vec<serde_json::Value>,
47}
48
49impl LMSRMarketMaker {
50 pub fn new(b: f64) -> Self {
51 Self {
52 outcomes: Vec::new(),
53 b,
54 price_history: Vec::new(),
55 }
56 }
57
58 pub fn cost_function(&self, quantities: &[f64]) -> f64 {
63 if quantities.is_empty() {
64 return 0.0;
65 }
66 let max_q = quantities.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
67 let sum_exp: f64 = quantities
68 .iter()
69 .map(|q| ((q - max_q) / self.b).exp())
70 .sum();
71 self.b * (max_q / self.b + sum_exp.ln())
72 }
73
74 pub fn price(&self, outcome_idx: usize) -> f64 {
80 let quantities: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
81 if quantities.is_empty() {
82 return 0.0;
83 }
84 let max_q = quantities.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
85 let exp_vals: Vec<f64> = quantities
86 .iter()
87 .map(|q| ((q - max_q) / self.b).exp())
88 .collect();
89 let total: f64 = exp_vals.iter().sum();
90 if total > 0.0 {
91 exp_vals[outcome_idx] / total
92 } else {
93 0.0
94 }
95 }
96
97 pub fn buy(&mut self, outcome_idx: usize, shares: f64) -> f64 {
99 let quantities_before: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
100 let cost_before = self.cost_function(&quantities_before);
101
102 self.outcomes[outcome_idx].shares += shares;
103 let quantities_after: Vec<f64> = self.outcomes.iter().map(|o| o.shares).collect();
104 let cost_after = self.cost_function(&quantities_after);
105
106 let trade_cost = cost_after - cost_before;
107
108 let now = SystemTime::now()
110 .duration_since(UNIX_EPOCH)
111 .unwrap_or_default()
112 .as_secs_f64();
113
114 let prices: Vec<f64> = (0..self.outcomes.len()).map(|i| self.price(i)).collect();
115
116 self.price_history.push(serde_json::json!({
117 "timestamp": now,
118 "outcome_idx": outcome_idx,
119 "shares": shares,
120 "cost": trade_cost,
121 "prices": prices,
122 }));
123
124 trade_cost
125 }
126
127 pub fn entropy(&self) -> f64 {
129 let n = self.outcomes.len();
130 if n == 0 {
131 return 0.0;
132 }
133 let prices: Vec<f64> = (0..n).map(|i| self.price(i)).collect();
134 -prices
135 .iter()
136 .map(|p| {
137 let safe_p = p + 1e-10;
138 safe_p * safe_p.ln()
139 })
140 .sum::<f64>()
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct DebateArgument {
149 pub agent_id: String,
150 pub position: String,
151 pub evidence: Vec<String>,
152 pub confidence: f64,
153 pub timestamp: f64,
154 pub argument_id: String,
155}
156
157impl DebateArgument {
158 pub fn new(agent_id: &str, position: &str, evidence: Vec<String>, confidence: f64) -> Self {
159 let now = SystemTime::now()
160 .duration_since(UNIX_EPOCH)
161 .unwrap_or_default()
162 .as_secs_f64();
163 let id = format!("{:012x}", rand::random::<u64>() & 0xFFFF_FFFF_FFFF);
164 Self {
165 agent_id: agent_id.to_string(),
166 position: position.to_string(),
167 evidence,
168 confidence,
169 timestamp: now,
170 argument_id: id,
171 }
172 }
173}
174
175pub struct DialecticDebateRound {
181 pub topic: String,
182 pub arguments: Vec<DebateArgument>,
183 pub market: LMSRMarketMaker,
184 pub round_id: String,
185}
186
187impl DialecticDebateRound {
188 pub fn new(topic: &str, b: f64) -> Self {
189 let id = format!("{:012x}", rand::random::<u64>() & 0xFFFF_FFFF_FFFF);
190 Self {
191 topic: topic.to_string(),
192 arguments: Vec::new(),
193 market: LMSRMarketMaker::new(b),
194 round_id: id,
195 }
196 }
197
198 pub fn submit_argument(
200 &mut self,
201 agent_id: &str,
202 position: &str,
203 evidence: Vec<String>,
204 confidence: f64,
205 ) -> DebateArgument {
206 let arg = DebateArgument::new(agent_id, position, evidence, confidence);
207
208 let position_idx = self
210 .market
211 .outcomes
212 .iter()
213 .position(|o| o.label == position);
214
215 let idx = match position_idx {
216 Some(i) => i,
217 None => {
218 let i = self.market.outcomes.len();
219 self.market
220 .outcomes
221 .push(MarketOutcome::new(&arg.argument_id, position));
222 i
223 }
224 };
225
226 self.market.buy(idx, confidence * 10.0);
228 self.arguments.push(arg.clone());
229 arg
230 }
231
232 pub fn score_arguments(&self) -> Vec<serde_json::Value> {
234 let mut scores: Vec<serde_json::Value> = self
235 .arguments
236 .iter()
237 .map(|arg| {
238 let position_idx = self
239 .market
240 .outcomes
241 .iter()
242 .position(|o| o.label == arg.position);
243 let market_price = position_idx.map(|i| self.market.price(i)).unwrap_or(0.0);
244 let evidence_weight = arg.evidence.len() as f64 * 0.1;
245 let score = (arg.confidence * 0.4) + (market_price * 0.4) + (evidence_weight * 0.2);
246
247 serde_json::json!({
248 "argument_id": arg.argument_id,
249 "agent_id": arg.agent_id,
250 "position": arg.position,
251 "score": (score * 10000.0).round() / 10000.0,
252 "market_price": (market_price * 10000.0).round() / 10000.0,
253 })
254 })
255 .collect();
256
257 scores.sort_by(|a, b| {
258 let sa = a["score"].as_f64().unwrap_or(0.0);
259 let sb = b["score"].as_f64().unwrap_or(0.0);
260 sb.partial_cmp(&sa).unwrap_or(std::cmp::Ordering::Equal)
261 });
262
263 scores
264 }
265
266 pub fn resolve_consensus(&self) -> serde_json::Value {
268 let scores = self.score_arguments();
269 if scores.is_empty() {
270 return serde_json::json!({"consensus": null, "confidence": 0.0});
271 }
272
273 let winning = &scores[0];
274 serde_json::json!({
275 "consensus_position": winning["position"],
276 "consensus_confidence": winning["score"],
277 "market_entropy": self.market.entropy(),
278 "total_arguments": self.arguments.len(),
279 "price_history": self.market.price_history,
280 })
281 }
282}
283
284pub struct ConsensusMetrics;
292
293impl ConsensusMetrics {
294 pub fn kl_divergence(p: &[f64], q: &[f64]) -> f64 {
300 logp::kl_divergence(p, q, 1e-6).unwrap_or(0.0)
301 }
302
303 pub fn jensen_shannon_divergence(beliefs: &[Vec<f64>]) -> f64 {
308 if beliefs.len() < 2 {
309 return 0.0;
310 }
311
312 let n = beliefs.len();
313 let dim = beliefs[0].len();
314
315 let m: Vec<f64> = (0..dim)
317 .map(|i| beliefs.iter().map(|b| b[i]).sum::<f64>() / n as f64)
318 .collect();
319
320 let total_jsd: f64 = beliefs
322 .iter()
323 .filter_map(|b| logp::jensen_shannon_divergence(b, &m, 1e-6).ok())
324 .sum();
325
326 total_jsd / n as f64
327 }
328
329 pub fn entropy(distribution: &[f64]) -> f64 {
333 logp::entropy_nats(distribution, 1e-6).unwrap_or(0.0)
334 }
335
336 pub fn consensus_score(
338 agent_beliefs: &std::collections::HashMap<String, Vec<f64>>,
339 ) -> serde_json::Value {
340 let beliefs_owned: Vec<Vec<f64>> = agent_beliefs.values().cloned().collect();
341 let jsd = Self::jensen_shannon_divergence(&beliefs_owned);
342 let avg_entropy: f64 = beliefs_owned.iter().map(|b| Self::entropy(b)).sum::<f64>()
343 / beliefs_owned.len() as f64;
344
345 serde_json::json!({
346 "jensen_shannon_divergence": (jsd * 1000000.0).round() / 1000000.0,
347 "average_entropy": (avg_entropy * 1000000.0).round() / 1000000.0,
348 "consensus_strength": ((1.0 - jsd.min(1.0)) * 10000.0).round() / 10000.0,
349 "num_agents": agent_beliefs.len(),
350 })
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_lmsr_market_maker_cost_function() {
360 let market = LMSRMarketMaker::new(100.0);
361 let cost = market.cost_function(&[0.0, 0.0]);
362 assert!((cost - 69.31).abs() < 0.1);
364 }
365
366 #[test]
367 fn test_lmsr_equal_shares_equal_prices() {
368 let mut market = LMSRMarketMaker::new(100.0);
369 market.outcomes.push(MarketOutcome::new("a", "Option A"));
370 market.outcomes.push(MarketOutcome::new("b", "Option B"));
371
372 let p0 = market.price(0);
373 let p1 = market.price(1);
374 assert!((p0 - 0.5).abs() < 1e-10);
375 assert!((p1 - 0.5).abs() < 1e-10);
376 }
377
378 #[test]
379 fn test_lmsr_buy_increases_price() {
380 let mut market = LMSRMarketMaker::new(100.0);
381 market.outcomes.push(MarketOutcome::new("a", "Option A"));
382 market.outcomes.push(MarketOutcome::new("b", "Option B"));
383
384 let price_before = market.price(0);
385 market.buy(0, 50.0);
386 let price_after = market.price(0);
387
388 assert!(price_after > price_before);
389 }
390
391 #[test]
392 fn test_prices_sum_to_one() {
393 let mut market = LMSRMarketMaker::new(100.0);
394 market.outcomes.push(MarketOutcome::new("a", "Option A"));
395 market.outcomes.push(MarketOutcome::new("b", "Option B"));
396 market.outcomes.push(MarketOutcome::new("c", "Option C"));
397
398 market.buy(0, 30.0);
399 market.buy(2, 10.0);
400
401 let total: f64 = (0..3).map(|i| market.price(i)).sum();
402 assert!((total - 1.0).abs() < 1e-10);
403 }
404
405 #[test]
406 fn test_kl_divergence_identical() {
407 let p = vec![0.5, 0.5];
408 let q = vec![0.5, 0.5];
409 let kl = ConsensusMetrics::kl_divergence(&p, &q);
410 assert!(kl.abs() < 1e-6);
411 }
412
413 #[test]
414 fn test_jsd_identical_beliefs() {
415 let beliefs = vec![vec![0.5, 0.5], vec![0.5, 0.5]];
416 let jsd = ConsensusMetrics::jensen_shannon_divergence(&beliefs);
417 assert!(jsd.abs() < 1e-6);
418 }
419
420 #[test]
421 fn test_jsd_divergent_beliefs() {
422 let beliefs = vec![vec![0.9, 0.1], vec![0.1, 0.9]];
423 let jsd = ConsensusMetrics::jensen_shannon_divergence(&beliefs);
424 assert!(jsd > 0.1); }
426
427 #[test]
428 fn test_debate_round() {
429 let mut debate = DialecticDebateRound::new("Should we use Rust?", 100.0);
430 debate.submit_argument("agent_1", "yes", vec!["performance".to_string()], 0.9);
431 debate.submit_argument("agent_2", "no", vec![], 0.3);
432 debate.submit_argument(
433 "agent_3",
434 "yes",
435 vec!["safety".to_string(), "speed".to_string()],
436 0.8,
437 );
438
439 let consensus = debate.resolve_consensus();
440 assert_eq!(consensus["consensus_position"], "yes");
441 assert!(consensus["consensus_confidence"].as_f64().unwrap() > 0.0);
442 }
443}