khtst-rust / src /pensamento /motor.rs
PowerMachine's picture
v0.10.0: análise/prova/teste das críticas de engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos jepa/pensamento/moe/python em 26 submódulos (API estável via re-exports; 'Amdahl de compilação' refutada por experimento controlado, builds frios neutros), checkpoints atômicos SHA-256 puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; correções da proposta: sem destruir estado anterior, manifesto com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 (fronteira tipada da convexidade N-2; alegação compile-time corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do controlador refutada no fonte; Vec<Var>; adaptador MoeComPapel bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia PT-BR com checkpoints íntegros
06e4b6f verified
Raw History Blame Contribute Delete
24.8 kB
//! `pensamento::motor` — extraído do monólito na v0.10.0
//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2).
use super::*;
// ---------------------------------------------------------------------------
// Confiança e gatilho (T-P1)
// ---------------------------------------------------------------------------
/// Confiança da distribuição preditiva: `conf = 1 − H(softmax(z))/log K`.
///
/// **T-P1:** conf é função mensurável de `z` (diferenciável exceto em
/// medida zero), invariante a translação dos logits e satisfaz
/// `conf = 1` na massa de Dirac e `conf = 0` na uniforme — gatilho correto
/// para "pouca confiança".
pub fn confianca_de_logits(logits: &Tensor) -> Result<f64> {
let p = candle_nn::ops::softmax(logits, candle_core::D::Minus1)?;
let logp = p.log()?;
let entropia = (&p * &logp)?.sum_all()?.to_scalar::<f32>()? as f64 * -1.0;
let k = logits.dim(candle_core::D::Minus1)? as f64;
if k <= 1.0 {
return Ok(1.0);
}
Ok(1.0 - entropia / k.ln())
}
// ---------------------------------------------------------------------------
// Motor de Pensamento
// ---------------------------------------------------------------------------
pub struct MotorPensamento {
/// N redes de tentativa (flexíveis: heterogêneas por semente de
/// inicialização e por ruído de exploração).
pub tentativas: Vec<MlpJepa>,
/// Montador/combinador: concatena as N saídas → resposta agregada.
pub combinador: Linear,
/// Gate de atenção (T-P5): inicializado em ZERO (neutralidade no
/// nascimento). Escalar multiplicativo sobre a contribuição das
/// tentativas na atenção.
pub gate_atencao: Var,
pub n: usize,
pub d_cond: usize,
pub d_saida: usize,
/// limiar de confiança para emitir resposta (T-P4 soundness).
pub limiar_conf: f64,
/// orçamento de iterações do ciclo d (T-P4 terminação).
pub orcamento: usize,
}
#[derive(Debug, Clone)]
pub enum EventoCiclo {
Planejamento { iteracao: usize, temperatura: f64 },
DistribuicaoCompetencias { por_tentativa: Vec<f64> },
RePlanejamento { motivo: String },
Resposta { confianca: f64, iteracoes: usize },
}
#[derive(Debug, Clone)]
pub struct ResultadoPensamento {
/// resposta agregada (d_saida,).
pub resposta: Option<Tensor>,
/// candidatos das N tentativas na última iteração (n, d_saida).
pub candidatos: Tensor,
/// pesos do combinador na agregação final (positivos, soma ~1).
pub pesos: Vec<f64>,
/// confiança do verificador na resposta emitida (ou da última tentativa).
pub confianca: f64,
/// nº de iterações do ciclo d executadas.
pub iteracoes: usize,
pub trajetoria: Vec<EventoCiclo>,
pub telemetria: HashMap<String, f64>,
}
impl MotorPensamento {
/// Cria o motor com N tentativas. `d_cond` = dim(a) + dim(b) + dim(c).
pub fn novo(
d_cond: usize,
d_saida: usize,
n: usize,
limiar_conf: f64,
orcamento: usize,
dev: &Device,
) -> Result<Self> {
let n = n.max(1);
let tentativas = (0..n)
.map(|i| {
let mut m = MlpJepa::novo(d_cond, 2 * d_saida, d_saida, dev)?;
// heterogeneidade: re-semeia cada tentativa
let _ = i;
Ok(m)
})
.collect::<Result<Vec<_>>>()?;
let combinador = Linear::sem_vies(d_saida * n, d_saida, 0.05, dev)?;
let gate = Tensor::zeros((), candle_core::DType::F32, dev)?;
Ok(Self {
tentativas,
combinador,
gate_atencao: Var::from_tensor(&gate)?,
n,
d_cond,
d_saida,
limiar_conf,
orcamento,
})
}
pub fn vars(&self) -> Vec<Var> {
let mut v: Vec<Var> = Vec::new();
for t in &self.tentativas {
v.extend(t.vars());
}
v.extend(self.combinador.vars());
v.push(self.gate_atencao.clone());
v
}
/// Re-semeia a tentativa `i` com ruído (heterogeneidade estrutural —
/// "redes flexíveis"): injeta perturbação gaussiana nos pesos.
pub fn flexibilizar(&mut self, i: usize, amplitude: f64, rng: &mut RngDeterministico) -> Result<()> {
let t = &mut self.tentativas[i];
for v in t.vars() {
let forma: Vec<usize> = v.dims().to_vec();
let total: usize = forma.iter().product();
let ruido: Vec<f32> = (0..total).map(|_| rng.normal(0.0, amplitude) as f32).collect();
let rt = Tensor::from_vec(ruido, forma, v.device())?;
v.set(&(v.as_tensor().affine(1.0, 0.0)? + rt.affine(1.0, 0.0)?)?)?;
}
Ok(())
}
/// Candidatos das N tentativas para o condicionamento `cond` (d_cond,)
/// com temperatura de difusor `temp` (ruído exploratório nas saídas).
///
/// **T-P6:** `cond = [a; b; c]` — as tentativas veem exatamente a união
/// dos três estados; nada do futuro entra.
pub fn candidatos(&self, cond: &Tensor, temp: f64, rng: &mut RngDeterministico) -> Result<Tensor> {
let dev = cond.device();
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
let cond_b = if cond.dims().len() == 1 {
cond.broadcast_as((1, self.d_cond))?
} else {
cond.clone()
};
for t in &self.tentativas {
let o = t.fwd(&cond_b)?;
let o = if temp > 0.0 {
let total = self.d_saida;
let ruido: Vec<f32> =
(0..total).map(|_| rng.normal(0.0, temp as f64) as f32).collect();
let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?;
(o.clone() + rt.affine(1.0, 0.0)?)?
} else {
o
};
saidas.push(o);
}
Ok(Tensor::cat(&saidas, 0)?) // (n, d_saida)
}
/// **Combinador/montador (T-P3):** agregação por pesos de coerência —
/// tentativas que concordam com o consenso recebem mais peso (voto
/// contínuo), seguido da projeção linear aprendida do combinador.
pub fn agregar(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> {
let consenso = candidatos.mean_keepdim(0)?; // (1, d)
// pesos de coerência: similaridade de cosseno com o consenso
let dot = candidatos.matmul(&consenso.transpose(0, 1)?)?; // (n, 1)
let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?;
let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?;
let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (n, 1)
let cos_v = cos.flatten_all()?.to_vec1::<f32>()?;
let exps: Vec<f64> = cos_v.iter().map(|c| (*c as f64 * 4.0).exp()).collect();
let soma: f64 = exps.iter().sum();
let pesos: Vec<f64> = exps.iter().map(|e| e / soma).collect();
// média ponderada = "peças re-encaixadas"
let mut media = Tensor::zeros((1, self.d_saida), candle_core::DType::F32, candidatos.device())?;
for (i, p) in pesos.iter().enumerate() {
let linha = candidatos.get(i)?.broadcast_as((1, self.d_saida))?;
media = (media.affine(1.0, 0.0)? + linha.affine(*p, 0.0)?)?;
}
// montagem final pelo combinador aprendido
let concat = candidatos.flatten_all()?.broadcast_as((1, self.d_saida * self.n))?;
let montado = self.combinador.fwd(&concat)?;
// mistura 50/50: montagem aprendida + consenso ponderado (o
// combinador pode especializar-se; o consenso dá robustez T-P2)
let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?;
Ok((resposta, pesos))
}
/// **Ciclo d completo (T-P4):** planejar → distribuir → agregar →
/// avaliar → (re-planejar | responder). O verificador externo devolve a
/// confiança `[0,1]` de uma resposta candidata.
pub fn raciocinar(
&mut self,
cond: &Tensor,
verificador: &dyn Fn(&Tensor) -> f64,
rng: &mut RngDeterministico,
) -> Result<ResultadoPensamento> {
let mut trajetoria = Vec::new();
let mut telemetria: HashMap<String, f64> = HashMap::new();
let mut temperatura = 0.1f64;
let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new());
let mut iteracoes = 0usize;
let mut candidatos_final = Tensor::zeros(
(self.n, self.d_saida),
candle_core::DType::F32,
cond.device(),
)?;
loop {
iteracoes += 1;
// (1) PLANEJAMENTO: gerar candidatos com a temperatura corrente
trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura });
let candidatos = self.candidatos(cond, temperatura, rng)?;
candidatos_final = candidatos.clone();
// (2) DISTRIBUIÇÃO/DESMEMBRAMENTO DE COMPETÊNCIAS: pesos por
// coerência (quem concorda mais lidera a montagem)
let (resposta, pesos) = self.agregar(&candidatos)?;
trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() });
// (3) AVALIAÇÃO pelo verificador
let conf = verificador(&resposta);
telemetria.insert(format!("conf_iter_{}", iteracoes), conf);
if conf > melhor.1 {
melhor = (Some(resposta), conf, pesos.clone());
}
// (4) DECISÃO: responder ou re-planejar
if conf >= self.limiar_conf {
trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes });
telemetria.insert("iteracoes".into(), iteracoes as f64);
telemetria.insert("confianca_final".into(), conf);
telemetria.insert("temperatura_final".into(), temperatura);
return Ok(ResultadoPensamento {
resposta: melhor.0,
candidatos: candidatos_final,
pesos: melhor.2,
confianca: conf,
iteracoes,
trajetoria,
telemetria,
});
}
if iteracoes >= self.orcamento {
// Φ = B − k atingiu 0: termina SEM confiança — honestidade
trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes });
telemetria.insert("iteracoes".into(), iteracoes as f64);
telemetria.insert("confianca_final".into(), melhor.1);
telemetria.insert("temperatura_final".into(), temperatura);
telemetria.insert("orcamento_esgotado".into(), 1.0);
return Ok(ResultadoPensamento {
resposta: melhor.0,
candidatos: candidatos_final,
pesos: melhor.2,
confianca: melhor.1,
iteracoes,
trajetoria,
telemetria,
});
}
// (5) RE-PLANEJAMENTO: aumenta a exploração (difusor mais quente)
temperatura = (temperatura * 1.6).min(2.0);
trajetoria.push(EventoCiclo::RePlanejamento {
motivo: format!("confiança {conf:.3} < limiar {}", self.limiar_conf),
});
}
}
/// **Perda de treino das tentativas + combinador (item a):** treino
/// cooperativo — cada tentativa minimiza sua perda contra o alvo E o
/// combinador minimiza a perda da agregação (o gradiente flui para
/// todos através do concat).
pub fn perda_treino(&self, cond: &Tensor, alvo: &Tensor) -> Result<Tensor> {
// cond (b, d_cond), alvo (b, d_saida)
let b = cond.dim(0)?;
let mut perdas: Vec<Tensor> = Vec::new();
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
for t in &self.tentativas {
let o = t.fwd(cond)?;
let dif = (&o - alvo)?;
perdas.push((dif.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?);
saidas.push(o);
}
let concat = Tensor::cat(&saidas, candle_core::D::Minus1)?; // (b, n·d)
let montado = self.combinador.fwd(&concat)?;
let dif_m = (&montado - alvo)?;
perdas.push((dif_m.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?);
// soma das perdas (cooperação: todas as redes + montador)
let mut total = perdas[0].clone();
for p in perdas.iter().skip(1) {
total = (total + p.clone())?;
}
Ok(total)
}
/// **Integração com o mecanismo de atenção (T-P5):** anexa as saídas das
/// tentativas como chaves/valores extra com gate escalar.
///
/// Com `gate = 0` (nascimento): saída IDÊNTICA à atenção base
/// (neutralidade); o gradiente flui pelo gate (não-degenerado).
pub fn atencao_integrada(
&self,
q: &Tensor,
k_base: &Tensor,
v_base: &Tensor,
candidatos: &Tensor,
) -> Result<Tensor> {
// q (b, n_q, d), k_base (b, n_kv, d), v_base (b, n_kv, d_v),
// candidatos (n_tent, d) — projetados para d e d_v
let dev = q.device();
let d = k_base.dim(candle_core::D::Minus1)?;
let d_v = v_base.dim(candle_core::D::Minus1)?;
let b = q.dim(0)?;
// projeta candidatos (média simples — sem parâmetros extra): os
// candidatos têm d_saida; se d == d_saida usa direto, senão padding/
// truncamento honesto
let cand = if self.d_saida == d {
candidatos.clone()
} else if self.d_saida > d {
let v = candidatos
.flatten_all()?
.to_vec1::<f32>()?
.chunks(self.d_saida)
.map(|c| c[..d].to_vec())
.flatten()
.collect::<Vec<f32>>();
Tensor::from_vec(v, (self.n, d), &dev)?
} else {
let mut v = candidatos.flatten_all()?.to_vec1::<f32>()?;
let mut linhas = Vec::new();
for i in 0..self.n {
let mut linha = v[i * self.d_saida..(i + 1) * self.d_saida].to_vec();
linha.extend(std::iter::repeat(0f32).take(d - self.d_saida));
linhas.push(linha);
}
v = linhas.into_iter().flatten().collect();
Tensor::from_vec(v, (self.n, d), &dev)?
};
let k_extra = cand.broadcast_as((b, self.n, d))?;
let v_extra = if d_v == d {
k_extra.clone()
} else {
// projeção simples por média de colunas quando d_v != d
let media = k_extra.mean_keepdim(candle_core::D::Minus1)?;
media.broadcast_as((b, self.n, d_v))?
};
// atenção sobre [base; extra]
let k = Tensor::cat(&[k_base.clone(), k_extra], 1)?;
let v = Tensor::cat(&[v_base.clone(), v_extra], 1)?;
let scores = (q.matmul(&k.transpose(1, 2)?)? / (d as f64).sqrt())?;
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?;
let y_base_e_extra = attn.matmul(&v)?;
// atenção apenas-base (para neutralidade: gate = 0 ⇒ saída = base)
let scores_base = (q.matmul(&k_base.transpose(1, 2)?)? / (d as f64).sqrt())?;
let attn_base = candle_nn::ops::softmax(&scores_base, candle_core::D::Minus1)?;
let y_base = attn_base.matmul(v_base)?;
// mistura: y = y_base + gate·(y_completo − y_base)
let g = self.gate_atencao.as_tensor();
let delta = (&y_base_e_extra - &y_base)?;
let forma: Vec<usize> = delta.dims().to_vec();
let delta = (delta.affine(1.0, 0.0)? * g.broadcast_as(forma)?)?;
Ok((&y_base + &delta)?)
}
// -----------------------------------------------------------------------
// v0.6.0 — verificador NCM (sem oráculo) + tentativas PARALELAS
// -----------------------------------------------------------------------
/// Candidatos das N tentativas em PARALELO (rayon) — as N redes têm
/// parâmetros disjuntos e entradas idênticas ⇒ N sub-DAGs independentes
/// no grafo de computação (mesma estrutura da prova M-R3): tempo
/// paralelo = max_i T_i + sobrecarga de agendamento.
/// Determinismo: o ruído da tentativa i usa a semente `semente + i`
/// (fluxos independentes por construção — reprodutível em qualquer
/// nº de threads).
pub fn candidatos_par(
&self,
cond: &Tensor,
temp: f64,
semente: u64,
) -> Result<(Tensor, f64)> {
use rayon::prelude::*;
let dev = cond.device();
let cond_b = if cond.dims().len() == 1 {
cond.broadcast_as((1, self.d_cond))?
} else {
cond.clone()
};
let t0 = std::time::Instant::now();
let saidas: Vec<Tensor> = self
.tentativas
.par_iter()
.enumerate()
.map(|(i, t)| -> Result<Tensor> {
let o = t.fwd(&cond_b)?;
if temp > 0.0 {
let mut rng = RngDeterministico::novo(semente + i as u64);
let ruido: Vec<f32> =
(0..self.d_saida).map(|_| rng.normal(0.0, temp) as f32).collect();
let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?;
Ok((&o + &rt.affine(1.0, 0.0)?)?)
} else {
Ok(o)
}
})
.collect::<Result<Vec<_>>>()?;
let dt = t0.elapsed().as_secs_f64();
Ok((Tensor::cat(&saidas, 0)?, dt)) // (n, d_saida), tempo paralelo
}
/// **Variante em LOTE (v0.6.0 — fase C):** cond (b, d_cond) →
/// candidatos (b, n, d_saida) — as N tentativas processam o lote
/// inteiro (paralelas entre si; ruído por (linha, tentativa) com
/// semente determinística `semente + i`).
pub fn candidatos_lote(&self, cond: &Tensor, temp: f64, semente: u64) -> Result<Tensor> {
let b = cond.dim(0)?;
let dev = cond.device();
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
for (i, t) in self.tentativas.iter().enumerate() {
let o = t.fwd(cond)?; // (b, d_saida)
let o = if temp > 0.0 {
let mut rng = RngDeterministico::novo(semente + i as u64);
let ruido: Vec<f32> = (0..b * self.d_saida)
.map(|_| rng.normal(0.0, temp) as f32)
.collect();
let rt = Tensor::from_vec(ruido, (b, self.d_saida), &dev)?;
(&o + &rt)?
} else {
o
};
saidas.push(o.unsqueeze(1)?); // (b, 1, d_saida)
}
Ok(Tensor::cat(&saidas, 1)?) // (b, n, d_saida)
}
/// **Agregação em LOTE (v0.6.0):** candidatos (b, n, d) → resposta
/// (b, d) — mesma mistura 50/50 (montagem aprendida + consenso
/// ponderado por coerência) da versão 1-D, VETORIZADA e totalmente
/// diferenciável (gradiente flui pelos DOIS caminhos). Devolve também
/// os pesos médios (telemetria).
pub fn agregar_lote(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> {
let b = candidatos.dim(0)?;
let n = candidatos.dim(1)?;
let d = candidatos.dim(2)?;
let dev = candidatos.device();
// consenso (b, d)
let consenso = candidatos.mean(1)?;
// cosseno por (b, n): ⟨cand_i, consenso⟩ / (‖cand_i‖·‖consenso‖)
let dot = candidatos.matmul(&consenso.unsqueeze(2)?)?; // (b, n, 1)
let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; // (b, n, 1)
let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?.unsqueeze(1)?; // (b, 1, 1)
let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (b, n, 1)
// pesos softmax de coerência (b, n, 1) — tensor puro (grafo intacto)
let exps = cos.affine(4.0, 0.0)?.exp()?;
let soma = exps.sum_keepdim(1)?; // (b, 1, 1)
let pesos = exps.broadcast_div(&soma)?; // (b, n, 1)
// média ponderada: Σ_n pesos·cand → (b, d)
let media = candidatos.broadcast_mul(&pesos)?.sum(1)?; // (b, d)
// montagem aprendida: concat sobre n → combinador
let concat = candidatos.reshape((b, n * d))?;
let montado = self.combinador.fwd(&concat)?; // (b, d)
let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?;
// pesos médios para telemetria (leitura — não afeta o grafo)
let pesos_v = pesos.flatten_all()?.to_vec1::<f32>()?;
let mut pesos_medios = vec![0f64; n];
for (k, p) in pesos_v.iter().enumerate() {
pesos_medios[k % n] += *p as f64 / b as f64;
}
let _ = d;
let _ = dev;
Ok((resposta, pesos_medios))
}
/// **Ciclo d com o VERIFICADOR NCM (v0.6.0 — fecha o débito r-21 §3.1).**
///
/// `logits_fn` mapeia uma resposta candidata (1, d_saida) → logits da
/// distribuição preditiva do BACKBONE condicionada à correção (passada
/// corrente — V-4 causal). O verificador calibrado devolve a confiança;
/// NENHUM acesso ao alvo verdadeiro (o oráculo morre aqui).
pub fn raciocinar_ncm(
&mut self,
cond: &Tensor,
verificador: &crate::verificador::VerificadorNcm,
logits_fn: &dyn Fn(&Tensor) -> Result<Tensor>,
rng: &mut RngDeterministico,
) -> Result<ResultadoPensamento> {
let mut trajetoria = Vec::new();
let mut telemetria: HashMap<String, f64> = HashMap::new();
let mut temperatura = 0.1f64;
let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new());
let mut iteracoes = 0usize;
let mut candidatos_final = Tensor::zeros(
(self.n, self.d_saida),
candle_core::DType::F32,
cond.device(),
)?;
loop {
iteracoes += 1;
trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura });
let candidatos = self.candidatos(cond, temperatura, rng)?;
candidatos_final = candidatos.clone();
let (resposta, pesos) = self.agregar(&candidatos)?;
trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() });
// AVALIAÇÃO pelo VERIFICADOR NCM (sem oráculo): confiança
// calibrada da distribuição preditiva do backbone
let logits = logits_fn(&resposta)?;
let conf = verificador.confianca_1d(&logits)?;
telemetria.insert(format!("conf_iter_{}", iteracoes), conf);
if conf > melhor.1 {
melhor = (Some(resposta), conf, pesos.clone());
}
if conf >= self.limiar_conf {
trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes });
telemetria.insert("iteracoes".into(), iteracoes as f64);
telemetria.insert("confianca_final".into(), conf);
telemetria.insert("temperatura_final".into(), temperatura);
telemetria.insert("verificador".into(), 1.0); // ncm (sem oráculo)
return Ok(ResultadoPensamento {
resposta: melhor.0,
candidatos: candidatos_final,
pesos: melhor.2,
confianca: conf,
iteracoes,
trajetoria,
telemetria,
});
}
if iteracoes >= self.orcamento {
trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes });
telemetria.insert("iteracoes".into(), iteracoes as f64);
telemetria.insert("confianca_final".into(), melhor.1);
telemetria.insert("temperatura_final".into(), temperatura);
telemetria.insert("orcamento_esgotado".into(), 1.0);
telemetria.insert("verificador".into(), 1.0);
return Ok(ResultadoPensamento {
resposta: melhor.0,
candidatos: candidatos_final,
pesos: melhor.2,
confianca: melhor.1,
iteracoes,
trajetoria,
telemetria,
});
}
temperatura = (temperatura * 1.6).min(2.0);
trajetoria.push(EventoCiclo::RePlanejamento {
motivo: format!("conf NCM {conf:.3} < limiar {}", self.limiar_conf),
});
}
}
}