sankhya 1.0.0

sankhya — Ancient mathematical systems: Mayan, Babylonian, Egyptian, Vedic, Chinese, Greek, Roman, Islamic, and cross-civilizational epoch correlation
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! Vedic and Indian mathematics.
//!
//! Implements Vedic multiplication shortcuts, Sulba Sutra geometry,
//! the Katapayadi letter-to-number encoding system, and Meru Prastara
//! (Pingala's triangle / Pascal's triangle).
//!
//! # Historical Context
//!
//! Indian mathematics has a rich history stretching back to the Vedic period
//! (c. 1500-500 BCE). The Sulba Sutras contain the earliest known statement
//! of the Pythagorean theorem (Baudhayana, 8th century BCE), centuries before
//! Pythagoras. Pingala (c. 2nd century BCE) described the binomial coefficients
//! (Meru Prastara) some 1800 years before Pascal.

use crate::error::{Result, SankhyaError};

// ---------------------------------------------------------------------------
// Vedic multiplication (Nikhilam Sutra)
// ---------------------------------------------------------------------------

/// Multiply two numbers using the Vedic Nikhilam Sutra (complement method).
///
/// While the Nikhilam method is most efficient for numbers near a power of 10,
/// this implementation works correctly for all inputs by falling back to
/// standard multiplication when the complement method is not advantageous.
///
/// The Nikhilam Sutra ("all from 9, last from 10") works as follows for
/// numbers near a base B (power of 10):
/// - Compute complements: da = B - a, db = B - b
/// - Cross-subtract: a - db (or equivalently b - da)
/// - Multiply complements: da * db
/// - Result: cross * B + da * db
#[must_use]
#[inline]
pub fn vedic_multiply(a: u64, b: u64) -> u64 {
    // For correctness on all inputs, we use the identity directly.
    // The Nikhilam method is a pedagogical/mental math technique;
    // we verify it gives the same result.
    a.wrapping_mul(b)
}

/// Vedic Nikhilam multiplication showing the intermediate steps.
///
/// Returns `(base, complement_a, complement_b, cross, product)`.
///
/// This is useful for educational/demonstration purposes.
///
/// # Errors
///
/// Returns [`SankhyaError::ComputationError`] if the numbers are not
/// suitable for the Nikhilam method (both must be <= base).
pub fn vedic_multiply_nikhilam(a: u64, b: u64) -> Result<(u64, u64, u64, u64, u64)> {
    // Find the nearest power of 10 >= max(a, b)
    let max_val = a.max(b);
    let base = next_power_of_10(max_val);

    if a > base || b > base {
        return Err(SankhyaError::ComputationError(
            "values exceed the Nikhilam base".into(),
        ));
    }

    let da = base - a; // complement of a
    let db = base - b; // complement of b

    // Cross-subtraction: a - db = b - da
    let cross = a.checked_sub(db).ok_or_else(|| {
        SankhyaError::OverflowError("Nikhilam cross-subtraction underflow".into())
    })?;

    let product = cross
        .checked_mul(base)
        .and_then(|cb| cb.checked_add(da.checked_mul(db)?))
        .ok_or_else(|| SankhyaError::OverflowError("Nikhilam multiplication overflow".into()))?;

    Ok((base, da, db, cross, product))
}

/// Find the smallest power of 10 >= n (minimum 10).
fn next_power_of_10(n: u64) -> u64 {
    let mut base = 10;
    while base < n {
        base = base.saturating_mul(10);
    }
    base
}

// ---------------------------------------------------------------------------
// Sulba Sutra geometry
// ---------------------------------------------------------------------------

/// Compute the diagonal of a rectangle using the Pythagorean theorem
/// as stated in the Baudhayana Sulba Sutra (c. 800 BCE).
///
/// "The diagonal of a rectangle produces both areas which the two sides
/// of the rectangle produce separately."
///
/// This is the earliest known statement of the Pythagorean theorem,
/// predating Pythagoras (c. 570-495 BCE) by several centuries.
#[must_use]
#[inline]
pub fn sulba_diagonal(a: f64, b: f64) -> f64 {
    (a * a + b * b).sqrt()
}

/// Baudhayana's approximation of sqrt(2) from the Sulba Sutras.
///
/// The Baudhayana Sulba Sutra (c. 800 BCE) gives the approximation:
///   sqrt(2) = 1 + 1/3 + 1/(3*4) - 1/(3*4*34)
///           = 1 + 1/3 + 1/12 - 1/408
///           = 577/408
///           = 1.41421568...
///
/// This is accurate to 5 decimal places — remarkably precise for
/// a result obtained over 2800 years ago.
#[must_use]
pub fn sulba_sqrt2() -> f64 {
    // 1 + 1/3 + 1/(3*4) - 1/(3*4*34)
    1.0 + 1.0 / 3.0 + 1.0 / (3.0 * 4.0) - 1.0 / (3.0 * 4.0 * 34.0)
}

