Skip to main content

qualia_cli/
science.rs

1// science.rs — CLI runners for domain-science modules:
2//   chem, bio, geo, thermo, geometric algebra, clinical, economics
3
4// ── Chemistry ─────────────────────────────────────────────────────────────────
5
6pub fn run_chem_smiles(smiles: &str) {
7    use qualia_core_db::domains::chemical::organic_chemistry::{
8        compute_descriptors, compute_logp, compute_tpsa, evaluate_lipinski, exact_molecular_weight,
9        formula_string, parse_smiles,
10    };
11    let mol = parse_smiles(smiles);
12    let desc = compute_descriptors(&mol);
13    println!("SMILES: {smiles}");
14    println!("  Formula  : {}", formula_string(&mol));
15    println!("  Exact MW : {:.4} Da", exact_molecular_weight(&mol));
16    println!("  LogP     : {:.4}", compute_logp(&mol));
17    println!("  TPSA     : {:.4} Ų", compute_tpsa(&mol));
18    println!("  HBA      : {}", desc.hb_acceptors);
19    println!("  HBD      : {}", desc.hb_donors);
20    println!("  Rot bonds: {}", desc.rotatable_bonds);
21    let lip = evaluate_lipinski(&desc);
22    println!(
23        "  Lipinski : {} (violations: {})",
24        if lip.passes { "PASS" } else { "FAIL" },
25        lip.violations
26    );
27}
28
29pub fn run_chem_thermo(reaction: &str, a: f64, b: f64, c: f64) {
30    use qualia_core_db::domains::chemical::organic_chemistry::{
31        arrhenius_rate, gibbs_free_energy, henderson_hasselbalch,
32    };
33    match reaction.to_ascii_lowercase().as_str() {
34        "arrhenius" => {
35            // a = pre-exponential factor, b = activation energy (J/mol), c = temp (K)
36            let rate = arrhenius_rate(a, b, c);
37            println!("Arrhenius rate: k = {rate:.6e} s⁻¹");
38            println!("  A={a:.3e}, Ea={b:.3e} J/mol, T={c} K");
39        }
40        "gibbs" => {
41            // a = ΔH (kJ/mol), b = ΔS (J/mol·K), c = temp (K)
42            let dg = gibbs_free_energy(a * 1000.0, b, c);
43            println!(
44                "Gibbs free energy: ΔG = {dg:.4} J/mol ({:.4} kJ/mol)",
45                dg / 1000.0
46            );
47            println!("  ΔH={a} kJ/mol, ΔS={b} J/mol·K, T={c} K");
48            println!(
49                "  Spontaneous: {}",
50                if dg < 0.0 {
51                    "yes (ΔG < 0)"
52                } else {
53                    "no (ΔG ≥ 0)"
54                }
55            );
56        }
57        "henderson-hasselbalch" | "hh" => {
58            // a = pKa, b = [base] (M), c = [acid] (M)
59            let ph = henderson_hasselbalch(a, b, c);
60            println!("Henderson-Hasselbalch: pH = {ph:.4}");
61            println!("  pKa={a}, [base]={b} M, [acid]={c} M");
62        }
63        other => {
64            eprintln!("Unknown reaction '{other}'. Use: arrhenius | gibbs | henderson-hasselbalch")
65        }
66    }
67}
68
69pub fn run_chem_druglike(smiles: &str) {
70    use qualia_core_db::domains::chemical::organic_chemistry::{
71        compute_descriptors, compute_logp, compute_tpsa, evaluate_egan, evaluate_ghose,
72        evaluate_lipinski, evaluate_veber, parse_smiles, predict_bbb_permeation,
73    };
74    let mol = parse_smiles(smiles);
75    let desc = compute_descriptors(&mol);
76    let logp = compute_logp(&mol);
77    let tpsa = compute_tpsa(&mol);
78    let lip = evaluate_lipinski(&desc);
79    let veb = evaluate_veber(&desc);
80    let gho = evaluate_ghose(&desc);
81    let ega = evaluate_egan(&desc);
82    let bbb = predict_bbb_permeation(desc.molecular_weight, logp, tpsa, desc.hb_donors);
83    println!("Drug-likeness for SMILES: {smiles}");
84    println!(
85        "  Lipinski : {} (violations: {})",
86        if lip.passes { "PASS" } else { "FAIL" },
87        lip.violations
88    );
89    println!("  Veber    : {}", if veb.passes { "PASS" } else { "FAIL" });
90    println!("  Ghose    : {}", if gho.passes { "PASS" } else { "FAIL" });
91    println!("  Egan     : {}", if ega.passes { "PASS" } else { "FAIL" });
92    println!(
93        "  BBB      : {} (MPO score: {})",
94        if bbb.is_cns_penetrant {
95            "CNS-penetrant"
96        } else {
97            "non-penetrant"
98        },
99        bbb.clark_score
100    );
101}
102
103pub fn run_chem_pka(pka: f64, conc_base: f64, conc_acid: f64) {
104    use qualia_core_db::domains::chemical::organic_chemistry::{
105        henderson_hasselbalch, ionisation_fraction,
106    };
107    let ph = henderson_hasselbalch(pka, conc_base, conc_acid);
108    let frac = ionisation_fraction(ph, pka);
109    println!("Henderson-Hasselbalch:");
110    println!("  pKa={pka}, [A⁻]={conc_base} M, [HA]={conc_acid} M");
111    println!("  pH              = {ph:.4}");
112    println!("  Ionised fraction= {frac:.4}");
113}
114
115// ── Biology ───────────────────────────────────────────────────────────────────
116
117pub fn run_bio_align(query: &str, target: &str, mode: &str) {
118    use qualia_core_db::domains::biological::bioinformatics::{align_nucleotide, align_protein};
119    let q = query.as_bytes();
120    let t = target.as_bytes();
121    let result = match mode.to_ascii_lowercase().as_str() {
122        "protein" | "aa" => align_protein(q, t),
123        _ => align_nucleotide(q, t),
124    };
125    println!("Sequence alignment ({mode}):");
126    println!("  Query  : {query}");
127    println!("  Target : {target}");
128    println!("  Score  : {}", result.score);
129    println!("  Aligned: {}", result.aligned_query.len());
130    println!("  Gaps   : {}", result.num_gaps);
131    println!("  Matches: {}", result.num_matches);
132}
133
134pub fn run_bio_kmer(sequence: &str, k: usize) {
135    use qualia_core_db::domains::biological::bioinformatics::kmer_frequencies;
136    let freqs = kmer_frequencies(sequence.as_bytes(), k);
137    println!(
138        "k-mer frequencies (k={k}) for sequence len={}:",
139        sequence.len()
140    );
141    println!("  Distinct k-mers: {}", freqs.len());
142    for (hash, count) in freqs.iter().take(8) {
143        println!("  0x{hash:016x} → {count}");
144    }
145    if freqs.len() > 8 {
146        println!("  … ({} total)", freqs.len());
147    }
148}
149
150pub fn run_bio_translate(dna: &str) {
151    use qualia_core_db::domains::biological::bioinformatics::translate_dna_to_protein;
152    let dna_bytes = dna.as_bytes();
153    let mut protein_buf = vec![0u8; dna_bytes.len() / 3 + 1];
154    let n = translate_dna_to_protein(dna_bytes, &mut protein_buf);
155    let protein = std::str::from_utf8(&protein_buf[..n]).unwrap_or("(invalid UTF-8)");
156    println!("DNA → protein translation:");
157    println!("  DNA    : {}", &dna[..dna.len().min(60)]);
158    println!("  Protein: {protein}  ({n} aa)");
159}
160
161pub fn run_bio_isoelectric(protein: &str) {
162    use qualia_core_db::domains::biological::bioinformatics::calculate_isoelectric_point;
163    let pi = calculate_isoelectric_point(protein.as_bytes());
164    println!(
165        "Isoelectric point for protein '{}':",
166        &protein[..protein.len().min(20)]
167    );
168    println!("  pI = {pi:.4}");
169}
170
171pub fn run_bio_jaccard(sketch_a: &str, sketch_b: &str) {
172    use qualia_core_db::domains::biological::bioinformatics::jaccard_similarity;
173    let parse_hashes = |s: &str| -> Vec<u64> {
174        s.split(',')
175            .filter_map(|t| {
176                let t = t.trim();
177                if t.starts_with("0x") || t.starts_with("0X") {
178                    u64::from_str_radix(&t[2..], 16).ok()
179                } else {
180                    t.parse::<u64>().ok()
181                }
182            })
183            .collect()
184    };
185    let a = parse_hashes(sketch_a);
186    let b = parse_hashes(sketch_b);
187    let j = jaccard_similarity(&a, &b);
188    println!("Jaccard similarity:");
189    println!("  |sketch A| = {}, |sketch B| = {}", a.len(), b.len());
190    println!("  J(A,B)     = {j:.6}");
191}
192
193pub fn run_bio_minhash(sequence: &str, k: usize, sketch_size: usize) {
194    use qualia_core_db::domains::biological::bioinformatics::minhash_sketch;
195    let sketch = minhash_sketch(sequence.as_bytes(), k, sketch_size);
196    println!("MinHash sketch (k={k}, size={sketch_size}):");
197    println!("  Sequence len: {}", sequence.len());
198    for h in sketch.iter().take(5) {
199        println!("  0x{h:016x}");
200    }
201    if sketch.len() > 5 {
202        println!("  … ({} total)", sketch.len());
203    }
204}
205
206// ── Geospatial ────────────────────────────────────────────────────────────────
207
208pub fn run_geo_embed_h3(index: u64) {
209    use qualia_core_db::domains::geospatial::spatial::embed_h3_context;
210    let context = embed_h3_context(index, 0, 0);
211    println!("H3 index embedding:");
212    println!("  Input  : 0x{index:016x}");
213    println!("  Context: 0x{context:016x}");
214}
215
216// ── Thermodynamics ────────────────────────────────────────────────────────────
217
218pub fn run_thermo_gibbs(enthalpy: f64, entropy: f64, temp: f64) {
219    use qualia_core_db::domains::physical::thermodynamics::ThermodynamicSampler;
220    let sampler = ThermodynamicSampler::new(temp, 1);
221    let dg = sampler.calculate_gibbs_free_energy(enthalpy, entropy);
222    println!("Gibbs free energy:");
223    println!("  H={enthalpy:.4} J, S={entropy:.4} J/K, T={temp:.1} K");
224    println!("  ΔG = H - TS = {dg:.6} J");
225    println!("  Spontaneous: {}", if dg < 0.0 { "yes" } else { "no" });
226}
227
228pub fn run_thermo_anneal(initial_temp: f64, particles: usize, proposed_energy: f64, random: f64) {
229    use qualia_core_db::domains::physical::thermodynamics::ThermodynamicSampler;
230    let mut sampler = ThermodynamicSampler::new(initial_temp, particles);
231    let accepted = sampler.metropolis_step(proposed_energy, random);
232    println!("Metropolis-Hastings step:");
233    println!("  T_init={initial_temp} K,  particles={particles}");
234    println!("  Proposed ΔE = {proposed_energy:.6}");
235    println!("  Uniform u   = {random:.6}");
236    println!("  Accepted    : {accepted}");
237}
238
239// ── Geometric algebra ─────────────────────────────────────────────────────────
240
241fn parse_vec3(s: &str) -> Option<[f32; 3]> {
242    let v: Vec<f32> = s.split(',').filter_map(|t| t.trim().parse().ok()).collect();
243    if v.len() >= 3 {
244        Some([v[0], v[1], v[2]])
245    } else {
246        eprintln!(
247            "Need 3 comma-separated values for a vector, got {}.",
248            v.len()
249        );
250        None
251    }
252}
253
254pub fn run_geometric_cross(a_str: &str, b_str: &str) {
255    use qualia_core_db::geometric_algebra::utils::{cross_product, dot_product};
256    let (Some(a), Some(b)) = (parse_vec3(a_str), parse_vec3(b_str)) else {
257        return;
258    };
259    let cross = cross_product(&a, &b);
260    let dot = dot_product(&a, &b);
261    println!("Geometric algebra:");
262    println!("  a = [{:.4}, {:.4}, {:.4}]", a[0], a[1], a[2]);
263    println!("  b = [{:.4}, {:.4}, {:.4}]", b[0], b[1], b[2]);
264    println!(
265        "  a×b = [{:.6}, {:.6}, {:.6}]",
266        cross[0], cross[1], cross[2]
267    );
268    println!("  a·b = {dot:.6}");
269}
270
271pub fn run_geometric_angle(a_str: &str, b_str: &str) {
272    use qualia_core_db::geometric_algebra::utils::{angle_between_vectors, rad_to_deg};
273    let (Some(a), Some(b)) = (parse_vec3(a_str), parse_vec3(b_str)) else {
274        return;
275    };
276    let angle_rad = angle_between_vectors(&a, &b);
277    let angle_deg = rad_to_deg(angle_rad);
278    println!("Angle between vectors:");
279    println!("  a = [{:.4}, {:.4}, {:.4}]", a[0], a[1], a[2]);
280    println!("  b = [{:.4}, {:.4}, {:.4}]", b[0], b[1], b[2]);
281    println!("  θ = {angle_rad:.6} rad  ({angle_deg:.4}°)");
282}
283
284// ── Clinical ──────────────────────────────────────────────────────────────────
285
286pub fn run_clinical_framingham(
287    age: u8,
288    sex_male: bool,
289    total_chol: f64,
290    hdl_chol: f64,
291    systolic_bp: f64,
292    bp_treated: bool,
293    smoker: bool,
294    diabetic: bool,
295) {
296    use qualia_core_db::clinical_engine::{framingham_10yr_risk, FraminghamInput};
297    let input = FraminghamInput {
298        age,
299        sex_male,
300        total_cholesterol_mmol: total_chol,
301        hdl_cholesterol_mmol: hdl_chol,
302        systolic_bp,
303        bp_treated,
304        current_smoker: smoker,
305        diabetic,
306    };
307    let r = framingham_10yr_risk(&input);
308    println!("Framingham 10-year CVD risk:");
309    println!(
310        "  Age={age}, sex={}, TC={total_chol:.2} mmol/L, HDL={hdl_chol:.2}, SBP={systolic_bp:.0}",
311        if sex_male { "M" } else { "F" }
312    );
313    println!("  Treated={bp_treated}, Smoker={smoker}, Diabetic={diabetic}");
314    println!("  Risk     : {:.1}%", r.risk_10yr * 100.0);
315    println!("  Category : {:?}", r.category);
316    println!("  Log score: {:.4}", r.log_score);
317}
318
319pub fn run_clinical_sofa(
320    pao2_fio2: f64,
321    platelets: f64,
322    bilirubin: f64,
323    map: f64,
324    gcs: u8,
325    creatinine: f64,
326) {
327    use qualia_core_db::clinical_engine::{sofa_score, SofaInput};
328    let input = SofaInput {
329        pao2_fio2_ratio: pao2_fio2,
330        platelets_10_9_l: platelets,
331        bilirubin_mg_dl: bilirubin,
332        map_mmhg: map,
333        dopamine_dose: 0.0,
334        epinephrine_dose: 0.0,
335        norepinephrine_dose: 0.0,
336        glasgow_coma_scale: gcs,
337        creatinine_mg_dl: creatinine,
338        urine_output_ml_d: 500.0,
339    };
340    let score = sofa_score(&input);
341    println!("SOFA score (Sequential Organ Failure Assessment):");
342    println!("  PaO₂/FiO₂={pao2_fio2}, Platelets={platelets}×10⁹/L");
343    println!("  Bilirubin={bilirubin} mg/dL, MAP={map} mmHg, GCS={gcs}");
344    println!("  Creatinine={creatinine} mg/dL");
345    println!("  SOFA score: {score}/24");
346    let mortality = match score {
347        0..=1 => "<10%",
348        2..=3 => "~10%",
349        4..=5 => "~20%",
350        6..=7 => "~20–30%",
351        8..=9 => "~40%",
352        10..=11 => "~40–50%",
353        12..=14 => ">50%",
354        _ => ">80%",
355    };
356    println!("  Est. mortality: {mortality}");
357}
358
359pub fn run_clinical_ckd(age: u8, sex_male: bool, weight_kg: f64, creatinine: f64) {
360    use qualia_core_db::clinical_engine::{ckd_epi_egfr, cockcroft_gault_crcl, RenalInput};
361    let input = RenalInput {
362        age,
363        sex_male,
364        weight_kg,
365        serum_creatinine: creatinine,
366    };
367    let crcl = cockcroft_gault_crcl(&input);
368    let egfr = ckd_epi_egfr(&input);
369    println!("Renal function:");
370    println!(
371        "  Age={age}, sex={}, weight={weight_kg} kg, Cr={creatinine} mg/dL",
372        if sex_male { "M" } else { "F" }
373    );
374    println!("  CrCl (Cockcroft-Gault) : {crcl:.1} mL/min");
375    println!("  eGFR (CKD-EPI 2021)    : {egfr:.1} mL/min/1.73m²");
376    let stage = match egfr as u32 {
377        90..=u32::MAX => "G1 (normal)",
378        60..=89 => "G2 (mildly decreased)",
379        45..=59 => "G3a (mild-moderate)",
380        30..=44 => "G3b (moderate-severe)",
381        15..=29 => "G4 (severe)",
382        _ => "G5 (kidney failure)",
383    };
384    println!("  CKD stage              : {stage}");
385}
386
387pub fn run_clinical_pk(dose_mg: f64, vd_l: f64, cl_l_hr: f64, time_hr: f64) {
388    use qualia_core_db::clinical_engine::{one_compartment_pk_model, PkOneCompartmentInput};
389    let input = PkOneCompartmentInput {
390        dose_mg,
391        volume_distribution_l: vd_l,
392        clearance_l_hr: cl_l_hr,
393        time_hr,
394    };
395    let r = one_compartment_pk_model(&input);
396    println!("1-compartment PK model (IV bolus):");
397    println!("  Dose={dose_mg} mg, Vd={vd_l} L, CL={cl_l_hr} L/hr");
398    println!("  C(t={time_hr}h) = {:.4} mg/L", r.concentration);
399    println!("  Half-life      = {:.4} hr", r.half_life_hr);
400}
401
402pub fn run_clinical_drug_interactions(drug_names: &str) {
403    use qualia_core_db::clinical_engine::check_drug_interactions;
404    use qualia_core_db::mini_parser::hash_token;
405    let hashes: Vec<u64> = drug_names
406        .split(',')
407        .map(|s| hash_token(s.trim()))
408        .collect();
409    let interactions = check_drug_interactions(&hashes);
410    println!("Drug interaction screening for: {drug_names}");
411    if interactions.is_empty() {
412        println!("  No known interactions found.");
413    } else {
414        for ix in &interactions {
415            println!(
416                "  [{:?}] 0x{:x} ↔ 0x{:x}: {}",
417                ix.severity, ix.drug_a, ix.drug_b, ix.mechanism
418            );
419        }
420    }
421}
422
423// ── Economics ─────────────────────────────────────────────────────────────────
424
425pub fn run_economics_gbm(price: f64, drift: f64, vol: f64, horizon: f64, steps: usize) {
426    use qualia_core_db::domains::financial::economics::simulate_gbm_path;
427    let final_price = simulate_gbm_path(price, drift, vol, horizon, steps);
428    println!("Geometric Brownian Motion path:");
429    println!("  S₀={price}, μ={drift}, σ={vol}, T={horizon}, steps={steps}");
430    println!("  S(T) ≈ {final_price:.4}");
431    let expected = price * (drift * horizon).exp();
432    println!("  E[S(T)] = {expected:.4}  (drift-only estimate)");
433}
434
435pub fn run_economics_var(
436    price: f64,
437    drift: f64,
438    vol: f64,
439    horizon: f64,
440    steps: usize,
441    paths: usize,
442) {
443    use qualia_core_db::domains::financial::economics::run_monte_carlo_var;
444    println!("Monte Carlo VaR (paths={paths}, steps={steps})…");
445    let (mean, var95) = run_monte_carlo_var(price, drift, vol, horizon, steps, paths);
446    println!("  S₀={price}, μ={drift}, σ={vol}, T={horizon}");
447    println!("  Mean end value : {mean:.4}");
448    println!("  95% VaR (loss) : {var95:.4}");
449}
450
451pub fn run_economics_macro(
452    m0: f64,
453    p0: f64,
454    velocity: f64,
455    real_gdp: f64,
456    horizon: f64,
457    steps: usize,
458) {
459    use qualia_core_db::domains::financial::economics::simulate_macroeconomic_flow;
460    let state = simulate_macroeconomic_flow(m0, p0, velocity, real_gdp, horizon, steps);
461    println!("Macroeconomic flow (M×V = P×Q):");
462    println!("  M₀={m0}, P₀={p0}, V={velocity}, Q={real_gdp}, T={horizon}");
463    if state.values.len() >= 2 {
464        println!("  M(T) = {:.4}", state.values[0]);
465        println!("  P(T) = {:.4}", state.values[1]);
466        println!(
467            "  Implied Q = {:.4}",
468            (state.values[0] * velocity) / state.values[1]
469        );
470    }
471}
472
473pub fn run_economics_bond(face: f64, coupon_rate: f64, y: f64, n: u32) {
474    use qualia_core_db::specialized_libs::computational_economics::fixed_income::coupon_bond_price;
475    match coupon_bond_price(face, coupon_rate, y, n as f64, 1) {
476        Ok(p) => {
477            println!("Coupon bond price (face={face}, c={coupon_rate}, y={y}, n={n}): {p:.4}");
478            println!("  (clean price, par=100 equivalent would be face)");
479        }
480        Err(_) => println!("Bond pricing failed (invalid inputs)."),
481    }
482}
483
484pub fn run_economics_paper(qty: f64, last: f64) {
485    use qualia_core_db::specialized_libs::computational_economics::paper_trading::{
486        simulate_fills_against_snapshots, submit_paper_order, Fill, MarketSnapshot, OrderType,
487        PaperOrder, Side,
488    };
489    let mut book: [PaperOrder; 8] = [PaperOrder {
490        id: 0,
491        side: Side::Buy,
492        order_type: OrderType::Market,
493        qty: 0.0,
494        limit_price: 0.0,
495        stop_price: 0.0,
496        filled_qty: 0.0,
497        avg_fill_price: 0.0,
498        status: 0,
499    }; 8];
500    let mut next_id = 1u64;
501    if submit_paper_order(
502        &mut book,
503        &mut next_id,
504        Side::Buy,
505        OrderType::Market,
506        qty,
507        0.0,
508        0.0,
509    )
510    .is_ok()
511    {
512        let snaps = [MarketSnapshot {
513            bid: last - 0.1,
514            ask: last + 0.1,
515            last,
516            volume: 500.0,
517        }];
518        let mut fills: [Fill; 8] = [Fill {
519            order_id: 0,
520            qty: 0.0,
521            price: 0.0,
522            fee: 0.0,
523        }; 8];
524        let _ = simulate_fills_against_snapshots(&mut book, &snaps, 0.001, &mut fills);
525        println!("Paper trade demo: market buy {qty} @~{last}");
526        println!("  (simulation only; no real execution or ledger mutation)");
527    } else {
528        println!("Paper order submit failed.");
529    }
530}
531
532pub fn run_economics_welfare(incomes_str: &str) {
533    use qualia_core_db::specialized_libs::computational_economics::welfare::{
534        atkinson_inequality, gini_coefficient,
535    };
536    let incomes: Vec<f64> = incomes_str
537        .split(',')
538        .filter_map(|s| s.trim().parse().ok())
539        .collect();
540    if incomes.is_empty() {
541        println!("Invalid incomes");
542        return;
543    }
544    if let Ok(g) = gini_coefficient(&incomes) {
545        println!("Gini: {g:.4}");
546    }
547    if let Ok(a) = atkinson_inequality(&incomes, 1.0) {
548        println!("Atkinson (eps=1): {a:.4}");
549    }
550}
551
552pub fn run_economics_game(a: f64) {
553    use qualia_core_db::specialized_libs::computational_economics::game_theory::cournot_duopoly;
554    match cournot_duopoly(a, 1.0, 1.0, 1.0) {
555        Ok((q1, q2, p)) => {
556            // Profits: pi_i = (p - c_i) * q_i, with c1 = c2 = 1.0.
557            let pi1 = (p - 1.0) * q1;
558            let pi2 = (p - 1.0) * q2;
559            println!(
560                "Cournot duopoly (a={a}): q1={q1:.2} q2={q2:.2} p={p:.2} pi1={pi1:.2} pi2={pi2:.2}"
561            );
562        }
563        Err(e) => println!("Cournot duopoly (a={a}): no equilibrium ({e:?})"),
564    }
565}