//! `python::nucleo` — 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::*; #[pyfunction] pub(crate) fn versao() -> &'static str { concat!("khtst-rust-", env!("CARGO_PKG_VERSION")) } #[pyfunction] pub(crate) fn info_build() -> String { format!( "rustc={}; perfil={}; features={}; alvo={}", build_info::RUSTC_VERSION, build_info::PROFILE, build_info::FEATURES_STR, build_info::TARGET, ) } /// Lint de config com sugestões por Levenshtein (diagnóstico de digitação). #[pyfunction] pub(crate) fn lint_config(py: Python, caminho: &str) -> PyResult { let txt = std::fs::read_to_string(caminho) .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{caminho}: {e}")))?; let v: serde_json::Value = serde_json::from_str(&txt) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; let achatado = diagnostico::achatar(&v); let avisos = diagnostico::lint_chaves(&achatado); let saida: Vec = avisos .iter() .map(|a| serde_json::json!({ "chave": a.chave, "sugestao": a.sugestao, "causa": a.causa, "correcao": a.correcao, })) .collect(); Ok(pythonize::pythonize(py, &serde_json::json!(saida))?.into()) } #[pyclass(name = "Bpe")] pub(crate) struct BpePy { bpe: tokens::Bpe, } #[pymethods] impl BpePy { #[staticmethod] fn treinar(textos: Vec, vocab_alvo: usize) -> PyResult { Ok(BpePy { bpe: tokens::Bpe::treinar(&textos, vocab_alvo) }) } #[staticmethod] fn carregar(caminho: &str) -> PyResult { let txt = std::fs::read_to_string(caminho) .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; let v: serde_json::Value = serde_json::from_str(&txt) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; let bpe = tokens::Bpe::de_json(&v) .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("formato BPE inválido"))?; Ok(BpePy { bpe }) } fn codificar(&mut self, texto: &str) -> Vec { self.bpe.codificar(texto) } fn decodificar(&self, ids: Vec) -> String { self.bpe.decodificar(&ids) } fn salvar(&self, caminho: &str) -> PyResult<()> { let txt = serde_json::to_string(&self.bpe.para_json()).unwrap(); crate::checkpoint::salvar_atomico(caminho, &txt) .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; Ok(()) } #[getter] fn n_vocab(&self) -> usize { self.bpe.n_vocab() } } #[pyclass(name = "Config")] pub(crate) struct ConfigPy { pub(crate) cfg: config::ConfigKhtst, caminho: Option, } #[pymethods] impl ConfigPy { #[new] #[allow(never_type_fallback)] #[pyo3(signature = (caminho=None))] fn new(caminho: Option) -> PyResult { let cfg = match &caminho { Some(c) => { let txt = std::fs::read_to_string(c) .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{c}: {e}")))?; let v: serde_json::Value = serde_json::from_str(&txt) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; serde_json::from_value(v) .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("config: {e}")))? } None => config::ConfigKhtst::default(), }; cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)?; Ok(ConfigPy { cfg, caminho }) } fn validar(&self) -> PyResult<()> { self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err) } fn checar_orcamento_cache(&self) -> PyResult { self.cfg.checar_orcamento_cache().map_err(pyo3::exceptions::PyValueError::new_err) } fn resumo(&self) -> String { format!( "modelo(vocab={}, d={}, camadas={}, ctx={}) ncm(L={}, niveis={:?}, K={}, d_lat={}, modo={}, ativo={})", self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas, self.cfg.modelo.comprimento_ctx, self.cfg.l_niveis(), self.cfg.ncm.niveis, self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo, ) } #[getter] fn l_niveis(&self) -> usize { self.cfg.l_niveis() } } #[pyclass(name = "PonteDupla")] pub(crate) struct PonteDuplaPy { ponte: ponte::PonteDupla, } #[pymethods] impl PonteDuplaPy { #[new] #[pyo3(signature = (capacidade=64, timeout_ms=2000))] fn new(capacidade: usize, timeout_ms: u64) -> Self { PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) } } fn rodada(&self, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult<(Vec, Vec)> { let (ma, mb) = self.ponte.rodada(carga_a, carga_b, nivel) .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; Ok((ma.carga, mb.carga)) } fn demo_professor_aluno(&self, rodadas: usize, dim: usize) -> PyResult { let inicio: Vec = (0..dim).map(|i| (i as f32) * 0.01).collect(); let stats = ponte::demo_professor_aluno(&self.ponte, rodadas, inicio, &|a, _| { a.iter().map(|x| x * 1.05).collect() }) .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; Python::with_gil(|py| { Ok(pythonize::pythonize(py, &serde_json::to_value(&stats).unwrap())?.into()) }) } fn estatisticas(&self) -> PyResult { let s = self.ponte.estatisticas(); Python::with_gil(|py| { Ok(pythonize::pythonize(py, &serde_json::to_value(&s).unwrap())?.into()) }) } } #[cfg(feature = "ponte_async")] #[pyclass(name = "PonteAsync")] pub(crate) struct PonteAsyncPy { interna: std::sync::Arc, } #[cfg(feature = "ponte_async")] #[pymethods] impl PonteAsyncPy { #[new] #[pyo3(signature = (capacidade=64, timeout_ms=2000))] fn new(capacidade: usize, timeout_ms: u64) -> Self { PonteAsyncPy { interna: std::sync::Arc::new(ponte::assincrona::PonteAsync::nova(capacidade, timeout_ms)) } } /// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea. fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult> { let p = self.interna.clone(); pyo3_async_runtimes::tokio::future_into_py(py, async move { let (ma, mb) = p.rodada_async(carga_a, carga_b, nivel) .await .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; Ok::<_, PyErr>((ma.carga, mb.carga)) }) } } /// Utilitários de prova expostos ao Python (V-série da suíte). #[pyfunction] pub(crate) fn verificar_mascara(cfg: &ConfigPy) -> PyResult { let dev = candle_core::Device::Cpu; let m = mascaras::mascara_block_causal(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &dev) .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; mascaras::contar_vazamentos(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &m) .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) } #[pyfunction] pub(crate) fn verificar_stiefel(k: usize, d: usize) -> PyResult { let dev = candle_core::Device::Cpu; let c = stiefel::init_stiefel(k, d, &dev) .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; stiefel::erro_ortogonalidade(&c.transpose(0, 1).unwrap()).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) } #[pyfunction] pub(crate) fn checkpoint_valido(caminho: &str) -> bool { crate::checkpoint::validar(caminho) } #[pyfunction] pub(crate) fn checkpoint_manifesto(caminho: &str) -> Option { std::fs::read_to_string(crate::checkpoint::caminho_manifesto(caminho)).ok() } #[pyfunction] pub(crate) fn sha256_arquivo(caminho: &str) -> Option { std::fs::read(caminho).ok().map(|d| crate::checkpoint::sha256_hex(&d)) }