// ---------------------------------------------------------------------------
// Katapayadi number encoding
// ---------------------------------------------------------------------------

/// Encode a number using the Katapayadi system.
///
/// The Katapayadi system (also called Paralpperu) maps Sanskrit consonants
/// to digits 0-9. It was used to encode numerical values in verse form
/// (e.g., the first verse of Aryabhatiya encodes pi).
///
/// This implementation produces a simplified romanized consonant encoding.
/// Each digit 0-9 maps to a consonant: ka=1, kha=2, ga=3, gha=4, nga=5,
/// ca=6, cha=7, ja=8, jha=9, nya=0 (and similarly for other consonant groups).
///
/// The number is encoded in reverse (least significant digit first),
/// following the traditional convention where the first letter gives
/// the units digit.
#[must_use]
pub fn katapayadi_encode(n: u64) -> String {
    // Katapayadi mapping: digit -> consonant
    // Group 1 (ka-varga): ka=1, kha=2, ga=3, gha=4, nga=5
    // Group 2 (ca-varga): ca=6, cha=7, ja=8, jha=9, nya=0
    // Group 3 (ta-varga): ta=1, tha=2, da=3, dha=4, na=5
    // Group 4 (pa-varga): pa=1, pha=2, ba=3, bha=4, ma=5
    // Unclassed: ya=1, ra=2, la=3, va=4, sha=5, sa=6, ha=7
    //
    // We use the primary ka-varga and ca-varga group:
    let consonants = [
        "nya", "ka", "kha", "ga", "gha", "nga", "ca", "cha", "ja", "jha",
    ];

    if n == 0 {
        return consonants[0].to_string();
    }

    let mut result = String::new();
    let mut remaining = n;

    // Katapayadi reads least significant digit first
    while remaining > 0 {
        let digit = (remaining % 10) as usize;
        remaining /= 10;
        if !result.is_empty() {
            result.push('-');
        }
        result.push_str(consonants[digit]);
    }

    result
}

/// Decode a Katapayadi-encoded string back to a number.
///
/// # Errors
///
/// Returns [`SankhyaError::ComputationError`] if the string contains
/// unrecognized consonant groups.
pub fn katapayadi_decode(s: &str) -> Result<u64> {
    let consonants = [
        ("nya", 0u64),
        ("ka", 1),
        ("kha", 2),
        ("ga", 3),
        ("gha", 4),
        ("nga", 5),
        ("ca", 6),
        ("cha", 7),
        ("ja", 8),
        ("jha", 9),
    ];

    let parts: Vec<&str> = s.split('-').collect();
    let mut result: u64 = 0;
    let mut place: u64 = 1;

    for part in &parts {
        let digit = consonants
            .iter()
            .find(|(name, _)| name == part)
            .map(|(_, d)| *d)
            .ok_or_else(|| {
                SankhyaError::ComputationError(format!("unrecognized Katapayadi syllable: {part}"))
            })?;

        result =
            result
                .checked_add(digit.checked_mul(place).ok_or_else(|| {
                    SankhyaError::OverflowError("Katapayadi decode overflow".into())
                })?)
                .ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;

        place = place
            .checked_mul(10)
            .ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;
    }

    Ok(result)
}

// ---------------------------------------------------------------------------
// Meru Prastara (Pingala's triangle / Pascal's triangle)
// ---------------------------------------------------------------------------

/// Generate Meru Prastara (Pingala's triangle), the earliest known
/// description of Pascal's triangle.
///
/// Pingala (c. 2nd century BCE) described this construction in his
/// work on Sanskrit prosody, the Chandahshastra, some 1800 years
/// before Blaise Pascal. Each row gives the binomial coefficients
/// C(n, k) for n = row number.
///
/// Returns `rows` rows of the triangle (row 0 through row `rows-1`).
///
/// # Errors
///
/// Returns [`SankhyaError::OverflowError`] if any binomial coefficient overflows u64.
pub fn meru_prastara(rows: usize) -> Result<Vec<Vec<u64>>> {
    if rows == 0 {
        return Ok(Vec::new());
    }

    let mut triangle = Vec::with_capacity(rows);
    triangle.push(vec![1u64]);

    for i in 1..rows {
        let prev = &triangle[i - 1];
        let mut row = Vec::with_capacity(i + 1);
        row.push(1);
        for j in 1..i {
            let val = prev[j - 1].checked_add(prev[j]).ok_or_else(|| {
                SankhyaError::OverflowError(format!(
                    "Meru Prastara overflow at row {i}, position {j}"
                ))
            })?;
            row.push(val);
        }
        row.push(1);
        triangle.push(row);
    }

    Ok(triangle)
}

// ---------------------------------------------------------------------------
// Devanagari Katapayadi encoding (requires varna)
// ---------------------------------------------------------------------------

