use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OscillationConfig {
pub cycles: usize,
pub min_ideas_per_diverge: usize,
pub max_ideas_to_converge: usize,
pub divergent_dimensions: Vec<DivergentDimension>,
pub convergent_criteria: Vec<ConvergentCriterion>,
pub track_lineage: bool,
}
impl Default for OscillationConfig {
fn default() -> Self {
Self {
cycles: 3,
min_ideas_per_diverge: 5,
max_ideas_to_converge: 3,
divergent_dimensions: vec![
DivergentDimension::Fluency,
DivergentDimension::Flexibility,
DivergentDimension::Originality,
DivergentDimension::Elaboration,
],
convergent_criteria: vec![
ConvergentCriterion::Feasibility,
ConvergentCriterion::Impact,
ConvergentCriterion::Novelty,
ConvergentCriterion::Alignment,
],
track_lineage: true,
}
}
}
impl OscillationConfig {
pub fn gigathink() -> Self {
Self {
cycles: 3,
min_ideas_per_diverge: 10,
max_ideas_to_converge: 5,
divergent_dimensions: vec![
DivergentDimension::Fluency,
DivergentDimension::Flexibility,
DivergentDimension::Originality,
DivergentDimension::Elaboration,
DivergentDimension::Analogical,
DivergentDimension::Contrarian,
],
convergent_criteria: vec![
ConvergentCriterion::Feasibility,
ConvergentCriterion::Impact,
ConvergentCriterion::Novelty,
ConvergentCriterion::Alignment,
],
track_lineage: true,
}
}
pub fn quick() -> Self {
Self {
cycles: 2,
min_ideas_per_diverge: 5,
max_ideas_to_converge: 2,
..Default::default()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DivergentDimension {
Fluency,
Flexibility,
Originality,
Elaboration,
Analogical,
Contrarian,
Combinatorial,
Inversion,
}
impl DivergentDimension {
pub fn prompt_guidance(&self) -> &'static str {
match self {
Self::Fluency => "Generate as many ideas as possible, without filtering",
Self::Flexibility => {
"Generate ideas from different perspectives, domains, and categories"
}
Self::Originality => "Generate unusual, surprising, or unconventional ideas",
Self::Elaboration => "Add specific details, mechanisms, and implementation paths",
Self::Analogical => "Draw ideas from analogous domains and transfer insights",
Self::Contrarian => "Challenge assumptions and generate opposing viewpoints",
Self::Combinatorial => "Combine and recombine existing ideas in new ways",
Self::Inversion => "Invert the problem - what would make it worse? Then reverse",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConvergentCriterion {
Feasibility,
Impact,
Novelty,
Alignment,
Risk,
ResourceCost,
TimeToValue,
ComparativeAdvantage,
}
impl ConvergentCriterion {
pub fn evaluation_question(&self) -> &'static str {
match self {
Self::Feasibility => "How implementable is this idea given current constraints?",
Self::Impact => "What magnitude of positive change would this create?",
Self::Novelty => "How unique is this compared to existing approaches?",
Self::Alignment => "How well does this serve the stated goals?",
Self::Risk => "What could go wrong and how severe would it be?",
Self::ResourceCost => "What resources (time, money, effort) are required?",
Self::TimeToValue => "How quickly can this start delivering value?",
Self::ComparativeAdvantage => "Why is this better than the alternatives?",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Idea {
pub id: usize,
pub content: String,
pub dimension: DivergentDimension,
pub parent_id: Option<usize>,
pub cycle: usize,
pub scores: Vec<CriterionScore>,
pub priority: f32,
pub survived: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriterionScore {
pub criterion: ConvergentCriterion,
pub score: f32, pub rationale: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DivergentPhase {
pub cycle: usize,
pub ideas_generated: Vec<Idea>,
pub dimension_coverage: Vec<(DivergentDimension, usize)>,
pub fluency_score: f32, pub flexibility_score: f32, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergentPhase {
pub cycle: usize,
pub ideas_evaluated: usize,
pub ideas_selected: Vec<usize>, pub elimination_rationale: Vec<String>,
pub selection_rationale: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OscillationResult {
pub problem: String,
pub all_ideas: Vec<Idea>,
pub divergent_phases: Vec<DivergentPhase>,
pub convergent_phases: Vec<ConvergentPhase>,
pub final_ideas: Vec<Idea>,
pub synthesis: String,
pub metrics: OscillationMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OscillationMetrics {
pub total_ideas: usize,
pub surviving_ideas: usize,
pub survival_rate: f32,
pub avg_fluency: f32,
pub flexibility_score: f32,
pub originality_score: f32,
pub cycles_completed: usize,
}
impl OscillationResult {
pub fn ideas_from_cycle(&self, cycle: usize) -> Vec<&Idea> {
self.all_ideas.iter().filter(|i| i.cycle == cycle).collect()
}
pub fn ideas_by_dimension(&self, dim: DivergentDimension) -> Vec<&Idea> {
self.all_ideas
.iter()
.filter(|i| i.dimension == dim)
.collect()
}
pub fn get_lineage(&self, idea_id: usize) -> Vec<&Idea> {
let mut lineage = vec![];
let mut current_id = Some(idea_id);
while let Some(id) = current_id {
if let Some(idea) = self.all_ideas.iter().find(|i| i.id == id) {
lineage.push(idea);
current_id = idea.parent_id;
} else {
break;
}
}
lineage.reverse();
lineage
}
}
pub struct OscillationPrompts;
impl OscillationPrompts {
pub fn diverge(
problem: &str,
dimensions: &[DivergentDimension],
prior_ideas: &[String],
) -> String {
let dimension_guidance: String = dimensions
.iter()
.enumerate()
.map(|(i, d)| format!("{}. {:?}: {}", i + 1, d, d.prompt_guidance()))
.collect::<Vec<_>>()
.join("\n");
let prior = if prior_ideas.is_empty() {
"None yet - this is the first cycle.".to_string()
} else {
prior_ideas
.iter()
.enumerate()
.map(|(i, idea)| format!("{}. {}", i + 1, idea))
.collect::<Vec<_>>()
.join("\n")
};
format!(
r#"DIVERGENT THINKING PHASE - Generate Ideas
PROBLEM: {problem}
Use these thinking dimensions to generate diverse ideas:
{dimension_guidance}
PRIOR IDEAS (to build on or differentiate from):
{prior}
Generate at least 5 ideas, covering multiple dimensions.
For each idea, specify:
- IDEA: [The core idea]
- DIMENSION: [Which dimension it came from]
- ELABORATION: [Key details, mechanism, or implementation]
Be creative, be bold, defer judgment. Quantity over quality in this phase.
Format each idea clearly:
IDEA 1:
- Content: ...
- Dimension: ...
- Elaboration: ..."#,
problem = problem,
dimension_guidance = dimension_guidance,
prior = prior
)
}
pub fn converge(
problem: &str,
ideas: &[String],
criteria: &[ConvergentCriterion],
max_select: usize,
) -> String {
let criteria_list: String = criteria
.iter()
.enumerate()
.map(|(i, c)| format!("{}. {:?}: {}", i + 1, c, c.evaluation_question()))
.collect::<Vec<_>>()
.join("\n");
let ideas_list: String = ideas
.iter()
.enumerate()
.map(|(i, idea)| format!("IDEA {}: {}", i + 1, idea))
.collect::<Vec<_>>()
.join("\n\n");
format!(
r#"CONVERGENT THINKING PHASE - Evaluate and Select
PROBLEM: {problem}
IDEAS TO EVALUATE:
{ideas_list}
EVALUATION CRITERIA:
{criteria_list}
For each idea, score it on each criterion (0.0 to 1.0) with a brief rationale.
Then SELECT the top {max_select} ideas to carry forward.
Explain why you're eliminating the others.
Format:
EVALUATION:
Idea 1: [criterion scores and rationale]
Idea 2: [criterion scores and rationale]
...
SELECTED (top {max_select}):
1. Idea X - [why this was chosen]
2. Idea Y - [why this was chosen]
...
ELIMINATED:
- Idea Z - [why eliminated]
..."#,
problem = problem,
ideas_list = ideas_list,
criteria_list = criteria_list,
max_select = max_select
)
}
pub fn synthesize(problem: &str, final_ideas: &[String]) -> String {
let ideas_formatted: String = final_ideas
.iter()
.enumerate()
.map(|(i, idea)| format!("{}. {}", i + 1, idea))
.collect::<Vec<_>>()
.join("\n");
format!(
r#"SYNTHESIS PHASE - Integrate Best Ideas
PROBLEM: {problem}
FINAL SELECTED IDEAS:
{ideas_formatted}
Create a coherent synthesis that:
1. Integrates the best elements of each idea
2. Resolves any tensions between them
3. Provides a unified approach
4. Identifies implementation priorities
SYNTHESIS:
[Provide a 2-3 paragraph synthesis]
KEY TAKEAWAYS:
1. [Most important insight]
2. [Second most important insight]
3. [Third most important insight]
RECOMMENDED APPROACH:
[Concise action plan]"#,
problem = problem,
ideas_formatted = ideas_formatted
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = OscillationConfig::default();
assert_eq!(config.cycles, 3);
assert!(config.min_ideas_per_diverge >= 5);
}
#[test]
fn test_gigathink_config() {
let config = OscillationConfig::gigathink();
assert_eq!(config.min_ideas_per_diverge, 10);
assert!(config
.divergent_dimensions
.contains(&DivergentDimension::Analogical));
assert!(config
.divergent_dimensions
.contains(&DivergentDimension::Contrarian));
}
#[test]
fn test_divergent_dimensions() {
let dim = DivergentDimension::Fluency;
assert!(dim.prompt_guidance().contains("many"));
let dim = DivergentDimension::Originality;
assert!(dim.prompt_guidance().contains("unusual"));
}
#[test]
fn test_convergent_criteria() {
let crit = ConvergentCriterion::Feasibility;
assert!(crit.evaluation_question().contains("implement"));
let crit = ConvergentCriterion::Impact;
assert!(crit.evaluation_question().contains("change"));
}
#[test]
fn test_oscillation_result_lineage() {
let result = OscillationResult {
problem: "Test".into(),
all_ideas: vec![
Idea {
id: 0,
content: "Root idea".into(),
dimension: DivergentDimension::Fluency,
parent_id: None,
cycle: 0,
scores: vec![],
priority: 0.8,
survived: true,
},
Idea {
id: 1,
content: "Child idea".into(),
dimension: DivergentDimension::Elaboration,
parent_id: Some(0),
cycle: 1,
scores: vec![],
priority: 0.9,
survived: true,
},
Idea {
id: 2,
content: "Grandchild idea".into(),
dimension: DivergentDimension::Originality,
parent_id: Some(1),
cycle: 2,
scores: vec![],
priority: 0.95,
survived: true,
},
],
divergent_phases: vec![],
convergent_phases: vec![],
final_ideas: vec![],
synthesis: "".into(),
metrics: OscillationMetrics {
total_ideas: 3,
surviving_ideas: 3,
survival_rate: 1.0,
avg_fluency: 1.0,
flexibility_score: 1.0,
originality_score: 0.8,
cycles_completed: 3,
},
};
let lineage = result.get_lineage(2);
assert_eq!(lineage.len(), 3);
assert_eq!(lineage[0].id, 0);
assert_eq!(lineage[1].id, 1);
assert_eq!(lineage[2].id, 2);
}
#[test]
fn test_metrics() {
let metrics = OscillationMetrics {
total_ideas: 30,
surviving_ideas: 5,
survival_rate: 5.0 / 30.0,
avg_fluency: 10.0,
flexibility_score: 0.85,
originality_score: 0.75,
cycles_completed: 3,
};
assert!(metrics.survival_rate < 0.2);
assert_eq!(metrics.cycles_completed, 3);
}
}