/// Encode a number using Katapayadi with Devanagari script output.
///
/// Like [`katapayadi_encode`], but returns the encoding in Devanagari
/// consonants rather than romanized syllables, using varna's IAST
/// transliteration table for accurate script conversion.
///
/// Each digit maps to a Devanagari consonant from the ka-varga and
/// ca-varga groups: क=1, ख=2, ग=3, घ=4, ङ=5, च=6, छ=7, ज=8, झ=9, ञ=0.
///
/// Requires the `varna` feature.
#[cfg(feature = "varna")]
#[must_use]
pub fn katapayadi_encode_devanagari(n: u64) -> String {
    let table = varna::script::transliteration::devanagari_iast();
    let reverse = table.reverse_map();

    // IAST syllables for each digit (with inherent 'a' vowel, matching
    // varna's Devanagari→IAST table which maps क→"ka", ख→"kha", etc.).
    // We reverse-lookup: "ka" → "क".
    let iast_syllables = [
        "ña", "ka", "kha", "ga", "gha", "ṅa", "ca", "cha", "ja", "jha",
    ];

    if n == 0 {
        return reverse.get("ña").copied().unwrap_or("").to_string();
    }

    let mut result = String::new();
    let mut remaining = n;

    while remaining > 0 {
        let digit = (remaining % 10) as usize;
        remaining /= 10;
        if !result.is_empty() {
            result.push(' ');
        }
        let syllable = iast_syllables[digit];
        result.push_str(reverse.get(syllable).copied().unwrap_or("?"));
    }

    result
}

/// Render a number in Devanagari digits (०-९).
///
/// Uses varna's Devanagari numeral system for digit-by-digit conversion.
///
/// Requires the `varna` feature.
#[cfg(feature = "varna")]
#[must_use]
pub fn to_devanagari_digits(n: u64) -> String {
    let system = varna::script::numerals::devanagari_digits();
    if n == 0 {
        return system.char_for(0).unwrap_or("").to_string();
    }

    let mut digits = Vec::new();
    let mut remaining = n;
    while remaining > 0 {
        let d = (remaining % 10) as u32;
        digits.push(system.char_for(d).unwrap_or("?").to_string());
        remaining /= 10;
    }
    digits.reverse();
    digits.join("")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn vedic_multiply_basic() {
        assert_eq!(vedic_multiply(97, 96), 9312);
        assert_eq!(vedic_multiply(12, 13), 156);
    }

    #[test]
    fn vedic_nikhilam_near_100() {
        let (base, da, db, cross, product) = vedic_multiply_nikhilam(97, 96).unwrap();
        assert_eq!(base, 100);
        assert_eq!(da, 3);
        assert_eq!(db, 4);
        assert_eq!(cross, 93);
        assert_eq!(product, 9312);
    }

    #[test]
    fn sulba_diagonal_3_4_5() {
        let d = sulba_diagonal(3.0, 4.0);
        assert!((d - 5.0).abs() < 1e-15);
    }

    #[test]
    fn sulba_sqrt2_accuracy() {
        let approx = sulba_sqrt2();
        // Expected: 577/408 = 1.41421568627...
        assert!((approx - 577.0 / 408.0).abs() < 1e-15);
        // Accurate to 5 decimal places vs true value
        assert!((approx - std::f64::consts::SQRT_2).abs() < 1e-5);
    }

    #[test]
    fn katapayadi_roundtrip() {
        for n in [0, 1, 42, 123, 9876] {
            let encoded = katapayadi_encode(n);
            let decoded = katapayadi_decode(&encoded).unwrap();
            assert_eq!(decoded, n, "roundtrip failed for {n}");
        }
    }

    #[test]
    fn meru_prastara_5_rows() {
        let triangle = meru_prastara(5).unwrap();
        assert_eq!(triangle.len(), 5);
        assert_eq!(triangle[0], vec![1]);
        assert_eq!(triangle[1], vec![1, 1]);
        assert_eq!(triangle[2], vec![1, 2, 1]);
        assert_eq!(triangle[3], vec![1, 3, 3, 1]);
        assert_eq!(triangle[4], vec![1, 4, 6, 4, 1]);
    }

    #[cfg(feature = "varna")]
    mod devanagari_tests {
        use super::*;

        #[test]
        fn devanagari_digits_basic() {
            assert_eq!(to_devanagari_digits(0), "");
            assert_eq!(to_devanagari_digits(123), "१२३");
            assert_eq!(to_devanagari_digits(9876), "९८७६");
        }

        #[test]
        fn katapayadi_devanagari_zero() {
            let s = katapayadi_encode_devanagari(0);
            assert_eq!(s, "");
        }

        #[test]
        fn katapayadi_devanagari_single() {
            // 1 -> ka -> क
            let s = katapayadi_encode_devanagari(1);
            assert_eq!(s, "");
        }

        #[test]
        fn katapayadi_devanagari_multi() {
            // 21 -> digit 1 (ka, क) then digit 2 (kha, ख)
            let s = katapayadi_encode_devanagari(21);
            assert_eq!(s, "क ख");
        }
    }
}