├── todo ├── fol.ts ├── go.ts ├── sprouts.ts ├── sudoku.ts ├── complex-number.ts ├── clifford-algebra.ts ├── grassmann-algebra.ts ├── simplicial-complex.ts ├── quaternion.ts ├── euclidean-space-class.ts └── number.ts ├── AUTHORS ├── docs ├── .gitignore ├── README.md └── note │ ├── linear-algebra.org │ ├── mathematical-structures.org │ └── cicada-note.org ├── web ├── index.js ├── .gitignore ├── utility.js ├── ng.ts ├── index.html └── mian-zhi.ts ├── .travis.yml ├── .gitignore ├── examples ├── todo │ └── pd.js ├── difference-matrix.js ├── num-linear-algebra.js ├── int-module.js └── four-ways-to-glue-a-square.js ├── tests ├── README.md ├── games │ ├── tic-tac-toe.test.js │ └── hackenbush.test.js ├── permutation.test.js ├── homology.test.js ├── num.test.js ├── number.test.js ├── panel-data.test.js ├── ndarray.test.js └── int.test.js ├── src ├── graph.ts ├── games │ ├── bots │ │ └── rand.ts │ ├── hackenbush.ts │ └── tic-tac-toe.ts ├── homotopy.ts ├── abstract │ ├── affine-space.ts │ ├── set.ts │ ├── module.ts │ ├── group.ts │ ├── category.ts │ └── ring.ts ├── polytope.ts ├── cse.ts ├── complexes │ └── four-ways-to-glue-a-square.ts ├── number.ts ├── combinatorial-game.ts ├── homology.ts ├── permutation.ts ├── dic.ts ├── graph-info.ts ├── int.ts ├── num.ts ├── nbe │ └── simple.ts ├── panel-data.ts └── ndarray.ts ├── tsconfig.json ├── benchmarks ├── matrix.py ├── num.matrix.js └── matrix-naive.js ├── STYLE-GUIDE.md ├── package.json ├── CODE-OF-CONDUCT.md ├── todo.org └── README.md /todo/fol.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/go.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Xie Yuheng -------------------------------------------------------------------------------- /todo/sprouts.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/sudoku.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/complex-number.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/clifford-algebra.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/grassmann-algebra.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /todo/simplicial-complex.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # typedoc 2 | api 3 | 4 | # gitbook 5 | _book -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | import { init } from "./mian-zhi" 2 | 3 | init () 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 13 4 | script: 5 | - npm install 6 | - npm run build 7 | - npm run test 8 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # js 2 | dist 3 | node_modules 4 | 5 | # parcel 6 | .cache 7 | 8 | # emacs 9 | *~ 10 | *#* 11 | .#* 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # js 2 | dist 3 | lib 4 | node_modules 5 | 6 | # parcel 7 | .cache 8 | 9 | # emacs 10 | *~ 11 | *#* 12 | .#* 13 | -------------------------------------------------------------------------------- /examples/todo/pd.js: -------------------------------------------------------------------------------- 1 | let assert = require ("assert") 2 | 3 | let pd = require ("../lib/panel-data") 4 | let ut = require ("../lib/util") 5 | -------------------------------------------------------------------------------- /todo/quaternion.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * `quaternion_t` is a concrete example of division ring. 3 | */ 4 | export 5 | class quaternion_t { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | 3 | - The time required to run all tests must be controlled under 15 seconds. 4 | - Tests should be written in javascript isntead of typescript, for it is too slow to run tests in typescript. 5 | -------------------------------------------------------------------------------- /web/utility.js: -------------------------------------------------------------------------------- 1 | window.addEventListener ("visibilitychange", () => { 2 | if (document.hidden) console.log ("hidden") 3 | else console.log ("focused") 4 | }) 5 | 6 | console.log (navigator.onLine ? "online" : "offline") 7 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## About API docs generated by typedoc 2 | 3 | - use `--ignoreCompilerErrors` for typedoc, 4 | because typedoc does not use the latest version of typescript 5 | : https://github.com/TypeStrong/typedoc/issues/880 6 | -------------------------------------------------------------------------------- /web/ng.ts: -------------------------------------------------------------------------------- 1 | export 2 | abstract class engine_t { 3 | constructor ( 4 | public canvas: canvas_t, 5 | public state: state_t, 6 | public event_queue: Array = [], 7 | ) {} 8 | 9 | abstract receive (): void 10 | abstract rander (): void 11 | } 12 | -------------------------------------------------------------------------------- /src/graph.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import { dic_t } from "./dic" 4 | 5 | export 6 | type id_t = number 7 | 8 | export 9 | class edge_t { 10 | constructor ( 11 | public start: id_t, 12 | public end: id_t, 13 | ) {} 14 | } 15 | 16 | export 17 | class graph_t { 18 | constructor ( 19 | public vertex_count: number, 20 | public edge_dic: dic_t , 21 | ) {} 22 | } 23 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | cg 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | // "module": "umd", 5 | "module": "commonjs", 6 | // "moduleResolution": "node", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "esModuleInterop": true, 10 | "strict": true, 11 | "outDir": "lib", 12 | "baseUrl": ".", 13 | "paths": { 14 | "*": [ 15 | "node_modules/*", 16 | // "src/types/*", 17 | ] 18 | } 19 | }, 20 | "include": [ 21 | "src", 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /src/games/bots/rand.ts: -------------------------------------------------------------------------------- 1 | import * as cg from "../../combinatorial-game" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | 5 | export 6 | class random_bot_t { 7 | constructor ( 8 | public game: cg.game_t , 9 | ) {} 10 | 11 | next_choice ( 12 | p: P, 13 | s: S, 14 | ): C { 15 | let choices = this.game.choices (p, s) 16 | if (choices.length !== 0) { 17 | return ut.rand_member (choices) 18 | } else { 19 | throw new Error ("random_bot_t.next_choice fail") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/note/linear-algebra.org: -------------------------------------------------------------------------------- 1 | * row vs. col 2 | - `row_trans` 3 | - If we view matrix as linear equations, 4 | `row_trans` does not change solution. 5 | - If we view matrix as linear map, 6 | `row_trans` does not change kernel of the map. 7 | - but `row_trans` can change the col_space 8 | - `col_trans` 9 | - If we view matrix as col of bases, 10 | `col_trans` does not change the spanned space of the bases. 11 | - If we view matrix as linear map, 12 | `col_trans` does not change the image of the map. 13 | - `col_trans` can not change the col_space 14 | -------------------------------------------------------------------------------- /benchmarks/matrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import time as time 4 | 5 | f0 = np.zeros ((512, 512)) .transpose () 6 | f1 = np.zeros ((512, 512)) .transpose () 7 | c0 = np.zeros ((512, 512)) 8 | c1 = np.zeros ((512, 512)) 9 | 10 | ones = np.ones ((512, 512)) 11 | 12 | def test (a, b): 13 | a [:] = a + (b*0.5) + ones 14 | 15 | def bench (a, b, n): 16 | start = time.time () 17 | for _ in range (0, n): 18 | test (a, b) 19 | end = time.time () 20 | print (end - start) 21 | 22 | bench (c0, c1, 1000) 23 | bench (f0, f1, 1000) 24 | bench (c0, f1, 1000) 25 | bench (f0, c1, 1000) 26 | -------------------------------------------------------------------------------- /examples/difference-matrix.js: -------------------------------------------------------------------------------- 1 | let assert = require ("assert") .strict 2 | 3 | let ut = require ("../lib/util") 4 | let num = require ("../lib/num") 5 | 6 | { 7 | let A = num.matrix ([ 8 | [-1, 1, 0, 0], 9 | [0, -1, 1, 0], 10 | [0, 0, -1, 1], 11 | ]) 12 | 13 | A.mul (A.transpose ()) .print () 14 | } 15 | 16 | { 17 | let A = num.matrix ([ 18 | [0, 1, 0, 0], 19 | [0, -1, 1, 0], 20 | [0, 0, -1, 1], 21 | ]) 22 | 23 | A.mul (A.transpose ()) .print () 24 | } 25 | 26 | { 27 | let A = num.matrix ([ 28 | [0, 1, 0, 0], 29 | [0, -1, 1, 0], 30 | [0, 0, -1, 0], 31 | ]) 32 | 33 | A.mul (A.transpose ()) .print () 34 | } 35 | -------------------------------------------------------------------------------- /examples/num-linear-algebra.js: -------------------------------------------------------------------------------- 1 | let assert = require ("assert") .strict 2 | 3 | let ut = require ("../lib/util") 4 | let num = require ("../lib/num") 5 | 6 | { 7 | /** 8 | * `reduced_row_echelon_form` reduces pivots to one 9 | * while respecting `epsilon` for numerical stability 10 | */ 11 | 12 | let A = num.matrix ([ 13 | [1, 3, 1, 9], 14 | [1, 1, -1, 1], 15 | [3, 11, 5, 35], 16 | ]) 17 | 18 | let B = num.matrix ([ 19 | [1, 0, -2, -3], 20 | [0, 1, 1, 4], 21 | [0, 0, 0, 0], 22 | ]) 23 | 24 | A.reduced_row_echelon_form () .print () 25 | 26 | assert ( 27 | A.reduced_row_echelon_form () .eq (B) 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /benchmarks/num.matrix.js: -------------------------------------------------------------------------------- 1 | let num = require ("../lib/num") 2 | 3 | let f0 = num.matrix_t.zeros (512, 512) .transpose () 4 | let f1 = num.matrix_t.zeros (512, 512) .transpose () 5 | let c0 = num.matrix_t.zeros (512, 512) 6 | let c1 = num.matrix_t.zeros (512, 512) 7 | 8 | let ones = num.matrix_t.ones (512, 512) 9 | 10 | function test (a, b) { 11 | a.update_add (b.update_scale (0.5)) .update_add (ones) 12 | } 13 | 14 | function bench (a, b, n) { 15 | let start = new Date () 16 | for (let i = 0; i < n; i++) { 17 | test (a, b) 18 | } 19 | let end = new Date () 20 | console.log (`${end - start} ms, for ${n} times`) 21 | } 22 | 23 | // time should under 1000 when n is 1000 24 | bench (c0, c1, 100) 25 | bench (f0, f1, 100) 26 | bench (c0, f1, 100) 27 | bench (f0, c1, 100) 28 | -------------------------------------------------------------------------------- /benchmarks/matrix-naive.js: -------------------------------------------------------------------------------- 1 | let eu = require ("../lib/euclid-naive") 2 | 3 | let f0 = eu.matrix_t.zeros (512, 512) .transpose () 4 | let f1 = eu.matrix_t.zeros (512, 512) .transpose () 5 | let c0 = eu.matrix_t.zeros (512, 512) 6 | let c1 = eu.matrix_t.zeros (512, 512) 7 | 8 | let ones = eu.matrix_t.ones (512, 512) 9 | 10 | function test (a, b) { 11 | a.update_add (b.update_scale (0.5)) .update_add (ones) 12 | } 13 | 14 | function bench (a, b, n) { 15 | let start = new Date () 16 | for (let i = 0; i < n; i++) { 17 | test (a, b) 18 | } 19 | let end = new Date () 20 | console.log (`${end - start} ms, for ${n} times`) 21 | } 22 | 23 | // time should under 1000 when n is 1000 24 | bench (c0, c1, 100) 25 | bench (f0, f1, 100) 26 | bench (c0, f1, 100) 27 | bench (f0, c1, 100) 28 | -------------------------------------------------------------------------------- /tests/games/tic-tac-toe.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as tic_tac_toe from "../../lib/games/tic-tac-toe" 4 | 5 | test ("tic_tac_toe", t => { 6 | let play = tic_tac_toe.new_play () 7 | 8 | play.move ("O", [1, 1]) 9 | play.move ("X", [1, 2]) 10 | play.move ("O", [0, 1]) 11 | play.move ("X", [0, 2]) 12 | play.move ("O", [2, 1]) 13 | 14 | t.pass () 15 | }) 16 | 17 | // test ("random_bot", t => { 18 | // let play = tic_tac_toe.new_play () 19 | 20 | // let bot = tic_tac_toe.random_bot 21 | 22 | // while (play.winner () === null) { 23 | // if (play.draw_p ()) { 24 | // return 25 | // } 26 | // let p = play.next_player () 27 | // let s = play.last_state () 28 | // let ch = bot.next_choice (p, s) 29 | // play.move (p, ch) 30 | // } 31 | 32 | // t.pass () 33 | // }) 34 | -------------------------------------------------------------------------------- /src/homotopy.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import { dic_t } from "./dic" 4 | import * as cx from "./cell-complex" 5 | 6 | export 7 | class path_t extends cx.cell_complex_t { 8 | 9 | } 10 | 11 | export 12 | class solid_polygon_t extends cx.cell_complex_t { 13 | 14 | } 15 | 16 | export 17 | class glob_t extends cx.morphism_t { 18 | 19 | } 20 | 21 | export 22 | class globular_t extends cx.cell_complex_t { 23 | 24 | } 25 | 26 | /** 27 | * `glob_t` is the basic object of higher algebra. 28 | * But, to get homological chain after abelianization, 29 | * we need array of glob, instead of one glob. 30 | */ 31 | export 32 | class chain_t { 33 | dim: number 34 | com: cx.cell_complex_t 35 | glob_array: Array 36 | 37 | constructor ( 38 | dim: number, 39 | com: cx.cell_complex_t, 40 | ) { 41 | this.dim = dim 42 | this.com = com 43 | this.glob_array = new Array () 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/note/mathematical-structures.org: -------------------------------------------------------------------------------- 1 | * Mathematical structures is about code reuse 2 | 3 | - To factor out the sameness of `row_canonical_form` between `row_hermite_normal_form`, 4 | we need to use matrix over euclidean ring 5 | 6 | - This is the main use of abstract mathematical structures. 7 | it solves the main problem of compute science -- code reuse. 8 | 9 | But currently no programming language allow us 10 | to formalize abstract mathematical structures intuitively. 11 | 12 | * Ironic 13 | 14 | - We have to re-implement `row_echelon_form` again for `num.matrix_t` 15 | because `float` is not precisely a ring. 16 | 17 | - We also have to re-implement the core algorithm again for the ring of polynomial, 18 | because there exist more efficient algorithm than the generic algorithm. 19 | 20 | - It is ironic that, after the abstraction, 21 | to reach useful API, we still have to re-implement the core algorithm. 22 | -------------------------------------------------------------------------------- /tests/games/hackenbush.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as hackenbush from "../../lib/games/hackenbush" 4 | 5 | test ("hackenbush", t => { 6 | let bush = new hackenbush.state_t () 7 | .blue (0, 1) 8 | .blue (0, 1) 9 | .blue (1, 2) 10 | .red (0, 1) 11 | .red (0, 1) 12 | .green (0, 1) 13 | 14 | let play = hackenbush.new_play (bush) 15 | 16 | play.move ("blue", 0) 17 | play.move ("red", 3) 18 | play.move ("blue", 1) 19 | play.move ("red", 4) 20 | 21 | t.pass () 22 | }) 23 | 24 | test ("random_bot", t => { 25 | let bush = new hackenbush.state_t () 26 | .blue (0, 1) 27 | .blue (0, 1) 28 | .blue (1, 2) 29 | .red (0, 1) 30 | .red (0, 1) 31 | .green (0, 1) 32 | 33 | let play = hackenbush.new_play (bush) 34 | 35 | let bot = hackenbush.random_bot 36 | 37 | while (play.winner () === null) { 38 | let p = play.next_player () 39 | let s = play.last_state () 40 | let ch = bot.next_choice (p, s) 41 | play.move (p, ch) 42 | } 43 | 44 | t.pass () 45 | }) 46 | -------------------------------------------------------------------------------- /tests/permutation.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import { 4 | permutation_t, 5 | } from "../lib/permutation" 6 | 7 | import * as ut from "@cicadoidea/basic/lib/util" 8 | 9 | test ("new", t => { 10 | let x = new permutation_t ([0, 2, 3, 1]) 11 | t.pass () 12 | }) 13 | 14 | 15 | test ("rev", t => { 16 | let x = new permutation_t ([0, 2, 3, 1]) 17 | t.true ( 18 | x.inv () .inv () .eq (x) 19 | ) 20 | }) 21 | 22 | test ("mul", t => { 23 | let x = new permutation_t ([0, 2, 3, 1]) 24 | t.true ( 25 | x.mul (x.inv ()) 26 | .eq (permutation_t.id (x.sequence.length)) 27 | ) 28 | }) 29 | 30 | test ("order", t => { 31 | let x = new permutation_t ([0, 2, 3, 1]) 32 | let y = new permutation_t ([0, 2, 1, 3]) 33 | 34 | t.true ( 35 | x.gt (y) 36 | ) 37 | 38 | t.true ( 39 | x.gte (y) 40 | ) 41 | 42 | t.true ( 43 | x.gte (x) 44 | ) 45 | }) 46 | 47 | test ("tuck", t => { 48 | let x = permutation_t.id (4) 49 | 50 | t.true ( 51 | x.tuck (0, 2) .eq ( 52 | new permutation_t ([1, 2, 0, 3]) 53 | ) 54 | ) 55 | }) 56 | -------------------------------------------------------------------------------- /STYLE-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Style Guide 2 | 3 | ## General rules 4 | 5 | - Maximal line length is limited to 66 characters. 6 | 7 | ## Easy to read Function calls 8 | 9 | - Function calls and similar expressions 10 | are separated from the expression. 11 | 12 | ## `var`, `let` & `const` 13 | 14 | - It is not allowed to use `var`. 15 | - It is ok to use both `let` & `const`. 16 | - It is ok to use `let`, even when one does not intend to change the variable. 17 | - I did this in examples to make them looks easier for new comers. 18 | 19 | ## API design 20 | 21 | - We choose well typed API over "easy to implement" 22 | 23 | - "hard to implement" maybe due to the limitation of the language, 24 | specially the limitation of the type system of the language. 25 | 26 | In language with dependent type, for example, 27 | we can use `matrix_t (m, n)` for general matrix, 28 | `matrix_t (1, n)` for row vector, 29 | `matrix_t (m, 1)` for column vector, 30 | this can be done while keep the implementation simple. 31 | 32 | But in language without dependent type, 33 | to keep API well typed, 34 | `matrix_t` and `vector_t` should be different types, 35 | and a lots of methods must be repeated, 36 | thus the implementation is not easy. 37 | -------------------------------------------------------------------------------- /src/abstract/affine-space.ts: -------------------------------------------------------------------------------- 1 | import { set_t, eqv } from "./set" 2 | import { abelian_group_t } from "./group" 3 | import { field_t } from "./ring" 4 | import { vector_space_t } from "./module" 5 | 6 | export 7 | class affine_space_t { 8 | points: set_t

9 | vectors: vector_space_t 10 | trans: (p: P, v: V) => P 11 | diff: (p: P, q: P) => V 12 | 13 | constructor (the: { 14 | points: set_t

, 15 | vectors: vector_space_t , 16 | trans: (p: P, v: V) => P, 17 | diff: (p: P, q: P) => V, 18 | }) { 19 | this.points = the.points 20 | this.vectors = the.vectors 21 | this.trans = the.trans 22 | this.diff = the.diff 23 | } 24 | 25 | eq = this.vectors.eq 26 | id = this.vectors.id 27 | add = this.vectors.add 28 | neg = this.vectors.neg 29 | scale = this.vectors.scale 30 | 31 | vector_id_action (p: P) { 32 | eqv ( 33 | this.points, 34 | this.trans (p, this.id), 35 | p) 36 | } 37 | 38 | vector_action (p: P, v: V, w: V) { 39 | eqv ( 40 | this.points, 41 | this.trans (this.trans (p, v), w), 42 | this.trans (p, this.add (v, w))) 43 | } 44 | 45 | weyl_s_axiom (a: P, b: P, c: P) { 46 | eqv ( 47 | this.vectors, 48 | this.add (this.diff (c, b), this.diff (b, a)), 49 | this.diff (c, a)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/polytope.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as num from "./num" 5 | 6 | export 7 | class convex_hull_t { 8 | matrix: num.matrix_t 9 | 10 | constructor ( 11 | matrix: num.matrix_t, 12 | ) { 13 | this.matrix = matrix 14 | } 15 | 16 | // homogenize (): conical_hull_t { 17 | // TODO 18 | // } 19 | 20 | // TODO 21 | // to_intersection (): intersection_t 22 | } 23 | 24 | export 25 | class conical_hull_t { 26 | matrix: num.matrix_t 27 | 28 | constructor ( 29 | matrix: num.matrix_t, 30 | ) { 31 | this.matrix = matrix 32 | } 33 | 34 | // TODO 35 | // to_linear_intersection (): linear_intersection_t 36 | } 37 | 38 | export 39 | class intersection_t { 40 | argumented: num.matrix_t 41 | 42 | constructor ( 43 | argumented: num.matrix_t, 44 | ) { 45 | this.argumented = argumented 46 | } 47 | 48 | // homogenize (): linear_intersection_t { 49 | // TODO 50 | // } 51 | 52 | // TODO 53 | // to_convex_conical_hull (): [convex_hull_t, conical_hull_t] {} 54 | 55 | // TODO 56 | // proj (): intersection_t {} 57 | } 58 | 59 | // TODO 60 | class linear_intersection_t { 61 | argumented: num.matrix_t 62 | 63 | constructor ( 64 | argumented: num.matrix_t, 65 | ) { 66 | this.argumented = argumented 67 | } 68 | 69 | // TODO 70 | // to_conical_hull (): conical_hull_t {} 71 | 72 | // TODO 73 | // proj (): linear_intersection_t {} 74 | } 75 | 76 | // TODO 77 | // class polytope_t {} 78 | -------------------------------------------------------------------------------- /examples/int-module.js: -------------------------------------------------------------------------------- 1 | let assert = require ("assert") .strict 2 | 3 | let ut = require ("../lib/util") 4 | let int = require ("../lib/int") 5 | 6 | { 7 | /** 8 | * generic `row_canonical_form` 9 | * i.e. `hermite_normal_form` for integers 10 | */ 11 | 12 | let A = int.matrix ([ 13 | [2, 3, 6, 2], 14 | [5, 6, 1, 6], 15 | [8, 3, 1, 1], 16 | ]) 17 | 18 | let B = int.matrix ([ 19 | [1, 0, -11, 2], 20 | [0, 3, 28, -2], 21 | [0, 0, 61, -13], 22 | ]) 23 | 24 | assert ( 25 | A.row_canonical_form () .eq (B) 26 | ) 27 | } 28 | 29 | { 30 | /** 31 | * generic `diag_canonical_form` 32 | * i.e. `smith_normal_form` for integers 33 | */ 34 | 35 | let A = int.matrix ([ 36 | [2, 4, 4], 37 | [-6, 6, 12], 38 | [10, -4, -16], 39 | ]) 40 | 41 | let S = int.matrix ([ 42 | [2, 0, 0], 43 | [0, 6, 0], 44 | [0, 0, -12], 45 | ]) 46 | 47 | assert ( 48 | A.diag_canonical_form () .eq (S) 49 | ) 50 | } 51 | 52 | { 53 | /** 54 | * solve linear diophantine equations 55 | */ 56 | 57 | let A = int.matrix ([ 58 | [1, 2, 3, 4, 5, 6, 7], 59 | [1, 0, 1, 0, 1, 0, 1], 60 | [2, 4, 5, 6, 1, 1, 1], 61 | [1, 4, 2, 5, 2, 0, 0], 62 | [0, 0, 1, 1, 2, 2, 3], 63 | ]) 64 | 65 | let b = int.vector ([ 66 | 28, 67 | 4, 68 | 20, 69 | 14, 70 | 9, 71 | ]) 72 | 73 | let solution = A.solve (b) 74 | 75 | if (solution !== null) { 76 | solution.print () 77 | 78 | assert ( 79 | A.act (solution) .eq (b) 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cell-complex", 3 | "version": "0.0.46", 4 | "author": "Xie Yuheng", 5 | "license": "GPL-3.0-or-later", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:xieyuheng/cell-complex.git" 9 | }, 10 | "files": [ 11 | "src", 12 | "lib" 13 | ], 14 | "scripts": { 15 | "build": "tsc", 16 | "watch": "tsc --watch", 17 | "test": "ava 2> /dev/null", 18 | "web-build": "parcel build web/index.html -d web/dist", 19 | "web-watch": "parcel web/index.html -d web/dist", 20 | "web-up": "surge web/dist cell-complex.surge.sh", 21 | "web": "npm run web-build; npm run web-up", 22 | "api-build": "typedoc src --out docs/api --mode modules --ignoreCompilerErrors", 23 | "api-surge": "surge docs/api api.cell-complex.surge.sh", 24 | "api-now": "now switch xieyuheng; now -n cell-complex deploy docs/api --target production", 25 | "api": "npm run api-build; npm run api-surge; npm run api-now", 26 | "up": "git commit -m 'up'; npm run build; npm run api; npm version patch; git push; npm publish" 27 | }, 28 | "devDependencies": { 29 | "@types/assert": "^1.4.6", 30 | "@types/lodash": "^4.14.149", 31 | "@types/nanoid": "^2.1.0", 32 | "@babel/core": "^7.9.0", 33 | "@babel/plugin-transform-runtime": "^7.9.0", 34 | "ava": "^3.5.1", 35 | "parcel-bundler": "^1.12.4", 36 | "typedoc": "^0.17.3", 37 | "typescript": "^3.8.3" 38 | }, 39 | "dependencies": { 40 | "@babel/runtime-corejs2": "^7.9.2", 41 | "@cicadoidea/basic": "0.0.17", 42 | "lodash": "^4.17.15", 43 | "nanoid": "^2.1.11" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/abstract/set.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | export 4 | class set_t { 5 | readonly eq: (x: T, y: T) => boolean 6 | 7 | constructor (the: { 8 | eq: (x: T, y: T) => boolean 9 | }) { 10 | this.eq = the.eq 11 | } 12 | } 13 | 14 | /** 15 | * Due to the lack of dependent type, 16 | * [[eqv_t]] is implemented as runtime test generator. 17 | */ 18 | export 19 | class eqv_t { 20 | set: set_t 21 | lhs: T 22 | rhs: T 23 | 24 | constructor (set: set_t , lhs: T, rhs: T) { 25 | this.set = set 26 | this.lhs = lhs 27 | this.rhs = rhs 28 | } 29 | 30 | check () { 31 | assert (this.set.eq (this.lhs, this.rhs)) 32 | } 33 | } 34 | 35 | export 36 | function eqv (set: set_t , lhs: T, rhs: T) { 37 | new eqv_t (set, lhs, rhs) .check () 38 | } 39 | 40 | export 41 | class not_eqv_t { 42 | set: set_t 43 | lhs: T 44 | rhs: T 45 | 46 | constructor (set: set_t , lhs: T, rhs: T) { 47 | this.set = set 48 | this.lhs = lhs 49 | this.rhs = rhs 50 | } 51 | 52 | check () { 53 | assert (! this.set.eq (this.lhs, this.rhs)) 54 | } 55 | } 56 | 57 | export 58 | function not_eqv (set: set_t , lhs: T, rhs: T) { 59 | new not_eqv_t (set, lhs, rhs) .check () 60 | } 61 | 62 | // TODO 63 | // the following definition is not useful. 64 | // maybe we can not formalize sub object relation in ts. 65 | 66 | export 67 | class subset_t { 68 | readonly set: set_t 69 | readonly in: (x: T) => boolean 70 | 71 | constructor (the: { 72 | set: set_t , 73 | in: (x: T) => boolean, 74 | }) { 75 | this.set = the.set 76 | this.in = the.in 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/cse.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as num from "./num" 5 | 6 | /** 7 | * Computational Science and Engineering 8 | * - we will need `sparse_matrix_t` 9 | * - maybe we need a abstract interface for `matrix_t` 10 | * to be able to represent K, C, T, B in even better ways 11 | */ 12 | 13 | { 14 | let K4 = num.matrix ([ 15 | [2, -1, 0, 0], 16 | [-1, 2, -1, 0], 17 | [0, -1, 2, -1], 18 | [0, 0, -1, 2], 19 | ]) 20 | 21 | let { 22 | lower, upper, 23 | permu, inver, 24 | } = K4.lower_upper_decomposition () 25 | 26 | lower.print () 27 | upper.print () 28 | permu.print () 29 | ut.log (inver) 30 | } 31 | 32 | { 33 | let C4 = num.matrix ([ 34 | [2, -1, 0, -1], 35 | [-1, 2, -1, 0], 36 | [0, -1, 2, -1], 37 | [-1, 0, -1, 2], 38 | ]) 39 | 40 | let { 41 | lower, upper, 42 | permu, inver, 43 | } = C4.lower_upper_decomposition () 44 | 45 | lower.print () 46 | upper.print () 47 | permu.print () 48 | ut.log (inver) 49 | } 50 | 51 | { 52 | let T4 = num.matrix ([ 53 | [1, -1, 0, 0], 54 | [-1, 2, -1, 0], 55 | [0, -1, 2, -1], 56 | [0, 0, -1, 2], 57 | ]) 58 | 59 | let { 60 | lower, upper, 61 | permu, inver, 62 | } = T4.lower_upper_decomposition () 63 | 64 | lower.print () 65 | upper.print () 66 | permu.print () 67 | ut.log (inver) 68 | } 69 | 70 | { 71 | let B4 = num.matrix ([ 72 | [1, -1, 0, 0], 73 | [-1, 2, -1, 0], 74 | [0, -1, 2, -1], 75 | [0, 0, -1, 1], 76 | ]) 77 | 78 | let { 79 | lower, upper, 80 | permu, inver, 81 | } = B4.lower_upper_decomposition () 82 | 83 | lower.print () 84 | upper.print () 85 | permu.print () 86 | ut.log (inver) 87 | } 88 | -------------------------------------------------------------------------------- /tests/homology.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as int from "../lib/int" 5 | import * as cx from "../lib/cell-complex" 6 | import * as hl from "../lib/homology" 7 | 8 | import { 9 | sphere_t, 10 | torus_t, 11 | klein_bottle_t, 12 | projective_plane_t, 13 | } from "../lib/complexes/four-ways-to-glue-a-square" 14 | 15 | test ("four-ways-to-glue-a-square", t => { 16 | let report = { 17 | "sphere": hl.report (new sphere_t ()), 18 | "torus": hl.report (new torus_t ()), 19 | "klein_bottle": hl.report (new klein_bottle_t ()), 20 | "projective_plane": hl.report (new projective_plane_t ()), 21 | } 22 | 23 | let expected_report = { 24 | sphere: 25 | { '0': { betti_number: 1, torsion_coefficients: [] }, 26 | '1': { betti_number: 0, torsion_coefficients: [] }, 27 | '2': { betti_number: 1, torsion_coefficients: [] }, 28 | euler_characteristic: 2 }, 29 | torus: 30 | { '0': { betti_number: 1, torsion_coefficients: [] }, 31 | '1': { betti_number: 2, torsion_coefficients: [] }, 32 | '2': { betti_number: 1, torsion_coefficients: [] }, 33 | euler_characteristic: 0 }, 34 | klein_bottle: 35 | { '0': { betti_number: 1, torsion_coefficients: [] }, 36 | '1': { betti_number: 1, torsion_coefficients: [ 2 ] }, 37 | '2': { betti_number: 0, torsion_coefficients: [] }, 38 | euler_characteristic: 0 }, 39 | projective_plane: 40 | { '0': { betti_number: 1, torsion_coefficients: [] }, 41 | '1': { betti_number: 0, torsion_coefficients: [ 2 ] }, 42 | '2': { betti_number: 0, torsion_coefficients: [] }, 43 | euler_characteristic: 1 } 44 | } 45 | 46 | t.deepEqual (report, expected_report) 47 | 48 | t.pass () 49 | }) 50 | -------------------------------------------------------------------------------- /src/complexes/four-ways-to-glue-a-square.ts: -------------------------------------------------------------------------------- 1 | import * as ut from "@cicadoidea/basic/lib/util" 2 | import * as cx from "../cell-complex" 3 | 4 | export 5 | class sphere_t extends cx.cell_complex_t { 6 | constructor () { 7 | super ({ dim: 2 }) 8 | this.attach_vertexes (["south", "middle", "north"]) 9 | this.attach_edge ("south_long", ["south", "middle"]) 10 | this.attach_edge ("north_long", ["middle", "north"]) 11 | this.attach_face ("surf", [ 12 | "south_long", 13 | "north_long", 14 | ["north_long", "rev"], 15 | ["south_long", "rev"], 16 | ]) 17 | } 18 | } 19 | 20 | export 21 | class torus_t extends cx.cell_complex_t { 22 | constructor () { 23 | super ({ dim: 2 }) 24 | this.attach_vertex ("origin") 25 | this.attach_edge ("toro", ["origin", "origin"]) 26 | this.attach_edge ("polo", ["origin", "origin"]) 27 | this.attach_face ("surf", [ 28 | "toro", 29 | "polo", 30 | ["toro", "rev"], 31 | ["polo", "rev"], 32 | ]) 33 | } 34 | } 35 | 36 | export 37 | class klein_bottle_t extends cx.cell_complex_t { 38 | constructor () { 39 | super ({ dim: 2 }) 40 | this.attach_vertex ("origin") 41 | this.attach_edge ("toro", ["origin", "origin"]) 42 | this.attach_edge ("cross", ["origin", "origin"]) 43 | this.attach_face ("surf", [ 44 | "toro", 45 | "cross", 46 | ["toro", "rev"], 47 | "cross", 48 | ]) 49 | } 50 | } 51 | 52 | export 53 | class projective_plane_t extends cx.cell_complex_t { 54 | constructor () { 55 | super ({ dim: 2 }) 56 | this.attach_vertexes (["start", "end"]) 57 | this.attach_edge ("left_rim", ["start", "end"]) 58 | this.attach_edge ("right_rim", ["end", "start"]) 59 | this.attach_face ("surf", [ 60 | "left_rim", "right_rim", 61 | "left_rim", "right_rim", 62 | ]) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/num.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as num from "../lib/num" 5 | 6 | test ("num.matrix", t => { 7 | let x = num.matrix ([ 8 | [1, 2, 4], 9 | [4, 5, 6], 10 | [7, 8, 9], 11 | ]) 12 | t.deepEqual (x.shape, [3, 3]) 13 | }) 14 | 15 | 16 | test ("num.vector", t => { 17 | let x = num.vector ([1, 2, 4]) 18 | t.deepEqual (x.size, 3) 19 | }) 20 | 21 | test ("num.matrix_t.reduced_row_echelon_form", t => { 22 | let m = num.matrix ([ 23 | [1, 3, 1, 9], 24 | [1, 1, -1, 1], 25 | [3, 11, 5, 35], 26 | ]) 27 | 28 | t.true ( 29 | m.reduced_row_echelon_form () .eq (num.matrix ([ 30 | [1, 0, -2, -3], 31 | [0, 1, 1, 4], 32 | [0, 0, 0, 0], 33 | ])) 34 | ) 35 | t.pass () 36 | }) 37 | 38 | function test_lower_upper_decomposition (t, m) { 39 | let { 40 | lower, upper, permu 41 | } = m.lower_upper_decomposition () 42 | t.true ( 43 | permu.mul (m) .eq (lower.mul (upper)) 44 | ) 45 | } 46 | 47 | test ("num.matrix_t.lower_upper_decomposition", t => { 48 | test_lower_upper_decomposition (t, num.matrix ([ 49 | [1, 3, 1], 50 | [1, 1, -1], 51 | [3, 11, 5], 52 | ])) 53 | 54 | test_lower_upper_decomposition (t, num.matrix ([ 55 | [1, 3, 1], 56 | [1, 1, -1], 57 | [3, 11, 5], 58 | ]) .transpose ()) 59 | 60 | test_lower_upper_decomposition (t, num.matrix ([ 61 | [1, 3, 1], 62 | [1, 1, -1], 63 | [3, 11, 5000], 64 | ])) 65 | 66 | test_lower_upper_decomposition (t, num.matrix ([ 67 | [4, 3], 68 | [6, 3], 69 | ])) 70 | 71 | test_lower_upper_decomposition (t, num.matrix ([ 72 | [-2, 3], 73 | [-1, 3], 74 | ])) 75 | 76 | test_lower_upper_decomposition (t, num.matrix ([ 77 | [2, 2, 100], 78 | [0, 0, 3], 79 | [2, 1, 1], 80 | ])) 81 | 82 | test_lower_upper_decomposition (t, num.matrix ([ 83 | [-2, 2, -3], 84 | [-1, 1, 3], 85 | [2, 0, -1], 86 | ])) 87 | }) 88 | -------------------------------------------------------------------------------- /tests/number.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as num from "../lib/number" 5 | 6 | test ("divmod", t => { 7 | t.deepEqual (num.divmod (17, 4), [4, 1]) 8 | t.deepEqual (num.divmod (19, 30), [0, 19]) 9 | t.deepEqual (num.divmod (-17, -10), [2, 3]) 10 | t.deepEqual (num.divmod (7, 1), [7, 0]) 11 | }) 12 | 13 | test ("gcd", t => { 14 | t.true (num.gcd (6, 7) === 1) 15 | t.true (num.gcd (1, 7) === 1) 16 | t.true (num.gcd (0, 7) === 7) 17 | t.true (num.gcd (6, 6) === 6) 18 | t.true (num.gcd (1071, 462) === 21) 19 | t.true (num.gcd (1071, -462) === 21) 20 | t.true (num.gcd (-1071, 462) === 21) 21 | }) 22 | 23 | test ("array_gcd", t => { 24 | t.true (num.array_gcd ([6, 7, 1]) === 1) 25 | t.true (num.array_gcd ([1, 7, 1]) === 1) 26 | t.true (num.array_gcd ([0, 7, 3]) === 1) 27 | t.true (num.array_gcd ([6, 6, 2]) === 2) 28 | t.true (num.array_gcd ([1071, 462, 20]) === 1) 29 | t.true (num.array_gcd ([1071, 462, 3]) === 3) 30 | t.true (num.array_gcd ([1071, -462, 15]) === 3) 31 | t.true (num.array_gcd ([-1071, 462, 11]) === 1) 32 | }) 33 | 34 | function test_gcd_ext (t, x, y) { 35 | let res = num.gcd_ext (x, y) 36 | // ut.log ([x, y, res]) 37 | t.true (num.gcd_ext_p (x, y, res)) 38 | } 39 | 40 | test ("gcd_ext", t => { 41 | test_gcd_ext (t, 6, 7) 42 | test_gcd_ext (t, 1, 7) 43 | test_gcd_ext (t, 0, 7) 44 | test_gcd_ext (t, 6, 6) 45 | test_gcd_ext (t, 1071, 462) 46 | test_gcd_ext (t, -1071, -462) 47 | test_gcd_ext (t, -1071, 462) 48 | test_gcd_ext (t, -1071, 0) 49 | test_gcd_ext (t, 0, 123) 50 | test_gcd_ext (t, 0, -123) 51 | }) 52 | 53 | function test_array_gcd_ext (t, array) { 54 | let res = num.array_gcd_ext (array) 55 | t.true (num.array_gcd_ext_p (array, res)) 56 | } 57 | 58 | test ("array_gcd_ext", t => { 59 | test_array_gcd_ext (t, [12, 27, 18, 13]) 60 | test_array_gcd_ext (t, [12, 27, 18, -13]) 61 | test_array_gcd_ext (t, [12, 27, 18, -1]) 62 | }) 63 | -------------------------------------------------------------------------------- /src/abstract/module.ts: -------------------------------------------------------------------------------- 1 | import { set_t, eqv, not_eqv } from "./set" 2 | import { abelian_group_t } from "./group" 3 | import { field_t, ring_t } from "./ring" 4 | 5 | export 6 | class module_t { 7 | ring: ring_t 8 | vector: abelian_group_t 9 | scale: (a: R, x: V) => V 10 | 11 | constructor (the: { 12 | ring: ring_t , 13 | vector: abelian_group_t , 14 | scale: (a: R, x: V) => V, 15 | }) { 16 | this.ring = the.ring 17 | this.vector = the.vector 18 | this.scale = the.scale 19 | } 20 | 21 | add (x: V, y: V): V { return this.vector.add (x, y) } 22 | eq (x: V, y: V): boolean { return this.vector.elements.eq (x, y) } 23 | get id (): V { return this.vector.id } 24 | neg (x: V): V { return this.vector.neg (x) } 25 | 26 | ring_id_action (x: V) { 27 | eqv ( 28 | this.vector.elements, 29 | this.scale (this.ring.one, x), 30 | x, 31 | ) 32 | } 33 | 34 | ring_action (a: R, b: R, x: V) { 35 | eqv ( 36 | this.vector.elements, 37 | this.scale (a, this.scale (b, x)), 38 | this.scale (this.ring.mul (a, b), x), 39 | ) 40 | } 41 | 42 | vector_add_distr (a: R, x: V, y: V) { 43 | eqv ( 44 | this.vector.elements, 45 | this.scale (a, this.add (x, y)), 46 | this.add ( 47 | this.scale (a, x), 48 | this.scale (a, y), 49 | ), 50 | ) 51 | } 52 | 53 | ring_add_distr (a: R, b: R, x: V) { 54 | eqv ( 55 | this.vector.elements, 56 | this.scale (this.ring.add (a, b), x), 57 | this.add ( 58 | this.scale (a, x), 59 | this.scale (b, x), 60 | ), 61 | ) 62 | } 63 | } 64 | 65 | export 66 | class vector_space_t extends module_t { 67 | field: field_t 68 | 69 | constructor (the: { 70 | field: field_t , 71 | vector: abelian_group_t , 72 | scale: (a: R, x: V) => V, 73 | }) { 74 | super ({ 75 | ...the, 76 | ring: the.field, 77 | }) 78 | this.field = the.field 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /todo/euclidean-space-class.ts: -------------------------------------------------------------------------------- 1 | import { set_t, eqv } from "./set" 2 | import { field_t } from "./field" 3 | import { abelian_group_t } from "./group" 4 | import { vector_space_t } from "./vector-space" 5 | import { affine_space_t } from "./affine-space" 6 | import { number_field_t } from "./number" 7 | 8 | /** 9 | * I do not define abstract inner product space yet. 10 | * because the axioms involves conjugation of complex structure, 11 | * which I do not yet fully understand. 12 | 13 | * There is only one Euclidean space of each dimension, 14 | * and since dimension is handled at runtime, 15 | * I define euclidean_space_t as concrete class. 16 | */ 17 | 18 | export 19 | class vec_t extends vector_space_t { 20 | dim: number 21 | 22 | constructor (dim: number) { 23 | super (new number_field_t ()) 24 | this.dim = dim 25 | } 26 | 27 | eq (x: vector_t, y: vector_t): boolean { 28 | return x.eq (y) 29 | } 30 | 31 | id = new vector_t (nd.array_t.zeros ([this.dim])) 32 | 33 | add (v: vector_t, w: vector_t): vector_t { 34 | return v.add (w) 35 | } 36 | 37 | neg (x: vector_t): vector_t { 38 | return x.map (n => -n) 39 | } 40 | 41 | scale (a: number, x: vector_t): vector_t { 42 | return x.scale (a) 43 | } 44 | } 45 | 46 | export 47 | class point_set_t extends set_t { 48 | dim: number 49 | 50 | constructor (dim: number) { 51 | super () 52 | this.dim = dim 53 | } 54 | 55 | eq (x: point_t, y: point_t): boolean { 56 | return x.eq (y) 57 | } 58 | } 59 | 60 | export 61 | class euclidean_space_t 62 | extends affine_space_t { 63 | dim: number 64 | 65 | constructor (dim: number) { 66 | let vec = new vec_t (dim) 67 | let points = new point_set_t (dim) 68 | super (vec, points) 69 | this.dim = dim 70 | } 71 | 72 | trans (p: point_t, v: vector_t): point_t { 73 | return p.trans (v) 74 | } 75 | 76 | diff (p: point_t, q: point_t): vector_t { 77 | return p.diff (q) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/abstract/group.ts: -------------------------------------------------------------------------------- 1 | import { set_t, eqv } from "./set" 2 | 3 | export 4 | class monoid_t { 5 | elements: set_t 6 | id: G 7 | mul: (x: G, y: G) => G 8 | 9 | constructor (the: { 10 | elements: set_t , 11 | id: G, 12 | mul: (x: G, y: G) => G, 13 | }) { 14 | this.elements = the.elements 15 | this.id = the.id 16 | this.mul = the.mul 17 | } 18 | 19 | assoc (x: G, y: G, z: G) { 20 | eqv ( 21 | this.elements, 22 | this.mul (this.mul (x, y), z), 23 | this.mul (x, this.mul (y, z)), 24 | ) 25 | } 26 | 27 | id_left (x: G) { 28 | eqv ( 29 | this.elements, 30 | this.mul (this.id, x), 31 | x, 32 | ) 33 | } 34 | 35 | id_right (x: G) { 36 | eqv ( 37 | this.elements, 38 | this.mul (x, this.id), 39 | x, 40 | ) 41 | } 42 | } 43 | 44 | export 45 | class group_t extends monoid_t { 46 | inv: (x: G) => G 47 | 48 | constructor (the: { 49 | elements: set_t , 50 | id: G, 51 | mul: (x: G, y: G) => G, 52 | inv: (x: G) => G, 53 | }) { 54 | super (the) 55 | this.inv = the.inv 56 | } 57 | 58 | div (x: G, y: G): G { 59 | return this.mul (x, this.inv (y)) 60 | } 61 | 62 | id_inv (x: G) { 63 | eqv ( 64 | this.elements, 65 | this.mul (x, this.inv (x)), 66 | this.id, 67 | ) 68 | } 69 | } 70 | 71 | export 72 | class abelian_group_t extends group_t { 73 | add: (x: G, y: G) => G 74 | neg: (x: G) => G 75 | 76 | constructor (the: { 77 | elements: set_t , 78 | id: G, 79 | add: (x: G, y: G) => G, 80 | neg: (x: G) => G, 81 | }) { 82 | super ({ 83 | ...the, 84 | mul: the.add, 85 | inv: the.neg, 86 | }) 87 | this.add = the.add 88 | this.neg = the.neg 89 | } 90 | 91 | sub (x: G, y: G): G { return this.div (x, y) } 92 | 93 | commu (x: G, y: G) { 94 | eqv ( 95 | this.elements, 96 | this.add (x, y), 97 | this.add (y, x), 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /todo/number.ts: -------------------------------------------------------------------------------- 1 | import { group_t, abelian_group_t } from "./group" 2 | import { field_t } from "./field" 3 | 4 | export 5 | class number_add_group_t extends group_t { 6 | constructor () { 7 | super () 8 | } 9 | 10 | eq (x: number, y: number): boolean { 11 | return x === y 12 | } 13 | 14 | id = 0 15 | 16 | mul (x: number, y: number): number { 17 | return x + y 18 | } 19 | 20 | inv (x: number): number { 21 | return - x 22 | } 23 | } 24 | 25 | { 26 | let group = new number_add_group_t () 27 | 28 | group.assoc (1, 2, 3) 29 | group.assoc (3, 2, 1) 30 | group.id_left (1) 31 | group.id_left (3) 32 | group.id_left (1) 33 | group.id_left (3) 34 | group.id_inv (1) 35 | group.id_inv (3) 36 | } 37 | 38 | export 39 | class number_add_abelian_group_t extends abelian_group_t { 40 | constructor () { 41 | super () 42 | } 43 | 44 | eq (x: number, y: number): boolean { 45 | return x === y 46 | } 47 | 48 | id = 0 49 | 50 | add (x: number, y: number): number { 51 | return x + y 52 | } 53 | 54 | neg (x: number): number { 55 | return - x 56 | } 57 | } 58 | 59 | { 60 | let abel = new number_add_abelian_group_t () 61 | 62 | abel.assoc (1, 2, 3) 63 | abel.assoc (3, 2, 1) 64 | abel.id_left (1) 65 | abel.id_left (3) 66 | abel.id_left (1) 67 | abel.id_left (3) 68 | 69 | abel.id_neg (1) 70 | abel.id_neg (3) 71 | 72 | abel.commu (1, 2) 73 | abel.commu (3, 2) 74 | } 75 | 76 | export 77 | class number_field_t extends field_t { 78 | constructor () { 79 | super () 80 | } 81 | 82 | eq (x: number, y: number): boolean { 83 | return x === y 84 | } 85 | 86 | add_id = 0 87 | 88 | add (x: number, y: number): number { 89 | return x + y 90 | } 91 | 92 | neg (x: number): number { 93 | return - x 94 | } 95 | 96 | mul_id = 1 97 | 98 | mul (x: number, y: number): number { 99 | return x * y 100 | } 101 | 102 | pure_inv (x: number): number { 103 | return 1 / x 104 | } 105 | } 106 | 107 | { 108 | let field = new number_field_t () 109 | 110 | field.add_assoc (1, 2, 3) 111 | field.add_assoc (3, 2, 1) 112 | field.add_id_left (1) 113 | field.add_id_left (3) 114 | field.add_id_right (1) 115 | field.add_id_right (3) 116 | field.add_id_neg (1) 117 | field.add_id_neg (3) 118 | 119 | field.mul_assoc (1, 2, 3) 120 | field.mul_assoc (3, 2, 1) 121 | field.mul_id_left (1) 122 | field.mul_id_left (3) 123 | field.mul_id_right (1) 124 | field.mul_id_right (3) 125 | field.mul_id_inv (1) 126 | field.mul_id_inv (3) 127 | 128 | field.distr (1, 2, 3) 129 | field.distr (3, 2, 1) 130 | } 131 | -------------------------------------------------------------------------------- /examples/four-ways-to-glue-a-square.js: -------------------------------------------------------------------------------- 1 | let cx = require ("../lib/cell-complex") 2 | let hl = require ("../lib/homology") 3 | let ut = require ("../lib/util") 4 | 5 | class sphere_t extends cx.cell_complex_t { 6 | constructor () { 7 | super ({ dim: 2 }) 8 | this.attach_vertexes (["south", "middle", "north"]) 9 | this.attach_edge ("south_long", ["south", "middle"]) 10 | this.attach_edge ("north_long", ["middle", "north"]) 11 | this.attach_face ("surf", [ 12 | "south_long", 13 | "north_long", 14 | ["north_long", "rev"], 15 | ["south_long", "rev"], 16 | ]) 17 | } 18 | } 19 | 20 | class torus_t extends cx.cell_complex_t { 21 | constructor () { 22 | super ({ dim: 2 }) 23 | this.attach_vertex ("origin") 24 | this.attach_edge ("toro", ["origin", "origin"]) 25 | this.attach_edge ("polo", ["origin", "origin"]) 26 | this.attach_face ("surf", [ 27 | "toro", 28 | "polo", 29 | ["toro", "rev"], 30 | ["polo", "rev"], 31 | ]) 32 | } 33 | } 34 | 35 | class klein_bottle_t extends cx.cell_complex_t { 36 | constructor () { 37 | super ({ dim: 2 }) 38 | this.attach_vertex ("origin") 39 | this.attach_edge ("toro", ["origin", "origin"]) 40 | this.attach_edge ("cross", ["origin", "origin"]) 41 | this.attach_face ("surf", [ 42 | "toro", 43 | "cross", 44 | ["toro", "rev"], 45 | "cross", 46 | ]) 47 | } 48 | } 49 | 50 | class projective_plane_t extends cx.cell_complex_t { 51 | constructor () { 52 | super ({ dim: 2 }) 53 | this.attach_vertexes (["start", "end"]) 54 | this.attach_edge ("left_rim", ["start", "end"]) 55 | this.attach_edge ("right_rim", ["end", "start"]) 56 | this.attach_face ("surf", [ 57 | "left_rim", "right_rim", 58 | "left_rim", "right_rim", 59 | ]) 60 | } 61 | } 62 | 63 | let report = { 64 | "sphere": hl.report (new sphere_t ()), 65 | "torus": hl.report (new torus_t ()), 66 | "klein_bottle": hl.report (new klein_bottle_t ()), 67 | "projective_plane": hl.report (new projective_plane_t ()), 68 | } 69 | 70 | ut.log (report) 71 | 72 | let expected_report = { 73 | sphere: 74 | { '0': { betti_number: 1, torsion_coefficients: [] }, 75 | '1': { betti_number: 0, torsion_coefficients: [] }, 76 | '2': { betti_number: 1, torsion_coefficients: [] }, 77 | euler_characteristic: 2 }, 78 | torus: 79 | { '0': { betti_number: 1, torsion_coefficients: [] }, 80 | '1': { betti_number: 2, torsion_coefficients: [] }, 81 | '2': { betti_number: 1, torsion_coefficients: [] }, 82 | euler_characteristic: 0 }, 83 | klein_bottle: 84 | { '0': { betti_number: 1, torsion_coefficients: [] }, 85 | '1': { betti_number: 1, torsion_coefficients: [ 2 ] }, 86 | '2': { betti_number: 0, torsion_coefficients: [] }, 87 | euler_characteristic: 0 }, 88 | projective_plane: 89 | { '0': { betti_number: 1, torsion_coefficients: [] }, 90 | '1': { betti_number: 0, torsion_coefficients: [ 2 ] }, 91 | '2': { betti_number: 0, torsion_coefficients: [] }, 92 | euler_characteristic: 1 } 93 | } 94 | -------------------------------------------------------------------------------- /src/games/hackenbush.ts: -------------------------------------------------------------------------------- 1 | import { 2 | id_t, vertex_t, edge_t, graph_t 3 | } from "../graph-info" 4 | 5 | import * as cg from "../combinatorial-game" 6 | import { random_bot_t } from "./bots/rand" 7 | 8 | export 9 | type player_t = "blue" | "red" 10 | 11 | export 12 | type color_t = "blue" | "red" | "green" 13 | 14 | export 15 | class state_t { 16 | constructor ( 17 | public graph: graph_t <{}, { value: color_t }> = new graph_t (), 18 | ) {} 19 | 20 | // self-builder 21 | 22 | colored_edge ( 23 | start: number, 24 | end: number, 25 | color: color_t, 26 | ): state_t { 27 | this.graph.vertex_map.set (start, new vertex_t (start, {})) 28 | this.graph.vertex_map.set (end, new vertex_t (end, {})) 29 | this.graph.edge (this.graph.edge_map.size, start, end, { value: color }) 30 | return this 31 | } 32 | 33 | blue (start: number, end: number): state_t { 34 | return this.colored_edge (start, end, "blue") 35 | } 36 | 37 | red (start: number, end: number): state_t { 38 | return this.colored_edge (start, end, "red") 39 | } 40 | 41 | green (start: number, end: number): state_t { 42 | return this.colored_edge (start, end, "green") 43 | } 44 | } 45 | 46 | export 47 | type choice_t = id_t 48 | 49 | export 50 | class game_t 51 | extends cg.game_t { 52 | choices ( 53 | p: player_t, 54 | s: state_t, 55 | ): Array { 56 | let array: Array = [] 57 | s.graph.edge_map.forEach ((edge, id) => { 58 | if (edge.info.value === p || 59 | edge.info.value === "green") 60 | array.push (id) 61 | }) 62 | return array 63 | } 64 | 65 | choose ( 66 | p: player_t, 67 | ch: choice_t, 68 | s: state_t, 69 | ): state_t { 70 | let graph = s.graph.copy () 71 | graph.edge_map.delete (ch) 72 | return new state_t ( 73 | graph.connected_component_of_vertex (0)) 74 | } 75 | 76 | win_p ( 77 | p: player_t, 78 | s: state_t, 79 | ): boolean { 80 | for (let [_id, edge] of s.graph.edge_map) { 81 | if (edge.info.value !== p && edge.info.value !== "green") 82 | return false 83 | } 84 | return true 85 | } 86 | } 87 | 88 | export 89 | class play_t 90 | extends cg.play_t { 91 | next_player = this.tow_player_alternating 92 | 93 | state_log (s: state_t) { 94 | console.log ("------") 95 | s.graph.edge_map.forEach ((edge, id) => { 96 | console.log ( 97 | `#${ id }:`, 98 | `${ edge.info.value }`, 99 | `${ edge.start }`, 100 | `${ edge.end }`, 101 | ) 102 | }) 103 | console.log ("------") 104 | } 105 | } 106 | 107 | export 108 | let hackenbush = new game_t () 109 | 110 | export 111 | let random_bot = new random_bot_t (hackenbush) 112 | 113 | export 114 | function new_play (bush: state_t): play_t { 115 | let play = new play_t ( 116 | hackenbush, bush, "blue", ["blue", "red"]) 117 | return play 118 | } 119 | -------------------------------------------------------------------------------- /src/number.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | 5 | export 6 | function mod ( 7 | x: number, 8 | y: number, 9 | ): number { 10 | let m = x % y 11 | if (m < 0) { 12 | return m + Math.abs (y) 13 | } else { 14 | return m 15 | } 16 | } 17 | 18 | export 19 | function divmod ( 20 | x: number, 21 | y: number, 22 | ): [number, number] { 23 | let m = mod (x, y) 24 | let d = (x - m) / y 25 | return [d, m] 26 | } 27 | 28 | export 29 | function div ( 30 | x: number, 31 | y: number, 32 | ): number { 33 | let m = mod (x, y) 34 | return (x - m) / y 35 | } 36 | 37 | export 38 | function gcd ( 39 | x: number, 40 | y: number, 41 | ): number { 42 | while (y !== 0) { 43 | if (Math.abs (y) > Math.abs (x)) { 44 | [x, y] = [y, x]; 45 | } else { 46 | let r = mod (x, y); 47 | [x, y] = [y, r]; 48 | } 49 | } 50 | return x 51 | } 52 | 53 | export 54 | function array_gcd ( 55 | array: Array 56 | ): number { 57 | return array.reduce ((acc, cur) => gcd (acc, cur)) 58 | } 59 | 60 | export 61 | function gcd_ext ( 62 | x: number, 63 | y: number, 64 | ): [number, [number, number]] { 65 | let old_s = 1 66 | let old_t = 0 67 | let old_r = x 68 | 69 | let s = 0 70 | let t = 1 71 | let r = y 72 | 73 | while (r !== 0) { 74 | if (Math.abs (r) > Math.abs (old_r)) { 75 | [ 76 | s, t, r, 77 | old_s, old_t, old_r, 78 | ] = [ 79 | old_s, old_t, old_r, 80 | s, t, r, 81 | ] 82 | } else { 83 | let q = div (old_r, r); 84 | [old_r, r] = [r, old_r - (q * r)]; 85 | [old_s, s] = [s, old_s - (q * s)]; 86 | [old_t, t] = [t, old_t - (q * t)]; 87 | } 88 | } 89 | return [old_r, [old_s, old_t]] 90 | } 91 | 92 | export 93 | function gcd_ext_p ( 94 | x: number, 95 | y: number, 96 | res: [number, [number, number]], 97 | ): boolean { 98 | let [d, [s, t]] = res 99 | return ((gcd (x, y) === d) && 100 | (s*x + t*y === d)) 101 | } 102 | 103 | export 104 | function array_dot ( 105 | x: Array , 106 | y: Array , 107 | ): number { 108 | assert (x.length === y.length) 109 | let sum = 0 110 | for (let i of ut.range (0, x.length)) { 111 | sum += x [i] * y [i] 112 | } 113 | return sum 114 | } 115 | 116 | export 117 | function array_gcd_ext_p ( 118 | array: Array , 119 | res: [number, Array ], 120 | ): boolean { 121 | let [d, ext] = res 122 | return ((array_gcd (array) === d) && 123 | (array_dot (array, ext) === d)) 124 | } 125 | 126 | export 127 | function array_gcd_ext ( 128 | array: Array 129 | ): [number, Array ] { 130 | let ext = new Array () 131 | let d = array.reduce ((acc, cur) => { 132 | let [d, [s, t]] = gcd_ext (acc, cur) 133 | if (ext.length === 0) { 134 | ext = [s, t] 135 | } else { 136 | ext = ext.map (x => x * s) 137 | ext.push (t) 138 | } 139 | return d 140 | }) 141 | return [d, ext] 142 | } 143 | -------------------------------------------------------------------------------- /docs/note/cicada-note.org: -------------------------------------------------------------------------------- 1 | #+title: note 2 | - fix `with_details` 3 | - fix `unique` 4 | - unification with function 5 | make the game of equivalent relation constructive 6 | * explicit type conversion 7 | 8 | - method such as `.as_random_variable_t` 9 | 10 | - subtype is implicit type conversion 11 | 12 | - we also a way to make explicit thing implicit in some context 13 | (just like type class of scala) 14 | 15 | * about model theory and category theory 16 | 17 | - with a language in which we can define class and function 18 | we can formalize both model theory and category theory 19 | 20 | - mathematical 21 | 22 | - API of class and object 23 | 24 | - be able to generate (enumerate) all objects of any (first order) class 25 | - first order -- can not generate elements of `type` 26 | - include function type -- this requires proof [todo] 27 | 28 | - unification with logic variable 29 | 30 | - API of function [todo] 31 | 32 | * levels 33 | 34 | - we do not handle levels, and see what paradox we will get 35 | 36 | * product-type and sum-type 37 | 38 | | | literal syntax | inhabit | 39 | |--------------+----------------+---------------------| 40 | | product-type | [ ...] | [ ...] | 41 | | sum-type | + ( ...) | : ... | 42 | 43 | * conj-type, disj-type and heir-type 44 | 45 | | | definition syntax | 46 | |-----------+----------------------------------| 47 | | conj-type | conj { ... } | 48 | | disj-type | disj { [ ...] ... } | 49 | | heir-type | heir { [ ...>] ... } | 50 | 51 | * conj-type 52 | 53 | - it bound to a named type-constructor for the conj-type 54 | and specify a record with named and typed fields 55 | 56 | - a conj-type is a partly inhabited record 57 | 58 | - for example : 59 | cons-t (t) 60 | cons-c (car cdr) 61 | 62 | * constructor call syntax 63 | 64 | - call-with-order : 65 | -c ( ...) 66 | 67 | - call-with-field-name : 68 | -c { = ...} 69 | 70 | * disj-type 71 | 72 | - it bound to a named type-constructor for the disj-type 73 | and specify a list of fields 74 | 75 | - sub-type-relation 76 | :> 77 | 78 | - type-constructor of each sub-types 79 | must include these fields and types 80 | 81 | - each data of any of its sub-types 82 | will inhabit the disj-type 83 | 84 | * heir-type 85 | 86 | - it bound to a named type-constructor for the heir-type 87 | and specify a list of fields 88 | 89 | - sub-type-relation 90 | <: 91 | 92 | - an heir-type includes its super-types records 93 | 94 | - each data of the heir-type 95 | will also inhabit all its super-types 96 | 97 | * sub-type-relation 98 | 99 | - `c1 <: c2` means c1 inherit c2 's fields, 100 | thus c1 is more special then c2, 101 | because c1 has more interface functions than c2. 102 | 103 | - whatever data inhabits c1 also inhabits c2. 104 | 105 | * eqv-relation-t of eqv-t 106 | 107 | - eqv-relation-t of eqv-t 108 | is proved by the rules of unification 109 | or the rules of substitution 110 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at project issue tracker. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /src/combinatorial-game.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import * as _ from "lodash" 3 | 4 | export 5 | abstract class game_t { 6 | abstract choices (p: P, s: S): Array 7 | 8 | abstract choose (p: P, c: C, s: S): S 9 | 10 | abstract win_p (p: P, s: S): boolean 11 | 12 | valid_choice_p (p: P, c: C, s: S): boolean { 13 | let choices = this.choices (p, s) 14 | return choices.some ((x) => _.isEqual (x, c)) 15 | } 16 | } 17 | 18 | export 19 | abstract class play_t { 20 | constructor ( 21 | public game: game_t , 22 | public init_state: S, 23 | public init_player: P, 24 | public players: Array

, 25 | public moments: Array <{ 26 | player: P, 27 | choice: C, 28 | state: S, 29 | }> = [], 30 | ) {} 31 | 32 | abstract next_player (): P 33 | 34 | beginning_p (): boolean { 35 | return this.moments.length === 0 36 | } 37 | 38 | last_moment (): { 39 | player: P, 40 | choice: C, 41 | state: S, 42 | } { 43 | if (this.beginning_p ()) { 44 | throw new Error ("play_t.last_moment") 45 | } else { 46 | let moment = this.moments.slice (-1) .pop () 47 | return moment as { 48 | player: P, 49 | choice: C, 50 | state: S, 51 | } 52 | } 53 | } 54 | 55 | last_player (): P { return this.last_moment () .player } 56 | 57 | last_choice (): C { return this.last_moment () .choice } 58 | 59 | last_state (): S { 60 | if (this.beginning_p ()) { 61 | return this.init_state 62 | } else { 63 | return this.last_moment () .state 64 | } 65 | } 66 | 67 | tow_player_alternating (): P { 68 | if (this.players.length !== 2) { 69 | throw new Error ("play_t.tow_player_alternating fail") 70 | } 71 | let p0 = this.players [0] 72 | let p1 = this.players [1] 73 | if (this.beginning_p ()) { 74 | return this.init_player 75 | } else { 76 | switch (this.last_player ()) { 77 | case p0: return p1 78 | case p1: return p0 79 | default: throw new Error ("play_t.tow_player_alternating fail") 80 | } 81 | } 82 | } 83 | 84 | winner (): P | null { 85 | let s = this.last_state () 86 | for (let p of this.players) { 87 | if (this.game.win_p (p, s)) { 88 | return p 89 | } 90 | } 91 | return null 92 | } 93 | 94 | state_log (s: S) { 95 | console.log (s) 96 | } 97 | 98 | move (p: P, c: C) { 99 | if (this.next_player () === p) { 100 | let s = this.last_state () 101 | if (this.game.valid_choice_p (p, c, s)) { 102 | let next_state = this.game.choose (p, c, s) 103 | this.moments.push ({ 104 | player: p, 105 | choice: c, 106 | state: next_state, 107 | }) 108 | } else { 109 | console.log ( 110 | `[warning]`, 111 | `invalid move: ${c} for player: ${p}`, 112 | ) 113 | } 114 | } else { 115 | console.log ( 116 | `[warning]`, 117 | `next player should be ${ this.next_player () }`, 118 | ) 119 | } 120 | this.state_log (this.last_state ()) 121 | let w = this.winner () 122 | if (w !== null) console.log ( 123 | `[info]`, 124 | `winner is ${w}`, 125 | ) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/games/tic-tac-toe.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash" 2 | 3 | import * as cg from "../combinatorial-game" 4 | import { random_bot_t } from "./bots/rand" 5 | 6 | type player_t = "X" | "O" 7 | 8 | type mark_t = "_" | "X" | "O" 9 | 10 | type row_t = [ mark_t, mark_t, mark_t ] 11 | 12 | type state_t = [ row_t, row_t, row_t ] 13 | 14 | let empty_state: state_t = [ 15 | [ "_", "_", "_" ], 16 | [ "_", "_", "_" ], 17 | [ "_", "_", "_" ], 18 | ] 19 | 20 | type choice_t = [ number, number ] 21 | 22 | class game_t 23 | extends cg.game_t { 24 | choices ( 25 | _p: player_t, 26 | s: state_t 27 | ): Array { 28 | let array: Array = [] 29 | s.forEach ((row, x) => { 30 | row.forEach ((mark, y) => { 31 | if (s [x] [y] === "_") 32 | array.push ([x, y]) 33 | }) 34 | }) 35 | return array 36 | } 37 | 38 | choose ( 39 | p: player_t, 40 | ch: choice_t, 41 | s: state_t, 42 | ): state_t { 43 | let s1 = _.cloneDeep (s) 44 | let [x, y] = ch 45 | s1 [x] [y] = p 46 | return s1 47 | } 48 | 49 | win_p ( 50 | p: player_t, 51 | s: state_t, 52 | ): boolean { 53 | return (row_win_p (p, s) || 54 | column_win_p (p, s) || 55 | diagonal_win_p (p, s)) 56 | } 57 | } 58 | 59 | function row_win_p ( 60 | p: player_t, 61 | s: state_t, 62 | ): boolean { 63 | return ((s [0] [0] === p && 64 | s [0] [1] === p && 65 | s [0] [2] === p) || 66 | (s [1] [0] === p && 67 | s [1] [1] === p && 68 | s [1] [2] === p) || 69 | (s [2] [0] === p && 70 | s [2] [1] === p && 71 | s [2] [2] === p)) 72 | } 73 | 74 | function column_win_p ( 75 | p: player_t, 76 | s: state_t, 77 | ): boolean { 78 | return ((s [0] [0] === p && 79 | s [1] [0] === p && 80 | s [2] [0] === p) || 81 | (s [0] [1] === p && 82 | s [1] [1] === p && 83 | s [2] [1] === p) || 84 | (s [0] [2] === p && 85 | s [1] [2] === p && 86 | s [2] [2] === p)) 87 | } 88 | 89 | function diagonal_win_p ( 90 | p: player_t, 91 | s: state_t, 92 | ): boolean { 93 | return ((s [0] [0] === p && 94 | s [1] [1] === p && 95 | s [2] [2] === p) || 96 | (s [0] [2] === p && 97 | s [1] [1] === p && 98 | s [2] [0] === p)) 99 | } 100 | 101 | class play_t 102 | extends cg.play_t { 103 | next_player = this.tow_player_alternating 104 | 105 | state_log (s: state_t) { 106 | let repr = "" 107 | s.forEach ((row, x) => { 108 | row.forEach ((mark, y) => { 109 | repr += `${ mark } ` 110 | }) 111 | repr += "\n" 112 | }) 113 | console.log (repr) 114 | } 115 | 116 | draw_p (): boolean { 117 | for (let p of this.players) { 118 | if (this.game.choices (p, this.last_state ()) .length > 0) { 119 | return false 120 | } 121 | } 122 | return true 123 | } 124 | } 125 | 126 | export 127 | let tic_tac_toe = new game_t () 128 | 129 | export 130 | let random_bot = new random_bot_t (tic_tac_toe) 131 | 132 | export 133 | function new_play (): play_t { 134 | let play = new play_t ( 135 | tic_tac_toe, empty_state, "O", ["O", "X"]) 136 | return play 137 | } 138 | 139 | // brute force the winning strategy 140 | -------------------------------------------------------------------------------- /src/homology.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import { dic_t } from "./dic" 5 | import * as int from "./int" 6 | import * as cx from "./cell-complex" 7 | 8 | export 9 | function boundary_vector ( 10 | com: cx.cell_complex_t, 11 | cell: cx.cell_t, 12 | ): int.vector_t { 13 | if (cell.dim === 0) { 14 | return int.vector_t.zeros (0) 15 | } else if (cell.dim === 1) { 16 | let edge = cell as cx.edge_t 17 | let size = com.vertex_array.length 18 | let vector = int.vector_t.zeros (size) 19 | vector.update_at (edge.start.ser (com), n => n - 1n) 20 | vector.update_at (edge.end.ser (com), n => n + 1n) 21 | return vector 22 | } else if (cell.dim === 2) { 23 | let face = cell as cx.face_t 24 | let size = com.edge_array.length 25 | let vector = int.vector_t.zeros (size) 26 | for (let e of face.circuit) { 27 | if (e instanceof cx.edge_rev_t) { 28 | vector.update_at (e.rev.ser (com), n => n - 1n) 29 | } else { 30 | vector.update_at (e.ser (com), n => n + 1n) 31 | } 32 | } 33 | return vector 34 | } else { 35 | throw new Error ("can only calculate dim 0, 1, 2 yet") 36 | } 37 | } 38 | 39 | export 40 | function boundary_matrix ( 41 | com: cx.cell_complex_t, 42 | dim: number, 43 | ): int.matrix_t { 44 | let m = com.dim_size (dim - 1) 45 | let n = com.dim_size (dim) 46 | let matrix = int.matrix_t.zeros (m, n) 47 | for (let cell of com.cell_array (dim)) { 48 | matrix.set_col (cell.ser (com), boundary_vector (com, cell)) 49 | } 50 | return matrix 51 | } 52 | 53 | export 54 | function homology_diag_canonical ( 55 | com: cx.cell_complex_t, 56 | dim: number, 57 | ): int.matrix_t { 58 | let low = boundary_matrix (com, dim) 59 | let high = boundary_matrix (com, dim + 1) 60 | let kernel = low.kernel () 61 | let image = high.image () 62 | let matrix = kernel.solve_matrix (image) 63 | if (matrix === null) { 64 | throw new Error ("[internal] solve_matrix fail") 65 | } else { 66 | return matrix.diag_canonical_form () 67 | } 68 | } 69 | 70 | export 71 | function euler_characteristic ( 72 | com: cx.cell_complex_t, 73 | ): number { 74 | let n = 0 75 | for (let d of ut.range (0, com.dim + 1)) { 76 | if (d % 2 === 0) { 77 | n += betti_number (homology_diag_canonical (com, d)) 78 | } else { 79 | n -= betti_number (homology_diag_canonical (com, d)) 80 | } 81 | } 82 | return n 83 | } 84 | 85 | export 86 | function betti_number ( 87 | diag_canonical: int.matrix_t 88 | ): number { 89 | let [m, n] = diag_canonical.shape 90 | let r = diag_canonical.rank () 91 | return m - r 92 | } 93 | 94 | export 95 | function torsion_coefficients ( 96 | diag_canonical: int.matrix_t 97 | ): Array { 98 | let array = new Array () 99 | let invariant_factors = diag_canonical.diag () 100 | for (let v of invariant_factors.values ()) { 101 | let n = Number (int.abs (v)) 102 | if (n !== 0 && n !== 1) { 103 | array.push (n) 104 | } 105 | } 106 | return array 107 | } 108 | 109 | export 110 | function report ( 111 | com: cx.cell_complex_t, 112 | ) { 113 | let obj: any = {} 114 | for (let d of ut.range (0, com.dim + 1)) { 115 | let diag_canonical = homology_diag_canonical (com, d) 116 | obj [d] = { 117 | betti_number: betti_number (diag_canonical), 118 | torsion_coefficients: torsion_coefficients (diag_canonical), 119 | } 120 | } 121 | obj ["euler_characteristic"] = euler_characteristic (com) 122 | return obj 123 | } 124 | -------------------------------------------------------------------------------- /src/permutation.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash" 2 | import assert from "assert" 3 | 4 | import * as ut from "@cicadoidea/basic/lib/util" 5 | 6 | export 7 | class permutation_t { 8 | /** 9 | * A permutation is encoded by 10 | * the result of its action on [1, 2, ..., n]. 11 | * I call this result "sequence". 12 | */ 13 | readonly sequence: Array 14 | readonly size: number 15 | 16 | constructor (sequence: Array ) { 17 | this.sequence = sequence 18 | this.size = sequence.length 19 | } 20 | 21 | get (i: number): number { 22 | return this.sequence [i] 23 | } 24 | 25 | *[Symbol.iterator] () { 26 | for (let v of this.sequence) { 27 | yield v 28 | } 29 | } 30 | 31 | *pairs () { 32 | let i = 0 33 | for (let x of this.sequence) { 34 | yield [i, x] as [number, number] 35 | i += 1 36 | } 37 | } 38 | 39 | *inv_pairs () { 40 | let i = 0 41 | for (let x of this.sequence) { 42 | yield [x, i] as [number, number] 43 | i += 1 44 | } 45 | } 46 | 47 | inv (): permutation_t { 48 | let sequence = Array.from (this.inv_pairs ()) 49 | .sort ((x, y) => x [0] - y [0]) 50 | .map (x => x [1]) 51 | return new permutation_t (sequence) 52 | } 53 | 54 | mul (that: permutation_t): permutation_t { 55 | let sequence = new Array () 56 | for (let i of that.sequence) { 57 | sequence.push (this.sequence [i]) 58 | } 59 | return new permutation_t (sequence) 60 | } 61 | 62 | eq (that: permutation_t): boolean { 63 | return _.isEqual (this.sequence, that.sequence) 64 | } 65 | 66 | lt (that: permutation_t): boolean { 67 | assert (this.size === that.size) 68 | return this.lt_after (that, 0) 69 | } 70 | 71 | private lt_after (that: permutation_t, n: number): boolean { 72 | if (n >= this.size) { 73 | return false 74 | } 75 | 76 | let x = this.get (n) 77 | let y = that.get (n) 78 | 79 | if (x < y) { 80 | return true 81 | } else if (x === y) { 82 | return this.lt_after (that, n + 1) 83 | } else { 84 | return false 85 | } 86 | } 87 | 88 | lte (that: permutation_t): boolean { 89 | return this.lte_after (that, 0) 90 | } 91 | 92 | private lte_after (that: permutation_t, n: number): boolean { 93 | if (n >= this.size) { 94 | return true 95 | } 96 | 97 | let x = this.get (n) 98 | let y = that.get (n) 99 | 100 | if (x < y) { 101 | return true 102 | } else if (x === y) { 103 | return this.lte_after (that, n + 1) 104 | } else { 105 | return false 106 | } 107 | } 108 | 109 | gt (that: permutation_t): boolean { 110 | return that.lt (this) 111 | } 112 | 113 | gte (that: permutation_t): boolean { 114 | return that.lte (this) 115 | } 116 | 117 | static id (size: number): permutation_t { 118 | return new permutation_t ( 119 | Array.from (ut.range (0, size)) 120 | ) 121 | } 122 | 123 | tuck (i: number, j: number): permutation_t { 124 | let sequence = new Array () 125 | for (let k of ut.range (0, this.size)) { 126 | if (i <= k && k < j) { 127 | sequence [k] = this.get (k+1) 128 | } else if (k === j) { 129 | sequence [k] = this.get (i) 130 | } else { 131 | sequence [k] = this.get (k) 132 | } 133 | } 134 | return new permutation_t (sequence) 135 | } 136 | 137 | // TODO 138 | // static from_cycle 139 | 140 | // TODO 141 | // canonical_cycle 142 | 143 | // TODO 144 | // lehmer_code (): Array 145 | } 146 | -------------------------------------------------------------------------------- /src/dic.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Map with `string` as key. 3 | */ 4 | export 5 | class dic_t { 6 | protected val_map: Map 7 | protected key_map: Map 8 | readonly key_to_str: (k: K) => string 9 | 10 | constructor (key_to_str: (k: K) => string = JSON.stringify) { 11 | this.val_map = new Map () 12 | this.key_map = new Map () 13 | this.key_to_str = key_to_str 14 | } 15 | 16 | set (k: K, v: V) { 17 | let s = this.key_to_str (k) 18 | this.val_map.set (s, v) 19 | this.key_map.set (s, k) 20 | } 21 | 22 | has (k: K): boolean { 23 | let s = this.key_to_str (k) 24 | return this.val_map.has (s) 25 | } 26 | 27 | get (k: K): V { 28 | let s = this.key_to_str (k) 29 | let v = this.val_map.get (s) 30 | if (v === undefined) { 31 | throw new Error ("key no in dic") 32 | } 33 | return v 34 | } 35 | 36 | update_at (k: K, f: (v: V) => V): dic_t { 37 | let v = this.get (k) 38 | this.set (k, f (v)) 39 | return this 40 | } 41 | 42 | get size (): number { 43 | return this.val_map.size 44 | } 45 | 46 | empty_p (): boolean { 47 | return this.size === 0 48 | } 49 | 50 | to_array (): Array <[K, V]> { 51 | let array = new Array <[K, V]> () 52 | for (let [s, v] of this.val_map) { 53 | let k = this.key_map.get (s) as K 54 | array.push ([k, v]) 55 | } 56 | return array 57 | } 58 | 59 | merge_array (array: Array <[K, V]>): dic_t { 60 | for (let [k, v] of array) { 61 | this.set (k, v) 62 | } 63 | return this 64 | } 65 | 66 | *[Symbol.iterator] () { 67 | for (let [s, v] of this.val_map) { 68 | let k = this.key_map.get (s) as K 69 | yield [k, v] as [K, V] 70 | } 71 | } 72 | 73 | *entries () { 74 | for (let [s, v] of this.val_map) { 75 | let k = this.key_map.get (s) as K 76 | yield [k, v] as [K, V] 77 | } 78 | } 79 | 80 | *keys () { 81 | for (let k of this.key_map.values ()) { 82 | yield k as K 83 | } 84 | } 85 | 86 | *values () { 87 | for (let v of this.val_map.values ()) { 88 | yield v as V 89 | } 90 | } 91 | 92 | /** 93 | * Assuming `this.key_lt (that)`. 94 | */ 95 | *zip (that: dic_t ) { 96 | for (let k of this.keys ()) { 97 | let x = this.get (k) 98 | let y = that.get (k) 99 | yield [k, [x, y]] as [K, [V, V]] 100 | } 101 | } 102 | 103 | key_array (): Array { 104 | return new Array (...this.key_map.values ()) 105 | } 106 | 107 | value_array (): Array { 108 | return new Array (...this.val_map.values ()) 109 | } 110 | 111 | copy (): dic_t { 112 | let dic = new dic_t (this.key_to_str) 113 | dic.val_map = new Map (this.val_map) 114 | dic.key_map = new Map (this.key_map) 115 | return dic 116 | } 117 | 118 | key_eq (that: dic_t ): boolean { 119 | if (this.size !== that.size) { 120 | return false 121 | } 122 | for (let k of this.keys ()) { 123 | if (! that.has (k)) { 124 | return false 125 | } 126 | } 127 | return true 128 | } 129 | 130 | key_lt (that: dic_t ): boolean { 131 | if (! (this.size < that.size)) { 132 | return false 133 | } 134 | for (let k of this.keys ()) { 135 | if (! that.has (k)) { 136 | return false 137 | } 138 | } 139 | return true 140 | } 141 | 142 | key_lteq (that: dic_t ): boolean { 143 | if (! (this.size <= that.size)) { 144 | return false 145 | } 146 | for (let k of this.keys ()) { 147 | if (! that.has (k)) { 148 | return false 149 | } 150 | } 151 | return true 152 | } 153 | 154 | key_gt (that: dic_t ): boolean { 155 | return that.key_lt (this) 156 | } 157 | 158 | key_gteq (that: dic_t ): boolean { 159 | return that.key_lteq (this) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/graph-info.ts: -------------------------------------------------------------------------------- 1 | export type id_t = number 2 | 3 | export 4 | class vertex_t { 5 | constructor ( 6 | public id: id_t, 7 | public info: V, 8 | ) {} 9 | 10 | // - shallow copy 11 | copy (): vertex_t { 12 | return new vertex_t (this.id, this.info) 13 | } 14 | 15 | more_info (info: I): vertex_t { 16 | return new vertex_t (this.id, { 17 | ...this.info, 18 | ...info, 19 | }) 20 | } 21 | } 22 | 23 | export 24 | class edge_t { 25 | constructor ( 26 | public id: id_t, 27 | public start: id_t, 28 | public end: id_t, 29 | public info: E, 30 | ) {} 31 | 32 | // - shallow copy 33 | copy (): edge_t { 34 | return new edge_t (this.id, this.start, this.end, this.info) 35 | } 36 | 37 | more_info (info: I): edge_t { 38 | return new edge_t (this.id, this.start, this.end, { 39 | ...this.info, 40 | ...info, 41 | }) 42 | } 43 | } 44 | 45 | export 46 | class graph_t { 47 | // simple graph representation 48 | // - in the following "O" notation V and E denote sizes 49 | 50 | // - space ~ O (V + E) 51 | constructor ( 52 | public vertex_map: Map > = new Map (), 53 | public edge_map: Map > = new Map (), 54 | ) {} 55 | 56 | // self-builder 57 | // - side-effect on existing object under the same id 58 | 59 | vertex ( 60 | v: id_t, 61 | info: V, 62 | ): graph_t { 63 | if (this.vertex_map.has (v)) throw new Error ( 64 | `[error] graph_t.vertex fail` 65 | ) 66 | else this.vertex_map.set (v, new vertex_t (v, info)) 67 | return this 68 | } 69 | 70 | edge ( 71 | e: id_t, 72 | start: id_t, 73 | end: id_t, 74 | info: E, 75 | ): graph_t { 76 | if (this.edge_map.has (e)) throw new Error ( 77 | `[error] graph_t.edge fail` 78 | ) 79 | else this.edge_map.set (e, new edge_t (e, start, end, info)) 80 | return this 81 | } 82 | 83 | // - shallow copy 84 | // - time ~ O (V + E) 85 | copy (): graph_t { 86 | let graph: graph_t = new graph_t () 87 | this.vertex_map.forEach ((vertex, id) => { 88 | graph.vertex_map.set (id, vertex) 89 | }) 90 | this.edge_map.forEach ((edge, id) => { 91 | graph.edge_map.set (id, edge) 92 | }) 93 | return graph 94 | } 95 | 96 | // - time ~ O (E) 97 | // - with side-effect on the set 98 | connected_vertex_set (vertex_set: Set ): Set { 99 | for (let edge of this.edge_map.values ()) { 100 | if (vertex_set.has (edge.start)) 101 | vertex_set.add (edge.end) 102 | else if (vertex_set.has (edge.end)) 103 | vertex_set.add (edge.start) 104 | } 105 | return vertex_set 106 | } 107 | 108 | // - time ~ O (E) 109 | connected_vertex_set_of_vertex (v: id_t): Set { 110 | return this.connected_vertex_set (new Set ([v])) 111 | } 112 | 113 | // - time ~ O (E) 114 | connected_vertex_p (v1: id_t, v2: id_t): boolean { 115 | return this 116 | .connected_vertex_set_of_vertex (v1) 117 | .has (v2) 118 | } 119 | 120 | // - time ~ O (V + E) 121 | subgraph_of_vertex_set ( 122 | vertex_set: Set 123 | ): graph_t { 124 | let subgraph: graph_t = new graph_t () 125 | this.vertex_map.forEach ((vertex, id) => { 126 | if (vertex_set.has (id)) 127 | subgraph.vertex_map.set (id, vertex) 128 | }) 129 | this.edge_map.forEach ((edge, id) => { 130 | if (vertex_set.has (edge.start) || 131 | vertex_set.has (edge.end)) 132 | subgraph.edge_map.set (id, edge) 133 | }) 134 | return subgraph 135 | } 136 | 137 | // - connected component is max connected subgraph 138 | // - time ~ O (V + E) 139 | connected_component_of_vertex (v: id_t): graph_t { 140 | return this.subgraph_of_vertex_set ( 141 | this.connected_vertex_set_of_vertex (v)) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/int.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as eu from "./euclid" 5 | import { set_t } from "./abstract/set" 6 | 7 | export 8 | let ints = new set_t ({ 9 | eq: (x, y) => x === y 10 | }) 11 | 12 | export 13 | function abs (x: bigint) { 14 | return x < 0n ? -x : x 15 | } 16 | 17 | export 18 | function mod ( 19 | x: bigint, 20 | y: bigint, 21 | ): bigint { 22 | let r: bigint = x % y 23 | return r < 0 ? r + abs (y) : r 24 | } 25 | 26 | export 27 | function divmod ( 28 | x: bigint, 29 | y: bigint, 30 | ): [bigint, bigint] { 31 | let r: bigint = mod (x, y) 32 | let q: bigint = (x - r) / y 33 | return [q, r] 34 | } 35 | 36 | export 37 | function div ( 38 | x: bigint, 39 | y: bigint, 40 | ): bigint { 41 | let r: bigint = mod (x, y) 42 | return (x - r) / y 43 | } 44 | 45 | export 46 | function abs_lt (x: bigint, y: bigint): boolean { 47 | return abs (x) < abs (y) 48 | } 49 | 50 | export 51 | let ring = eu.ring ({ 52 | elements: ints, 53 | zero: 0n, 54 | add: (x: bigint, y: bigint) => x + y, 55 | neg: (x: bigint) => - x, 56 | one: 1n, 57 | mul: (x: bigint, y: bigint) => x * y, 58 | degree_lt: abs_lt, 59 | divmod: divmod, 60 | }) 61 | 62 | export 63 | class matrix_t extends eu.matrix_t { 64 | constructor (the: { 65 | buffer: Array , 66 | shape: [number, number], 67 | strides: [number, number], 68 | offset?: number, 69 | }) { 70 | super ({ 71 | ...the, 72 | ring, 73 | }) 74 | } 75 | 76 | copy (): matrix_t { 77 | return new matrix_t (super.copy ()) 78 | } 79 | 80 | static constant ( 81 | n: bigint, 82 | x: number, 83 | y: number, 84 | ): matrix_t { 85 | return new matrix_t ( 86 | eu.matrix_t.ring_constant (ring, n, x, y) 87 | ) 88 | } 89 | 90 | static zeros ( 91 | x: number, 92 | y: number, 93 | ): matrix_t { 94 | return new matrix_t ( 95 | eu.matrix_t.ring_zeros (ring, x, y) 96 | ) 97 | } 98 | 99 | static ones ( 100 | x: number, 101 | y: number, 102 | ): matrix_t { 103 | return new matrix_t ( 104 | eu.matrix_t.ring_ones (ring, x, y) 105 | ) 106 | } 107 | 108 | static id ( 109 | n: number, 110 | ): matrix_t { 111 | return new matrix_t ( 112 | eu.matrix_t.ring_id (ring, n) 113 | ) 114 | } 115 | 116 | transpose (): matrix_t { 117 | return new matrix_t ( 118 | super.transpose () 119 | ) 120 | } 121 | } 122 | 123 | export 124 | function matrix ( 125 | array: eu.Array2d 126 | ): matrix_t { 127 | let new_array = new Array > () 128 | for (let row of array) { 129 | let new_row = new Array () 130 | for (let x of row) { 131 | new_row.push (BigInt (x)) 132 | } 133 | new_array.push (new_row) 134 | } 135 | return new matrix_t ( 136 | eu.matrix_t.from_ring_Array2d (ring, new_array) 137 | ) 138 | } 139 | 140 | export 141 | class vector_t extends eu.vector_t { 142 | constructor (the: { 143 | buffer: Array , 144 | shape: [number], 145 | strides: [number], 146 | offset?: number, 147 | }) { 148 | super ({ 149 | ...the, 150 | ring, 151 | }) 152 | } 153 | 154 | copy (): vector_t { 155 | return new vector_t (super.copy ()) 156 | } 157 | 158 | static constant ( 159 | n: bigint, 160 | size: number, 161 | ): vector_t { 162 | return new vector_t ( 163 | eu.vector_t.ring_constant (ring, n, size) 164 | ) 165 | } 166 | 167 | static zeros ( 168 | size: number, 169 | ): vector_t { 170 | return new vector_t ( 171 | eu.vector_t.ring_zeros (ring, size) 172 | ) 173 | } 174 | 175 | static ones ( 176 | size: number, 177 | ): vector_t { 178 | return new vector_t ( 179 | eu.vector_t.ring_ones (ring, size) 180 | ) 181 | } 182 | } 183 | 184 | export 185 | function vector ( 186 | array: eu.Array1d 187 | ): vector_t { 188 | return new vector_t ( 189 | eu.vector_t.from_ring_Array (ring, array.map (BigInt)) 190 | ) 191 | } 192 | -------------------------------------------------------------------------------- /src/abstract/category.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import { set_t, eqv, not_eqv } from "./set" 4 | 5 | import { group_t } from "./group" 6 | 7 | // TODO 8 | // the following definition is not useful. 9 | // because we can not define the category_t of all group_t 10 | // because the objects should be 11 | // the set of all `group_t` 12 | // instead of the set of `group_t ` for some the `G` 13 | 14 | export 15 | class category_t { 16 | readonly objects: set_t 17 | readonly arrows: set_t 18 | readonly dom: (f: A) => O 19 | readonly cod: (f: A) => O 20 | readonly id: (x: O) => A 21 | readonly compose: (f: A, g: A) => A 22 | 23 | constructor (the: { 24 | objects: set_t , 25 | arrows: set_t , 26 | dom: (f: A) => O, 27 | cod: (f: A) => O, 28 | id: (x: O) => A, 29 | compose: (f: A, g: A) => A, 30 | }) { 31 | this.objects = the.objects 32 | this.arrows = the.arrows 33 | this.dom = the.dom 34 | this.cod = the.cod 35 | this.id = the.id 36 | this.compose = (f, g) => { 37 | eqv (this.objects, this.cod (f), this.dom (g)) 38 | return the.compose (f, g) 39 | } 40 | } 41 | 42 | id_left (f: A) { 43 | eqv ( 44 | this.arrows, 45 | this.compose (this.id (this.dom (f)), f), 46 | f, 47 | ) 48 | } 49 | 50 | id_right (f: A) { 51 | eqv ( 52 | this.arrows, 53 | this.compose (f, this.id (this.cod (f))), 54 | f, 55 | ) 56 | } 57 | 58 | assoc (f: A, g: A, h: A) { 59 | eqv ( 60 | this.arrows, 61 | this.compose (this.compose (f, g), h), 62 | this.compose (f, this.compose (g, h)), 63 | ) 64 | } 65 | } 66 | 67 | export 68 | class functor_t { 69 | readonly lcat: category_t 70 | readonly rcat: category_t 71 | readonly map: (x: LO) => RO 72 | readonly fmap: (f: LA) => RA 73 | 74 | constructor (the: { 75 | lcat: category_t , 76 | rcat: category_t , 77 | map: (x: LO) => RO, 78 | fmap: (f: LA) => RA, 79 | }) { 80 | this.lcat = the.lcat 81 | this.rcat = the.rcat 82 | this.map = the.map 83 | this.fmap = (f) => { 84 | let g = the.fmap (f) 85 | eqv ( 86 | the.rcat.objects, 87 | the.map (the.lcat.dom (f)), 88 | the.rcat.dom (g), 89 | ) 90 | eqv ( 91 | the.rcat.objects, 92 | the.map (the.lcat.cod (f)), 93 | the.rcat.cod (g), 94 | ) 95 | return g 96 | } 97 | } 98 | 99 | fmap_respect_compose (f: LA, g: LA) { 100 | eqv ( 101 | this.rcat.arrows, 102 | this.fmap (this.lcat.compose (f, g)), 103 | this.rcat.compose (this.fmap (f), this.fmap (g)), 104 | ) 105 | } 106 | 107 | fmap_respect_id (x: LO) { 108 | eqv ( 109 | this.rcat.arrows, 110 | this.fmap (this.lcat.id (x)), 111 | this.rcat.id (this.map (x)), 112 | ) 113 | } 114 | } 115 | 116 | export 117 | class natural_transformation_t { 118 | readonly lcat: category_t 119 | readonly rcat: category_t 120 | readonly lfun: functor_t 121 | readonly rfun: functor_t 122 | readonly component: (x: LO) => RA 123 | 124 | constructor (the: { 125 | lfun: functor_t , 126 | rfun: functor_t , 127 | component: (x: LO) => RA, 128 | }) { 129 | // note the use of === here 130 | assert (the.lfun.lcat === the.rfun.lcat) 131 | assert (the.lfun.rcat === the.rfun.rcat) 132 | this.lcat = the.lfun.lcat 133 | this.rcat = the.lfun.rcat 134 | this.lfun = the.lfun 135 | this.rfun = the.rfun 136 | this.component = (x) => { 137 | let c = the.component (x) 138 | eqv ( 139 | this.rcat.objects, 140 | this.rcat.dom (c), 141 | this.lfun.map (x), 142 | ) 143 | eqv ( 144 | this.rcat.objects, 145 | this.rcat.cod (c), 146 | this.rfun.map (x), 147 | ) 148 | return c 149 | } 150 | } 151 | 152 | natural (f: LA) { 153 | let a = this.lcat.dom (f) 154 | let b = this.lcat.cod (f) 155 | eqv ( 156 | this.rcat.arrows, 157 | this.rcat.compose ( 158 | this.component (a), 159 | this.rfun.fmap (f), 160 | ), 161 | this.rcat.compose ( 162 | this.lfun.fmap (f), 163 | this.component (b), 164 | ), 165 | ) 166 | } 167 | } 168 | 169 | export 170 | class groupoid_t extends category_t { 171 | readonly inv: (f: A) => A 172 | 173 | constructor (the: { 174 | objects: set_t , 175 | arrows: set_t , 176 | dom: (f: A) => O, 177 | cod: (f: A) => O, 178 | id: (x: O) => A, 179 | compose: (f: A, g: A) => A, 180 | inv: (f: A) => A, 181 | }) { 182 | super (the) 183 | this.inv = the.inv 184 | } 185 | 186 | // `dom` and `cod` of composition are only checked at runtime 187 | // if the `eqv` can report report instead of throw error 188 | // maybe we can use it as semantics of type system 189 | arrow_iso (f: A) { 190 | eqv ( 191 | this.arrows, 192 | this.compose (f, this.inv (f)), 193 | this.id (this.dom (f)), 194 | ) 195 | 196 | eqv ( 197 | this.arrows, 198 | this.compose (this.inv (f), f), 199 | this.id (this.cod (f)), 200 | ) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/abstract/ring.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import { set_t, eqv, not_eqv } from "./set" 4 | import { abelian_group_t, monoid_t } from "./group" 5 | 6 | export 7 | class ring_t { 8 | elements: set_t 9 | addition: abelian_group_t 10 | multiplication: monoid_t 11 | 12 | constructor (the: { 13 | addition: abelian_group_t , 14 | multiplication: monoid_t , 15 | }) { 16 | this.addition = the.addition 17 | this.multiplication = the.multiplication 18 | this.elements = the.addition.elements 19 | } 20 | 21 | get zero (): R { return this.addition.id } 22 | add (x: R, y: R): R { return this.addition.add (x, y) } 23 | neg (x: R): R { return this.addition.neg (x) } 24 | sub (x: R, y: R): R { return this.addition.sub (x, y) } 25 | 26 | get one (): R { return this.multiplication.id } 27 | mul (x: R, y: R): R { return this.multiplication.mul (x, y) } 28 | 29 | eq (x: R, y: R): boolean { return this.elements.eq (x, y) } 30 | 31 | zero_p (x: R): boolean { 32 | return this.eq (x, this.zero) 33 | } 34 | 35 | one_p (x: R): boolean { 36 | return this.eq (x, this.one) 37 | } 38 | 39 | left_distr (x: R, y: R, z: R) { 40 | eqv ( 41 | this.elements, 42 | this.mul (x, this.add (y, z)), 43 | this.add (this.mul (x, y), this.mul (x, z)), 44 | ) 45 | } 46 | 47 | right_distr (x: R, y: R, z: R) { 48 | eqv ( 49 | this.elements, 50 | this.mul (this.add (y, z), x), 51 | this.add (this.mul (y, x), this.mul (z, x)), 52 | ) 53 | } 54 | } 55 | 56 | export 57 | class commutative_ring_t extends ring_t { 58 | commu (x: R, y: R) { 59 | eqv ( 60 | this.elements, 61 | this.mul (x, y), 62 | this.mul (y, x), 63 | ) 64 | } 65 | 66 | distr (x: R, y: R, z: R) { 67 | this.left_distr (x, y, z) 68 | this.right_distr (x, y, z) 69 | } 70 | } 71 | 72 | export 73 | class integral_ring_t extends commutative_ring_t { 74 | nonzero_product (x: R, y: R) { 75 | not_eqv (this.elements, x, this.zero) 76 | not_eqv (this.elements, y, this.zero) 77 | not_eqv ( 78 | this.elements, 79 | this.mul (x, y), 80 | this.zero, 81 | ) 82 | } 83 | } 84 | 85 | export 86 | class euclidean_ring_t extends integral_ring_t { 87 | degree_lt: (x: R, y: R) => boolean 88 | divmod: (x: R, y: R) => [R, R] 89 | 90 | constructor (the: { 91 | addition: abelian_group_t , 92 | multiplication: monoid_t , 93 | degree_lt: (x: R, y: R) => boolean, 94 | divmod: (x: R, y: R) => [R, R], 95 | }) { 96 | super (the) 97 | this.degree_lt = the.degree_lt 98 | this.divmod = the.divmod 99 | } 100 | 101 | degree_lte (x: R, y: R): boolean { 102 | return this.eq (x, y) || this.degree_lt (x, y) 103 | } 104 | 105 | degree_gt (x: R, y: R): boolean { 106 | return this.degree_lt (y, x) 107 | } 108 | 109 | degree_gte (x: R, y: R): boolean { 110 | return this.eq (x, y) || this.degree_gt (x, y) 111 | } 112 | 113 | euclidean_divmod (x: R, y: R) { 114 | not_eqv (this.elements, y, this.zero) 115 | let [q, r] = this.divmod (x, y) 116 | eqv (this.elements, x, this.add (this.mul (y, q), r)) 117 | assert (this.eq (r, this.zero) || 118 | this.degree_lt (r, y)) 119 | } 120 | 121 | div (x: R, y: R): R { 122 | let [q, r] = this.divmod (x, y) 123 | return q 124 | } 125 | 126 | mod (x: R, y: R): R { 127 | let [q, r] = this.divmod (x, y) 128 | return r 129 | } 130 | 131 | gcd (x: R, y: R): R { 132 | while (! this.eq (y, this.zero)) { 133 | if (this.degree_lt (x, y)) { 134 | [x, y] = [y, x]; 135 | } else { 136 | let r = this.mod (x, y); 137 | [x, y] = [y, r]; 138 | } 139 | } 140 | return x 141 | } 142 | 143 | gcd_ext (x: R, y: R): [R, [R, R]] { 144 | let old_s = this.one 145 | let old_t = this.zero 146 | let old_r = x 147 | 148 | let s = this.zero 149 | let t = this.one 150 | let r = y 151 | 152 | while (! this.zero_p (r)) { 153 | if (this.degree_lt (old_r, r)) { 154 | [ 155 | s, t, r, 156 | old_s, old_t, old_r, 157 | ] = [ 158 | old_s, old_t, old_r, 159 | s, t, r, 160 | ] 161 | } else { 162 | let q = this.div (old_r, r); 163 | [old_r, r] = [r, this.sub (old_r, this.mul (q, r))]; 164 | [old_s, s] = [s, this.sub (old_s, this.mul (q, s))]; 165 | [old_t, t] = [t, this.sub (old_t, this.mul (q, t))]; 166 | } 167 | } 168 | 169 | return [old_r, [old_s, old_t]] 170 | } 171 | 172 | gcd_ext_p (x: R, y: R, res: [R, [R, R]]): boolean { 173 | let [d, [s, t]] = res 174 | return (this.eq (d, this.gcd (x, y)) && 175 | this.eq (d, this.add ( 176 | this.mul (s, x), 177 | this.mul (t, y)))) 178 | } 179 | } 180 | 181 | export 182 | class field_t extends integral_ring_t { 183 | inv: (x: R) => R 184 | 185 | constructor (the: { 186 | addition: abelian_group_t , 187 | multiplication: monoid_t , 188 | inv: (x: R) => R, 189 | }) { 190 | super (the) 191 | this.inv = (x) => { 192 | not_eqv (this.elements, x, this.zero) 193 | return the.inv (x) 194 | } 195 | } 196 | 197 | div (x: R, y: R): R { 198 | return this.mul (x, this.inv (y)) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/panel-data.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as nd from "../lib/ndarray" 5 | import * as pd from "../lib/panel-data" 6 | 7 | import { permutation_t } from "../lib/permutation" 8 | 9 | 10 | test ("new pd.data_t", t => { 11 | let axes = pd.axes ([ 12 | ["Alice", pd.axis (["silent", "betray"])], 13 | ["Bob", pd.axis (["silent", "betray"])], 14 | ["payoff", pd.axis (["Alice", "Bob"])], 15 | ]) 16 | 17 | let prisoner_s_dilemma = new pd.data_t ( 18 | pd.axes ([ 19 | ["Alice", pd.axis (["silent", "betray"])], 20 | ["Bob", pd.axis (["silent", "betray"])], 21 | ["payoff", pd.axis (["Alice", "Bob"])], 22 | ]), 23 | nd.array ([ 24 | [[-1, -1], [-3, -0]], 25 | [[-0, -3], [-2, -2]], 26 | ]), 27 | ) 28 | prisoner_s_dilemma.print () 29 | t.pass () 30 | }) 31 | 32 | test ("pd.data_t.proj", t => { 33 | let axes = pd.axes ([ 34 | ["Alice", pd.axis (["silent", "betray"])], 35 | ["Bob", pd.axis (["silent", "betray"])], 36 | ["payoff", pd.axis (["Alice", "Bob"])], 37 | ]) 38 | 39 | let prisoner_s_dilemma = new pd.data_t ( 40 | pd.axes ([ 41 | ["Alice", pd.axis (["silent", "betray"])], 42 | ["Bob", pd.axis (["silent", "betray"])], 43 | ["payoff", pd.axis (["Alice", "Bob"])], 44 | ]), 45 | nd.array ([ 46 | [[-1, -1], [-3, -0]], 47 | [[-0, -3], [-2, -2]], 48 | ]), 49 | ) 50 | 51 | prisoner_s_dilemma.proj (pd.index ([ 52 | ["Alice", "betray"], 53 | ])) .print () 54 | 55 | t.pass () 56 | }) 57 | 58 | test ("new pd.series_t", t => { 59 | let x = pd.series ( 60 | "abc", 61 | pd.axis (["a", "b", "c"]), 62 | nd.array ([1, 2, 3]), 63 | ) 64 | 65 | // x.print () 66 | 67 | t.pass () 68 | }) 69 | 70 | test ("pd.frame_t.from_rows", t => { 71 | let frame = pd.frame_t.from_rows ( 72 | "rows", "cols", [ 73 | pd.series ("row1", 74 | pd.axis (["col1", "col2"]), 75 | nd.array ([1, 2])), 76 | pd.series ("row2", 77 | pd.axis (["col1", "col2"]), 78 | nd.array ([3, 4])), 79 | ]) 80 | 81 | t.true ( 82 | frame.data.get (pd.index ([ 83 | ["rows", "row1"], 84 | ["cols", "col2"], 85 | ])) === 2 86 | ) 87 | 88 | t.true ( 89 | frame.data.get (pd.index ([ 90 | ["cols", "col2"], 91 | ["rows", "row1"], 92 | ])) === 2 93 | ) 94 | }) 95 | 96 | test ("pd.frame_t.from_cols", t => { 97 | let frame = pd.frame_t.from_cols ( 98 | "rows", "cols", [ 99 | pd.series ("col1", 100 | pd.axis (["row1", "row2"]), 101 | nd.array ([1, 3])), 102 | pd.series ("col2", 103 | pd.axis (["row1", "row2"]), 104 | nd.array ([2, 4])), 105 | ]) 106 | 107 | t.true ( 108 | frame.data.get (pd.index ([ 109 | ["rows", "row1"], 110 | ["cols", "col2"], 111 | ])) === 2 112 | ) 113 | 114 | t.true ( 115 | frame.data.get (pd.index ([ 116 | ["cols", "col2"], 117 | ["rows", "row1"], 118 | ])) === 2 119 | ) 120 | }) 121 | 122 | test ("pd.frame_t.row & col", t => { 123 | let frame = pd.frame_t.from_rows ( 124 | "rows", "cols", [ 125 | pd.series ("row1", 126 | pd.axis (["col1", "col2"]), 127 | nd.array ([1, 2])), 128 | pd.series ("row2", 129 | pd.axis (["col1", "col2"]), 130 | nd.array ([3, 4])), 131 | ]) 132 | 133 | t.true ( 134 | frame.row ("row1") .get ("col1") === 1 135 | ) 136 | 137 | t.true ( 138 | frame.row ("row1") .get ("col2") === 2 139 | ) 140 | 141 | t.true ( 142 | frame.row ("row1") .get ("col1") === 143 | frame.col ("col1") .get ("row1") 144 | ) 145 | 146 | t.true ( 147 | frame.row ("row1") .get ("col2") === 148 | frame.col ("col2") .get ("row1") 149 | ) 150 | 151 | t.pass () 152 | }) 153 | 154 | test ("pd.frame_t *row & *col", t => { 155 | let frame = pd.frame_t.from_rows ( 156 | "rows", "cols", [ 157 | pd.series ("row1", 158 | pd.axis (["col1", "col2"]), 159 | nd.array ([1, 2])), 160 | pd.series ("row2", 161 | pd.axis (["col1", "col2"]), 162 | nd.array ([3, 4])), 163 | ]) 164 | 165 | for (let row of frame.rows ()) { 166 | // row.print () 167 | } 168 | 169 | for (let col of frame.cols ()) { 170 | // col.print () 171 | } 172 | 173 | t.pass () 174 | }) 175 | 176 | test ("pd.frame_t.print", t => { 177 | { 178 | let frame = pd.frame_t.from_rows ( 179 | "rows", "cols", [ 180 | pd.series ("row1", 181 | pd.axis (["col1", "col2"]), 182 | nd.array ([1, 2])), 183 | pd.series ("row2", 184 | pd.axis (["col1", "col2"]), 185 | nd.array ([3, 4])), 186 | ]) 187 | 188 | // frame.print () 189 | } 190 | 191 | { 192 | let frame = pd.frame_t.from_cols ( 193 | "rows", "cols", [ 194 | pd.series ("col1", 195 | pd.axis (["row1", "row2"]), 196 | nd.array ([1, 3])), 197 | pd.series ("col2", 198 | pd.axis (["row1", "row2"]), 199 | nd.array ([2, 4])), 200 | ]) 201 | 202 | // frame.print () 203 | } 204 | 205 | t.pass () 206 | }) 207 | 208 | test ("pd.frame_t.add", t => { 209 | let frame = pd.frame_t.from_rows ( 210 | "rows", "cols", [ 211 | pd.series ("row1", 212 | pd.axis (["col1", "col2"]), 213 | nd.array ([1, 2])), 214 | pd.series ("row2", 215 | pd.axis (["col1", "col2"]), 216 | nd.array ([3, 4])), 217 | ]) 218 | 219 | frame.add (frame) 220 | // .print () 221 | 222 | t.pass () 223 | }) 224 | 225 | test ("pd.frame_t.mul", t => { 226 | let x = pd.frame ( 227 | "rows", pd.axis (["row1", "row2"]), 228 | "cols", pd.axis (["col1", "col2"]), 229 | nd.array ([ 230 | [0, 1], 231 | [0, 0], 232 | ])) 233 | 234 | let y = pd.frame ( 235 | "rows", pd.axis (["row1", "row2"]), 236 | "cols", pd.axis (["col1", "col2"]), 237 | nd.array ([ 238 | [0, 0], 239 | [1, 0], 240 | ])) 241 | 242 | // x.mul (y) .print () 243 | // y.mul (x) .print () 244 | 245 | t.pass () 246 | }) 247 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | ------ 2 | - classification of regular polytope 3 | - holding implements until finite element method and PDE 4 | - example usages of higher order incidence relation 5 | - graph.ts with geometry 6 | - geometry of convex polytope 7 | - example of give geometry to topological structure 8 | - how to describe edge and face so they are easy to draw ? 9 | - a generic way to specify geometry of cell-complex 10 | * graph 11 | - graph without info -- for new hackenbush 12 | - reuse cell_complex_t -- by `graph_t.as_cell_complex ()` 13 | - find cycles of graph by spanning tree's complement edges 14 | - homology of general graph 15 | * games -- for frontend jobs 16 | - hackenbush 17 | - dots-and-boxes 18 | - connect-four 19 | - go 20 | - hackenbush editor 21 | * algebra of cell-complex 22 | - check equality proof 23 | - the game of equality 24 | - 2-dim cell-complex can be expressed purely algebraicly 25 | thus classification can also be expressed algebraicly 26 | but, for 3-dim cell-complex 27 | we do not have the syntax of higher algebra 28 | * manifold classification 29 | - another manifold_check 30 | - any edge occur twice 31 | thus when glued 32 | we will have not boundary 33 | - condition on edge_figure_t 34 | is weaker then condition on vertex_figure_t 35 | - every dimension have its element_figure_t 36 | the higher dimension the weaker the condition 37 | - maybe we should use the word "shape" instead of "figure" 38 | because of the use of `figure_t` 39 | - but this is not enough 40 | because of pinch points 41 | - how should we call this kind of weaker manifold_check ? 42 | pinchfold_t ? 43 | instead of think of name for each dimension 44 | we should 45 | | manifold_t | weak_manifold_t (0) | 46 | | pinchfold_t | weak_manifold_t (1) | 47 | | | weak_manifold_t (2) | 48 | | | ... | 49 | pinchfold_t ? 50 | - 2-dim manifold classification -- zip of john conway 51 | - can the normalization algorithm works on more than manifold_t ? 52 | - normal forms 53 | - sphere: a a.rev 54 | - tori: 55 | a1 b1 a1.rev b1.rev 56 | a2 b2 a2.rev b2.rev 57 | ... 58 | an bn an.rev bn.rev 59 | - cross-caps (projective-plane): 60 | a1 a1 61 | a2 a2 62 | ... 63 | an an 64 | - note that 65 | two cross-caps is a klein_bottle 66 | - the normalization algorithm 67 | 1. merge faces at double occuring edge pairs 68 | while maintaining homeomorphic to disk 69 | - only double occuring pairs can be merged 70 | without losing information 71 | - this will reduce the cell-complex to the following state: 72 | - there are no double occuring edge pairs 73 | - or merging any more double occuring edge pairs 74 | will make it non homeomorphic to disk 75 | - information of non-disk-ness is encoded by edge pairs 76 | if we merge more the information of non-disk-ness 77 | will be lost 78 | 2. reduce vertexes to one vertex 79 | 3. make same-direction edges next to each other 80 | 4. make opposite-direction edges ??? 81 | 5. cross-cap + torus = three cross-caps 82 | - new im_dic_compatible_p 83 | - new manifold_check 84 | - new vertex_figure_t 85 | - update cell-complex 86 | * what knowledge is worth knowing ? 87 | - topological and geometrical modeling 88 | - mesh 89 | - polytopal-complex 90 | - blender 91 | - clifford algebra 92 | - physics simulation 93 | - differential equation & difference equation 94 | - finite element method -- PDE 95 | - direction field -- ODE 96 | * mesh 97 | - to give geometry to cell-complex, we can 98 | 1. generate mesh for cell-complex 99 | 2. use affine variety of algebraic geometry 100 | * polytope 101 | - fourier-motzkin elimination 102 | - simplify inequalities 103 | - double description method 104 | * num 105 | - projection matrix for 1-dim subspace 106 | - rank one matrix 107 | - P.mul (P) .eq (P) 108 | - P.transpose () .eq (P) 109 | - projection matrix for m-dim subspace 110 | - subspace represented by A 111 | - columns are column vectors of the subspace 112 | - P = A.mul (A.transpose () .mul (A) .inv ()) .mul (A.transpose ()) 113 | - P.mul (P) .eq (P) 114 | - P.transpose () .eq (P) 115 | - normal equation 116 | - gram -- only gram 117 | - gram-schmidt -- with normalization 118 | - is there a version of gram-schmidt for integer matrix ? 119 | - num.matrix_t.positive_definite_p () 120 | - abstract/order.ts -- for num.ts, for polytope.ts 121 | - use num.ts to re-imp hackenbush 122 | * mathematical structures 123 | - ring.cs substructure and ideal_t 124 | - order.ts -- lattice_t, poset_t, total_t, heyting_algebra_t 125 | * int 126 | - linear diophantine equations with mod -- finite field 127 | * euclid 128 | - .diag => .main_diag 129 | - .diag .set_diag 130 | - convert invariant_factors to elementary_divisors 131 | - primary_decomposition -- [rank, [[p0, n0], [p1, n1], ...]] 132 | - chinese_remainder_theorem 133 | * computational-science 134 | - stiffness matrix 135 | - circulant matrix 136 | * polynomial 137 | - polynomial.ts -- symbolic algebra 138 | * panel-data 139 | - frame_t.act & series_t.trans 140 | - data_t slice 141 | * optimize 142 | - optimize frame_t and series_t by not using data_t 143 | but to use matrix_t and vector_t 144 | * homology 145 | - what is the meaning of 1 torsion_coefficients ? 146 | * homotopy 147 | - presentation of groupoid is the same as 2-dim cell-complex 148 | - by which we can calculate homology group of groupoid 149 | - my first aim is to 150 | generalize this algebraic structure for 3-dim cell-complex 151 | - we also want to study group representation 152 | i.e. find matrix group iso to given group 153 | - groupoid of 2-dim cell-complex 154 | - `as_groupoid ()` 155 | - what is special about manifold's groupoid 156 | - glob_t 157 | - ht.chain_t 158 | - `.boundary ()` 159 | - `.as_group ()` -- formalize presentation of group 160 | - `.as_groupoid ()` -- presentation of groupoid with `ht.chain_t` 161 | - abelianization of `ht.chain_t` to get homology theory 162 | abelianization 时如何获得定向 ? 163 | - `.glue ()` 164 | 我们所要处理的代数结构中的元素是 ht.chain_t 165 | 这在于 166 | 元素是有类型的 (或者说是有边界的) 167 | 我们的代数结构类似於 groupoid 而不是 group 168 | 元素之间的复合不是简单地左右相乘 169 | 而是 沿着边界 glue 170 | - 我们可以从 presentation of a groupoid 入手 171 | 研究 groupoid 对 ht.chain_t 的需要 172 | 也就是说 173 | 1. 放宽对元素联通性的要求 174 | 2. 丰富 compose 为 glue 175 | - higher_groupoid_t 176 | * cell-complex 177 | - we can fully encode the information of cell-complex 178 | by cell-valued incidence matrixes, 179 | - we can specialize cells for each dimension, 180 | for examples: 181 | - +1,-1 (2-dim rotation) for [2-dim, 1-dim] incidence relation 182 | - 2-dim rotation for [3-dim, 2-dim] incidence relation 183 | - 3-dim rotation for [4-dim, 3-dim] incidence relation 184 | - how about adjacency matrix between higher order elements ? 185 | - bounfold_check 186 | - cell_check -- is im_dic_compatible_p enough ? 187 | - can we encode cell-complex by graph ? 188 | - what is "encode something by graph" ? 189 | with graph label ? 190 | - product_complex_t 191 | - quotient_complex_t -- self-gluing 192 | - vertex_figure_t -- 3 dim 193 | - pure_complex_t 194 | an n-dimensional complex is said to be pure 195 | if each k cell (k < n) is a face of at least one n-dimensional cell 196 | - boundary operator 197 | - the boundary of the boundary of a cell_complex_t should be zero 198 | even if the cell_complex_t is not a bounfold_t 199 | * polytopal-complex 200 | - like cell-complex 201 | but without self adjacency 202 | which simplifies the data structure 203 | - polytopal-complex can be used as basic data structure in meshing 204 | * geometry 205 | - quaternion 206 | - clifford-algebra 207 | * combinatorial-game 208 | - use go to test game tree searching 209 | - why the games of logic seem like one-player game ? 210 | - aristotle (lukasiewicz) -> de morgan -> peirce 211 | - martin-gardner 212 | - (paper) investigations into game semantics of logic 213 | - surreal -- the theory of surreal number 214 | - theory about two-player normal-ending game 215 | * dance 216 | - 3 circle dance 217 | - 4 circle dance 218 | -------------------------------------------------------------------------------- /src/num.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as eu from "./euclid" 5 | import { set_t } from "./abstract/set" 6 | 7 | export 8 | interface config_t { 9 | /** for almost degenerated matrix */ 10 | epsilon: number 11 | } 12 | 13 | export 14 | let config: config_t = { 15 | epsilon: 0.000000001 16 | } 17 | 18 | export 19 | function epsilon_p (x: number): boolean { 20 | return Math.abs (x) < config.epsilon 21 | } 22 | 23 | export 24 | function non_epsilon_p (x: number): boolean { 25 | return Math.abs (x) >= config.epsilon 26 | } 27 | 28 | export 29 | let nums = new set_t ({ 30 | eq: (x, y) => x === y 31 | }) 32 | 33 | export 34 | let ring = eu.ring ({ 35 | elements: nums, 36 | zero: 0, 37 | add: (x: number, y: number) => x + y, 38 | neg: (x: number) => - x, 39 | one: 1, 40 | mul: (x: number, y: number) => x * y, 41 | degree_lt: (x: number, y: number) => false, 42 | divmod: (x: number, y: number) => [x / y, 0], 43 | }) 44 | 45 | export 46 | function argmax ( 47 | lo: number, 48 | hi: number, 49 | f: (i: number) => number, 50 | ): number { 51 | let arg = lo 52 | let cur = f (lo) 53 | for (let i = lo; i < hi; i++) { 54 | let next = f (i) 55 | if (cur < next) { 56 | arg = i 57 | cur = next 58 | } 59 | } 60 | return arg 61 | } 62 | 63 | export 64 | class matrix_t extends eu.matrix_t { 65 | constructor (the: { 66 | buffer: Array , 67 | shape: [number, number], 68 | strides: [number, number], 69 | offset?: number, 70 | }) { 71 | super ({ 72 | ...the, 73 | ring, 74 | }) 75 | } 76 | 77 | copy (): matrix_t { 78 | return new matrix_t (super.copy ()) 79 | } 80 | 81 | static constant ( 82 | n: number, 83 | x: number, 84 | y: number, 85 | ): matrix_t { 86 | return new matrix_t ( 87 | eu.matrix_t.ring_constant (ring, n, x, y) 88 | ) 89 | } 90 | 91 | static zeros ( 92 | x: number, 93 | y: number, 94 | ): matrix_t { 95 | return new matrix_t ( 96 | eu.matrix_t.ring_zeros (ring, x, y) 97 | ) 98 | } 99 | 100 | static ones ( 101 | x: number, 102 | y: number, 103 | ): matrix_t { 104 | return new matrix_t ( 105 | eu.matrix_t.ring_ones (ring, x, y) 106 | ) 107 | } 108 | 109 | static id ( 110 | n: number, 111 | ): matrix_t { 112 | return new matrix_t ( 113 | eu.matrix_t.ring_id (ring, n) 114 | ) 115 | } 116 | 117 | transpose (): matrix_t { 118 | return new matrix_t ( 119 | super.transpose () 120 | ) 121 | } 122 | 123 | append_cols (that: matrix_t): matrix_t { 124 | return new matrix_t ( 125 | this.append_cols (that) 126 | ) 127 | } 128 | 129 | row_echelon_form (): matrix_t { 130 | let matrix = this.copy () 131 | let [m, n] = this.shape 132 | let h = 0 // init pivot row 133 | let k = 0 // init pivot column 134 | while (h < m && k < n) { 135 | // find the next pivot 136 | let piv = argmax (h, m, (i) => Math.abs (matrix.get (i, k))) 137 | if (epsilon_p (matrix.get (piv, k))) { 138 | // no pivot in this column, pass to next column 139 | k += 1 140 | } else { 141 | if (h !== piv) { 142 | matrix.update_swap_rows (h, piv) 143 | } 144 | // for all rows below pivot 145 | for (let i = h + 1; i < m; i++) { 146 | let f = matrix.get (i, k) / matrix.get (h, k) 147 | matrix.set (i, k, 0) 148 | // for all remaining elements in current row 149 | for (let j = k + 1; j < n; j++) { 150 | let v = matrix.get (i, j) - matrix.get (h, j) * f 151 | matrix.set (i, j, v) 152 | } 153 | } 154 | h += 1 155 | k += 1 156 | } 157 | } 158 | return matrix 159 | } 160 | 161 | /** 162 | * with all pivots equal to 1. 163 | */ 164 | unit_row_echelon_form (): matrix_t { 165 | let matrix = this.row_echelon_form () 166 | matrix.for_each_row_index ((row, i) => { 167 | let pivot = row.first (x => ! epsilon_p (x)) 168 | if (pivot !== null) { 169 | row.update_scale (1 / pivot) 170 | } 171 | }) 172 | return matrix 173 | } 174 | 175 | /** 176 | * unit row echelon form + back substitution 177 | 178 | * The reduced row echelon form of a matrix is unique 179 | * i.e. does not depend on the algorithm used to compute it. 180 | */ 181 | reduced_row_echelon_form (): matrix_t { 182 | let matrix = this.unit_row_echelon_form () 183 | for (let [i, row] of matrix.row_entries ()) { 184 | for (let [j, sub] of matrix.row_entries ()) { 185 | if (j > i) { 186 | let arg = sub.argfirst (x => x === 1) 187 | if (arg !== null) { 188 | let x = matrix.get (i, arg) 189 | if (! epsilon_p (x)) { 190 | row.update_add (sub.scale (-x)) 191 | } 192 | } 193 | } 194 | } 195 | } 196 | return matrix 197 | } 198 | 199 | /** 200 | * P * A = L * U, 201 | * `permu.mul (this) .eq (lower.mul (upper))`, 202 | * (singular matrixes allowed) 203 | */ 204 | lower_upper_decomposition (): { 205 | lower: matrix_t, 206 | upper: matrix_t, 207 | permu: matrix_t, 208 | inver: number, 209 | } { 210 | let matrix = this.copy () 211 | let [m, n] = this.shape 212 | assert (m === n) 213 | let record = matrix_t.zeros (m, n) 214 | let permu = matrix_t.id (n) 215 | let h = 0 // init pivot row 216 | let k = 0 // init pivot column 217 | let inver = 0 218 | while (h < m && k < n) { 219 | // find the next pivot 220 | let piv = argmax (h, m, (i) => Math.abs (matrix.get (i, k))) 221 | if (epsilon_p (matrix.get (piv, k))) { 222 | // no pivot in this column, pass to next column 223 | k += 1 224 | } else { 225 | if (h !== piv) { 226 | matrix.update_swap_rows (h, piv) 227 | record.update_swap_rows (h, piv) 228 | permu.update_swap_rows (h, piv) 229 | inver += 1 230 | } 231 | // for all rows below pivot 232 | for (let i = h + 1; i < m; i++) { 233 | let f = matrix.get (i, k) / matrix.get (h, k) 234 | matrix.set (i, k, 0) 235 | record.update_at (i, k, v => v + f) 236 | // for all remaining elements in current row 237 | for (let j = k + 1; j < n; j++) { 238 | let v = matrix.get (i, j) - matrix.get (h, j) * f 239 | matrix.set (i, j, v) 240 | } 241 | } 242 | h += 1 243 | k += 1 244 | } 245 | } 246 | return { 247 | lower: new matrix_t ( 248 | record.update_add (matrix_t.id (n)) 249 | ), 250 | upper: matrix, 251 | permu, 252 | inver, 253 | } 254 | } 255 | } 256 | 257 | export 258 | function matrix ( 259 | array: eu.Array2d 260 | ): matrix_t { 261 | let new_array = new Array > () 262 | for (let row of array) { 263 | let new_row = new Array () 264 | for (let x of row) { 265 | new_row.push (Number (x)) 266 | } 267 | new_array.push (new_row) 268 | } 269 | return new matrix_t ( 270 | eu.matrix_t.from_ring_Array2d (ring, new_array) 271 | ) 272 | } 273 | 274 | export 275 | class vector_t extends eu.vector_t { 276 | constructor (the: { 277 | buffer: Array , 278 | shape: [number], 279 | strides: [number], 280 | offset?: number, 281 | }) { 282 | super ({ 283 | ...the, 284 | ring, 285 | }) 286 | } 287 | 288 | copy (): vector_t { 289 | return new vector_t (super.copy ()) 290 | } 291 | 292 | static constant ( 293 | n: number, 294 | size: number, 295 | ): vector_t { 296 | return new vector_t ( 297 | eu.vector_t.ring_constant (ring, n, size) 298 | ) 299 | } 300 | 301 | static zeros ( 302 | size: number, 303 | ): vector_t { 304 | return new vector_t ( 305 | eu.vector_t.ring_zeros (ring, size) 306 | ) 307 | } 308 | 309 | static ones ( 310 | size: number, 311 | ): vector_t { 312 | return new vector_t ( 313 | eu.vector_t.ring_ones (ring, size) 314 | ) 315 | } 316 | } 317 | 318 | export 319 | function vector ( 320 | array: eu.Array1d 321 | ): vector_t { 322 | return new vector_t ( 323 | eu.vector_t.from_ring_Array (ring, array.map (Number)) 324 | ) 325 | } 326 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cell Complex 2 | 3 | ## Aims 4 | 5 | Libraries and tools for topological and geometric modeling. 6 | 7 | ## Docs 8 | 9 | - [A Recursive Combinatorial Description of cell-complex](https://inner.xieyuheng.now.sh/paper/a-recursive-combinatorial-description-of-cell-complex) 10 | - A paper about the definition of cell-complex in this project 11 | 12 | ## Modules 13 | 14 | - `npm install cell-complex` 15 | 16 | - [API Docs](https://cell-complex.xieyuheng.now.sh) 17 | 18 | - Try [examples](https://github.com/xieyuheng/cell-complex/tree/master/examples) 19 | 20 | - Contents: 21 | - [int](#int) 22 | - [num](#num) 23 | - [euclid](#eu-euclid) 24 | - [combinatorial-game](#cg-combinatorial-game) 25 | - [cell-complex](#cx-cell-complex) 26 | - [homology](#hl-homology) 27 | 28 | ### `int` int 29 | 30 | #### [`examples/int-module.js`](examples/int-module.js): 31 | 32 | ``` javascript 33 | let assert = require ("assert") .strict 34 | 35 | let ut = require ("cell-complex/lib/util") 36 | let int = require ("cell-complex/lib/int") 37 | 38 | { 39 | /** 40 | * generic `row_canonical_form` 41 | * i.e. `hermite_normal_form` for integers 42 | */ 43 | 44 | let A = int.matrix ([ 45 | [2, 3, 6, 2], 46 | [5, 6, 1, 6], 47 | [8, 3, 1, 1], 48 | ]) 49 | 50 | let B = int.matrix ([ 51 | [1, 0, -11, 2], 52 | [0, 3, 28, -2], 53 | [0, 0, 61, -13], 54 | ]) 55 | 56 | assert ( 57 | A.row_canonical_form () .eq (B) 58 | ) 59 | } 60 | 61 | { 62 | /** 63 | * generic `diag_canonical_form` 64 | * i.e. `smith_normal_form` for integers 65 | */ 66 | 67 | let A = int.matrix ([ 68 | [2, 4, 4], 69 | [-6, 6, 12], 70 | [10, -4, -16], 71 | ]) 72 | 73 | let S = int.matrix ([ 74 | [2, 0, 0], 75 | [0, 6, 0], 76 | [0, 0, -12], 77 | ]) 78 | 79 | assert ( 80 | A.diag_canonical_form () .eq (S) 81 | ) 82 | } 83 | 84 | { 85 | /** 86 | * solve linear diophantine equations 87 | */ 88 | 89 | let A = int.matrix ([ 90 | [1, 2, 3, 4, 5, 6, 7], 91 | [1, 0, 1, 0, 1, 0, 1], 92 | [2, 4, 5, 6, 1, 1, 1], 93 | [1, 4, 2, 5, 2, 0, 0], 94 | [0, 0, 1, 1, 2, 2, 3], 95 | ]) 96 | 97 | let b = int.vector ([ 98 | 28, 99 | 4, 100 | 20, 101 | 14, 102 | 9, 103 | ]) 104 | 105 | let solution = A.solve (b) 106 | 107 | if (solution !== null) { 108 | solution.print () 109 | 110 | assert ( 111 | A.act (solution) .eq (b) 112 | ) 113 | } 114 | } 115 | ``` 116 | 117 | ### `num` num 118 | 119 | - with config-able `epsilon` for numerical stability 120 | 121 | #### [`examples/num-linear-algebra.js`](examples/num-linear-algebra.js): 122 | 123 | ``` javascript 124 | let assert = require ("assert") .strict 125 | 126 | let ut = require ("cell-complex/lib/util") 127 | let num = require ("cell-complex/lib/num") 128 | 129 | { 130 | /** 131 | * `reduced_row_echelon_form` reduces pivots to one 132 | * while respecting `epsilon` for numerical stability 133 | */ 134 | 135 | let A = num.matrix ([ 136 | [1, 3, 1, 9], 137 | [1, 1, -1, 1], 138 | [3, 11, 5, 35], 139 | ]) 140 | 141 | let B = num.matrix ([ 142 | [1, 0, -2, -3], 143 | [0, 1, 1, 4], 144 | [0, 0, 0, 0], 145 | ]) 146 | 147 | A.reduced_row_echelon_form () .print () 148 | 149 | assert ( 150 | A.reduced_row_echelon_form () .eq (B) 151 | ) 152 | } 153 | ``` 154 | 155 | ### `eu` euclid 156 | 157 | - [module theory](https://en.wikipedia.org/wiki/Module_(mathematics)) over [euclidean ring](https://en.wikipedia.org/wiki/Euclidean_ring) 158 | - for generic matrix algorithms 159 | 160 | ### `cg` combinatorial-game 161 | 162 | - a game engine for n-player perfect information games 163 | - example games: 164 | - tic-tac-toe 165 | - hackenbush -- [demo](http://hackenbush.combinatorial-game.surge.sh/) 166 | 167 | ### `cx` cell-complex 168 | 169 | - cell-complex based low dimensional algebraic topology library 170 | 171 | ### `hl` homology 172 | 173 | - [cellular homology](https://en.wikipedia.org/wiki/Cellular_homology) of cell-complex 174 | 175 | #### [`examples/four-ways-to-glue-a-square.js`](examples/four-ways-to-glue-a-square.js): 176 | 177 | - The following pictures are made by Guy Inchbald, a.k.a. [Steelpillow](https://commons.wikimedia.org/wiki/User:Steelpillow) 178 | 179 | ![Flatsurfaces.svg](https://github.com/xieyuheng/image-link/blob/master/homology/Flatsurfaces.svg) 180 | 181 | ``` javascript 182 | let cx = require ("cell-complex/lib/cell-complex") 183 | let hl = require ("cell-complex/lib/homology") 184 | let ut = require ("cell-complex/lib/util") 185 | ``` 186 | 187 | ![Spherecycles1.svg](https://github.com/xieyuheng/image-link/blob/master/homology/Spherecycles1.svg) 188 | 189 | ``` javascript 190 | class sphere_t extends cx.cell_complex_t { 191 | constructor () { 192 | super ({ dim: 2 }) 193 | this.attach_vertexes (["south", "middle", "north"]) 194 | this.attach_edge ("south_long", ["south", "middle"]) 195 | this.attach_edge ("north_long", ["middle", "north"]) 196 | this.attach_face ("surf", [ 197 | "south_long", 198 | "north_long", 199 | ["north_long", "rev"], 200 | ["south_long", "rev"], 201 | ]) 202 | } 203 | } 204 | ``` 205 | 206 | ![Toruscycles1.svg](https://github.com/xieyuheng/image-link/blob/master/homology/Toruscycles1.svg) 207 | 208 | ``` javascript 209 | class torus_t extends cx.cell_complex_t { 210 | constructor () { 211 | super ({ dim: 2 }) 212 | this.attach_vertex ("origin") 213 | this.attach_edge ("toro", ["origin", "origin"]) 214 | this.attach_edge ("polo", ["origin", "origin"]) 215 | this.attach_face ("surf", [ 216 | "toro", 217 | "polo", 218 | ["toro", "rev"], 219 | ["polo", "rev"], 220 | ]) 221 | } 222 | } 223 | ``` 224 | 225 | ![Kleincycles1.svg](https://github.com/xieyuheng/image-link/blob/master/homology/Kleincycles1.svg) 226 | 227 | ``` javascript 228 | class klein_bottle_t extends cx.cell_complex_t { 229 | constructor () { 230 | super ({ dim: 2 }) 231 | this.attach_vertex ("origin") 232 | this.attach_edge ("toro", ["origin", "origin"]) 233 | this.attach_edge ("cross", ["origin", "origin"]) 234 | this.attach_face ("surf", [ 235 | "toro", 236 | "cross", 237 | ["toro", "rev"], 238 | "cross", 239 | ]) 240 | } 241 | } 242 | ``` 243 | 244 | ![Projectivecycles1.svg](https://github.com/xieyuheng/image-link/blob/master/homology/Projectivecycles1.svg) 245 | 246 | ``` javascript 247 | class projective_plane_t extends cx.cell_complex_t { 248 | constructor () { 249 | super ({ dim: 2 }) 250 | this.attach_vertexes (["start", "end"]) 251 | this.attach_edge ("left_rim", ["start", "end"]) 252 | this.attach_edge ("right_rim", ["end", "start"]) 253 | this.attach_face ("surf", [ 254 | "left_rim", "right_rim", 255 | "left_rim", "right_rim", 256 | ]) 257 | } 258 | } 259 | 260 | ``` 261 | 262 | - calculate [homology groups](https://en.wikipedia.org/wiki/Homology_(mathematics)): 263 | 264 | ``` javascript 265 | let report = { 266 | "sphere": hl.report (new sphere_t ()), 267 | "torus": hl.report (new torus_t ()), 268 | "klein_bottle": hl.report (new klein_bottle_t ()), 269 | "projective_plane": hl.report (new projective_plane_t ()), 270 | } 271 | 272 | ut.log (report) 273 | 274 | let expected_report = { 275 | sphere: 276 | { '0': { betti_number: 1, torsion_coefficients: [] }, 277 | '1': { betti_number: 0, torsion_coefficients: [] }, 278 | '2': { betti_number: 1, torsion_coefficients: [] }, 279 | euler_characteristic: 2 }, 280 | torus: 281 | { '0': { betti_number: 1, torsion_coefficients: [] }, 282 | '1': { betti_number: 2, torsion_coefficients: [] }, 283 | '2': { betti_number: 1, torsion_coefficients: [] }, 284 | euler_characteristic: 0 }, 285 | klein_bottle: 286 | { '0': { betti_number: 1, torsion_coefficients: [] }, 287 | '1': { betti_number: 1, torsion_coefficients: [ 2 ] }, 288 | '2': { betti_number: 0, torsion_coefficients: [] }, 289 | euler_characteristic: 0 }, 290 | projective_plane: 291 | { '0': { betti_number: 1, torsion_coefficients: [] }, 292 | '1': { betti_number: 0, torsion_coefficients: [ 2 ] }, 293 | '2': { betti_number: 0, torsion_coefficients: [] }, 294 | euler_characteristic: 1 } 295 | } 296 | ``` 297 | 298 | ## Community 299 | 300 | - We enforce C4 as collaboration protocol -- [The C4 RFC](https://rfc.zeromq.org/spec:42/C4) 301 | - [Style Guide](STYLE-GUIDE.md) -- observe the style of existing code and respect it 302 | - [Code of Conduct](CODE-OF-CONDUCT.md) 303 | - Source code -- [github](https://github.com/xieyuheng/cell-complex) 304 | - CI -- [travis-ci](https://travis-ci.org/xieyuheng/cell-complex) 305 | 306 | ## Contributing 307 | 308 | - Prepare: `npm install` 309 | - Compile: `npx tsc` 310 | - Compile and watch: `npx tsc --watch` 311 | - Run all tests: `npx ava` 312 | - Run specific test file: `npx ava -sv ` 313 | 314 | ## License 315 | 316 | - [GPLv3](LICENSE) 317 | -------------------------------------------------------------------------------- /web/mian-zhi.ts: -------------------------------------------------------------------------------- 1 | // 勉之 -- 免枝而有餘者勝 2 | 3 | import * as hackenbush from "../lib/games/hackenbush" 4 | 5 | import { 6 | id_t, vertex_t, edge_t, graph_t 7 | } from "cell-complex/lib/graph-info" 8 | 9 | import * as ng from "./ng" 10 | 11 | class point_t { 12 | constructor ( 13 | public x: number, 14 | public y: number, 15 | ) {} 16 | } 17 | 18 | type vertex_coord_t = point_t 19 | 20 | type edge_coord_t = point_t 21 | // control point of quadratic bézier curves 22 | 23 | function affine_combination ( 24 | points: Array , 25 | coefficients: Array , 26 | ): point_t { 27 | let p = new point_t (0, 0) 28 | for (let i = 0; i < points.length; i += 1) { 29 | let q = points[i] 30 | p.x += q.x * coefficients[i] 31 | p.y += q.y * coefficients[i] 32 | } 33 | return p 34 | } 35 | 36 | function bezier1 ( 37 | t: number, 38 | p0: point_t, 39 | p1: point_t, 40 | ): point_t { 41 | let s = 1 - t 42 | return affine_combination ([p0, p1], [s, t]) 43 | } 44 | 45 | function bezier2 ( 46 | t: number, 47 | p0: point_t, 48 | p1: point_t, 49 | p2: point_t, 50 | ): point_t { 51 | return bezier1 ( 52 | t, 53 | bezier1 (t, p0, p1), 54 | bezier1 (t, p1, p2)) 55 | } 56 | 57 | function bezier3 ( 58 | t: number, 59 | p0: point_t, 60 | p1: point_t, 61 | p2: point_t, 62 | p3: point_t, 63 | ): point_t { 64 | return bezier1 ( 65 | t, 66 | bezier2 (t, p0, p1, p2), 67 | bezier2 (t, p1, p2, p3)) 68 | } 69 | 70 | class geometry_t { 71 | constructor ( 72 | public width: number, 73 | public height: number, 74 | public origin: point_t = new point_t (0, 0), 75 | public vertex_coord_map: Map = new Map (), 76 | public edge_coord_map: Map = new Map (), 77 | ) {} 78 | } 79 | 80 | function point_distant ( 81 | p: point_t, 82 | q: point_t, 83 | ): number { 84 | return Math.sqrt ((p.x - q.x) ** 2 + (p.y - q.y) ** 2) 85 | } 86 | 87 | function point_on_curve_p ( 88 | p: point_t, 89 | curve_sample: Array , 90 | ): boolean { 91 | for (let q of curve_sample) { 92 | if (point_distant (p, q) <= 10) { 93 | return true 94 | } 95 | } 96 | return false 97 | } 98 | 99 | function uniform_sample ( 100 | start: number, 101 | end: number, 102 | step: number, 103 | ): Array { 104 | let array: Array = [] 105 | let x = start 106 | while (x < end) { 107 | array.push (x) 108 | x += step 109 | } 110 | return array 111 | } 112 | 113 | function blue_point_on_edge ( 114 | p: point_t, 115 | graph: graph_t <{}, { value: hackenbush.color_t }>, 116 | geometry: geometry_t, 117 | ): edge_t <{}, { value: hackenbush.color_t }> | null { 118 | for (let [_id, edge] of graph.edge_map) { 119 | let p0 = geometry.vertex_coord_map.get (edge.start) as point_t 120 | let p1 = geometry.vertex_coord_map.get (edge.end) as point_t 121 | let pc = geometry.edge_coord_map.get (edge.id) as point_t 122 | let curve_sample = uniform_sample (0, 1, 1/100) 123 | .map ((t) => bezier2 (t, p0, pc, p1)) 124 | if (((edge.info.value === "blue") || 125 | (edge.info.value === "green")) && 126 | point_on_curve_p (p, curve_sample)) { 127 | return edge 128 | } 129 | } 130 | return null 131 | } 132 | 133 | function geometrize ( 134 | width: number, 135 | height: number, 136 | graph: graph_t <{}, { value: hackenbush.color_t }>, 137 | ): geometry_t { 138 | let geometry = new geometry_t (width, height) 139 | 140 | let interval = (width * 0.9) / (graph.vertex_map.size - 1) 141 | let factor = (height * 0.9) / graph.edge_map.size 142 | 143 | let origin = new point_t (20, height / 2) 144 | geometry.origin = origin 145 | 146 | let id_to_point = (id: id_t): point_t => new point_t ( 147 | id * interval + origin.x, origin.y) 148 | 149 | graph.vertex_map.forEach ((vertex) => { 150 | let p = id_to_point (vertex.id) 151 | geometry.vertex_coord_map.set (vertex.id, p) 152 | }) 153 | 154 | graph.edge_map.forEach ((edge) => { 155 | let p0 = id_to_point (edge.start) 156 | let p1 = id_to_point (edge.end) 157 | let p = bezier1 (1/2, p0, p1) 158 | p.y = factor * edge.id 159 | // if (edge.id % 2 === 0) { 160 | // p.y = factor * edge.id 161 | // } else { 162 | // p.y = - factor * edge.id 163 | // } 164 | geometry.edge_coord_map.set (edge.id, p) 165 | }) 166 | 167 | return geometry 168 | } 169 | 170 | class state_t { 171 | mouse: point_t 172 | 173 | constructor ( 174 | public play: hackenbush.play_t, 175 | public geometry: geometry_t, 176 | ) { 177 | this.mouse = new point_t (0, 0) 178 | } 179 | } 180 | 181 | interface mousedown_t { 182 | kind: "mousedown", 183 | mouse: point_t, 184 | } 185 | 186 | interface mousemove_t { 187 | kind: "mousemove", 188 | mouse: point_t, 189 | } 190 | 191 | type event_t = mousedown_t | mousemove_t 192 | 193 | let color_map = { 194 | red: "hsla(0, 80%, 60%, 0.6)", 195 | blue: "hsla(210, 80%, 60%, 0.6)", 196 | green: "hsla(120, 80%, 60%, 0.6)", 197 | gray: "hsla(120, 0%, 60%, 0.8)", 198 | } 199 | 200 | let deep_color_map = { 201 | red: "hsla(0, 80%, 60%, 0.9)", 202 | blue: "hsla(210, 80%, 60%, 0.9)", 203 | green: "hsla(120, 80%, 60%, 0.9)", 204 | } 205 | 206 | class engine_t 207 | extends ng.engine_t { 208 | receive (): void { 209 | let event = this.event_queue.shift () 210 | 211 | if (event === undefined) { return } 212 | 213 | if (event.kind === "mousemove") { 214 | this.state.mouse.x = event.mouse.x 215 | this.state.mouse.y = event.mouse.y 216 | } else if (event.kind === "mousedown") { 217 | this.state.mouse.x = event.mouse.x 218 | this.state.mouse.y = event.mouse.y 219 | let play = this.state.play 220 | let geometry = this.state.geometry 221 | let s = play.last_state () 222 | let edge = blue_point_on_edge ( 223 | event.mouse, s.graph, geometry) 224 | if ((edge !== null) && 225 | (play.winner () === null)) { 226 | play.move ("blue", edge.id) 227 | let bot = hackenbush.random_bot 228 | let p = play.next_player () 229 | let ch = bot.next_choice (p, s) 230 | window.setTimeout (() => play.move (p, ch), 600) 231 | // if (play.winner () === null) {} 232 | // [todo] winner screen 233 | } 234 | } 235 | 236 | this.receive () 237 | } 238 | 239 | rander (): void { 240 | let width = this.state.geometry.width 241 | let height = this.state.geometry.height 242 | this.canvas.width = width 243 | this.canvas.height = height 244 | 245 | let mouse = this.state.mouse 246 | let x = mouse.x 247 | let y = mouse.y 248 | 249 | let play = this.state.play 250 | let s = play.last_state () 251 | 252 | let geometry = this.state.geometry 253 | let origin = geometry.origin 254 | 255 | let ctx = this.canvas.getContext ("2d") as CanvasRenderingContext2D 256 | 257 | ctx.save () 258 | 259 | ctx.clearRect (0, 0, width, height) 260 | 261 | ctx.beginPath () 262 | ctx.arc (origin.x, origin.y, 8, 0, Math.PI * 2, true) 263 | ctx.closePath () 264 | let winner = play.winner () 265 | if (winner === null) { 266 | ctx.fillStyle = color_map ["gray"] 267 | } else { 268 | ctx.fillStyle = color_map [winner] 269 | } 270 | ctx.fill () 271 | 272 | ctx.beginPath () 273 | ctx.arc (x, y, 6, 0, Math.PI * 2, true) 274 | ctx.closePath () 275 | ctx.fillStyle = color_map ["blue"] 276 | ctx.fill () 277 | 278 | let draw_edge = ( 279 | edge: edge_t <{}, { value: hackenbush.color_t }>, 280 | color_style: string, 281 | ) => { 282 | ctx.beginPath () 283 | let p0 = geometry.vertex_coord_map.get (edge.start) as point_t 284 | let p1 = geometry.vertex_coord_map.get (edge.end) as point_t 285 | let pc = geometry.edge_coord_map.get (edge.id) as point_t 286 | ctx.lineCap = "round" 287 | ctx.lineWidth = 5 288 | ctx.strokeStyle = color_style 289 | ctx.moveTo (p0.x, p0.y) 290 | ctx.quadraticCurveTo (pc.x, pc.y, p1.x, p1.y) 291 | ctx.stroke () 292 | } 293 | 294 | s.graph.edge_map.forEach ((edge) => { 295 | draw_edge (edge, color_map [edge.info.value]) 296 | }) 297 | 298 | let edge = blue_point_on_edge ( 299 | mouse, s.graph, geometry) 300 | 301 | if (edge !== null) { 302 | draw_edge (edge, deep_color_map [edge.info.value]) 303 | } 304 | 305 | ctx.restore () 306 | } 307 | 308 | ////// 309 | 310 | init (): void { 311 | let width = this.canvas.width 312 | let height = this.canvas.height 313 | let play = this.state.play 314 | let s = play.last_state () 315 | let geometry = geometrize ( 316 | width, height, play.init_state.graph) 317 | console.log (play.init_state.graph) 318 | console.log (geometry) 319 | } 320 | 321 | run (): void { 322 | this.receive () 323 | this.rander () 324 | window.requestAnimationFrame (() => this.run ()) 325 | } 326 | } 327 | 328 | let bush = new hackenbush.state_t () 329 | .blue (0, 1) 330 | .blue (0, 1) 331 | .blue (1, 2) 332 | .red (0, 2) 333 | .red (0, 1) 334 | .red (0, 1) 335 | .green (0, 1) 336 | 337 | let play = hackenbush.new_play (bush) 338 | 339 | let geometry = geometrize ( 340 | window.innerWidth, 341 | window.innerHeight, 342 | play.init_state.graph) 343 | 344 | let engine: engine_t = new engine_t ( 345 | document.getElementById ("canvas") as HTMLCanvasElement, 346 | new state_t (play, geometry)) 347 | 348 | function get_mouse ( 349 | canvas: HTMLCanvasElement, 350 | event: MouseEvent, 351 | ): point_t { 352 | let x, y 353 | let r = canvas.getBoundingClientRect () 354 | x = Math.round (event.clientX - r.left) 355 | y = Math.round (event.clientY - r.top) 356 | return new point_t (x, y) 357 | } 358 | 359 | export 360 | function init (): void { 361 | window.addEventListener ("resize", () => { 362 | engine.state.geometry = geometrize ( 363 | window.innerWidth, 364 | window.innerHeight, 365 | engine.state.play.init_state.graph) 366 | }) 367 | 368 | engine.canvas.addEventListener ("mousedown", (event) => { 369 | if (event.button !== 0) { return } 370 | engine.event_queue.push ({ 371 | kind: "mousedown", 372 | mouse: get_mouse (engine.canvas, event), 373 | }) 374 | }) 375 | 376 | engine.canvas.addEventListener ("mousemove", (event) => { 377 | engine.event_queue.push ({ 378 | kind: "mousemove", 379 | mouse: get_mouse (engine.canvas, event), 380 | }) 381 | }) 382 | 383 | engine.init () 384 | engine.run () 385 | } 386 | -------------------------------------------------------------------------------- /tests/ndarray.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as nd from "../lib/ndarray" 4 | import { permutation_t } from "../lib/permutation" 5 | import * as ut from "@cicadoidea/basic/lib/util" 6 | 7 | test ("shape_to_strides", t => { 8 | let shape = [2, 3, 4] 9 | let strides = nd.array_t.shape_to_strides (shape) 10 | t.deepEqual (strides, [12, 4, 1]) 11 | }) 12 | 13 | test ("new nd.array_t", t => { 14 | let shape = [3, 4] 15 | let strides = nd.array_t.shape_to_strides (shape) 16 | let buffer = new Float64Array (12) 17 | let x = new nd.array_t (buffer, shape, strides) 18 | t.true (x.get ([1, 2]) === 0) 19 | x.set ([1, 2], 666) 20 | t.true (x.get ([1, 2]) === 666) 21 | t.true (x.get ([0, 0]) === 0) 22 | 23 | t.throws (() => { 24 | let shape = [3, 4] 25 | let strides = nd.array_t.shape_to_strides (shape) 26 | let buffer = new Float64Array (11) 27 | let x = new nd.array_t (buffer, shape, strides) 28 | }) 29 | }) 30 | 31 | test ("from_1darray", t => { 32 | let x = nd.array_t.from_1darray ([0, 1, 2]) 33 | t.true (x.get ([0]) === 0) 34 | t.true (x.get ([1]) === 1) 35 | t.true (x.get ([2]) === 2) 36 | }) 37 | 38 | test ("from_2darray", t => { 39 | t.throws (() => { 40 | nd.array_t.from_2darray ([[0, 1, 2], [3, 4, 5, 6]]) 41 | }) 42 | 43 | let x = nd.array_t.from_2darray ([[0, 1, 2], [3, 4, 5]]) 44 | t.true (x.get ([0, 0]) === 0) 45 | t.true (x.get ([0, 1]) === 1) 46 | t.true (x.get ([0, 2]) === 2) 47 | t.true (x.get ([1, 0]) === 3) 48 | t.true (x.get ([1, 1]) === 4) 49 | t.true (x.get ([1, 2]) === 5) 50 | }) 51 | 52 | test ("from_3darray", t => { 53 | t.throws (() => { 54 | nd.array_t.from_3darray ([ 55 | [[0, 1, 2], [3, 4, 5]], 56 | [[5, 4, 3], [2, 1, 0, 0]], 57 | ]) 58 | }) 59 | 60 | let x = nd.array_t.from_3darray ([ 61 | [[0, 1, 2], [3, 4, 5]], 62 | [[5, 4, 3], [2, 1, 0]], 63 | ]) 64 | t.true (x.get ([0, 0, 0]) === 0) 65 | t.true (x.get ([0, 0, 1]) === 1) 66 | t.true (x.get ([0, 0, 2]) === 2) 67 | t.true (x.get ([0, 1, 0]) === 3) 68 | t.true (x.get ([0, 1, 1]) === 4) 69 | t.true (x.get ([0, 1, 2]) === 5) 70 | t.true (x.get ([1, 0, 0]) === 5) 71 | t.true (x.get ([1, 0, 1]) === 4) 72 | t.true (x.get ([1, 0, 2]) === 3) 73 | t.true (x.get ([1, 1, 0]) === 2) 74 | t.true (x.get ([1, 1, 1]) === 1) 75 | t.true (x.get ([1, 1, 2]) === 0) 76 | }) 77 | 78 | test ("from_buffer", t => { 79 | let x = nd.array_t.from_buffer ( 80 | Float64Array.from ([ 81 | 0, 1, 2, 3, 4, 5, 82 | 5, 4, 3, 2, 1, 0, 83 | ]), 84 | [2, 2, 3], 85 | ) 86 | t.true (x.get ([0, 0, 0]) === 0) 87 | t.true (x.get ([0, 0, 1]) === 1) 88 | t.true (x.get ([0, 0, 2]) === 2) 89 | t.true (x.get ([0, 1, 0]) === 3) 90 | t.true (x.get ([0, 1, 1]) === 4) 91 | t.true (x.get ([0, 1, 2]) === 5) 92 | t.true (x.get ([1, 0, 0]) === 5) 93 | t.true (x.get ([1, 0, 1]) === 4) 94 | t.true (x.get ([1, 0, 2]) === 3) 95 | t.true (x.get ([1, 1, 0]) === 2) 96 | t.true (x.get ([1, 1, 1]) === 1) 97 | t.true (x.get ([1, 1, 2]) === 0) 98 | }) 99 | 100 | test ("proj", t => { 101 | let y = nd.array_t.from_3darray ([ 102 | [[0, 1, 2], [3, 4, 5]], 103 | [[5, 4, 3], [2, 1, 0]], 104 | ]) 105 | 106 | t.throws (() => { 107 | y.proj ([0, null]) 108 | }) 109 | 110 | { 111 | let x = y.proj ([0, null, null]) 112 | t.true (x.get ([0, 0]) === 0) 113 | t.true (x.get ([0, 1]) === 1) 114 | t.true (x.get ([0, 2]) === 2) 115 | t.true (x.get ([1, 0]) === 3) 116 | t.true (x.get ([1, 1]) === 4) 117 | t.true (x.get ([1, 2]) === 5) 118 | } 119 | 120 | { 121 | let x = y.proj ([0, null, 0]) 122 | t.true (x.get ([0]) === 0) 123 | t.true (x.get ([1]) === 3) 124 | x = x.copy () 125 | t.true (x.get ([0]) === 0) 126 | t.true (x.get ([1]) === 3) 127 | } 128 | 129 | { 130 | let x = y.proj ([1, null, null]) 131 | t.true (x.get ([0, 0]) === 5) 132 | t.true (x.get ([0, 1]) === 4) 133 | t.true (x.get ([0, 2]) === 3) 134 | t.true (x.get ([1, 0]) === 2) 135 | t.true (x.get ([1, 1]) === 1) 136 | t.true (x.get ([1, 2]) === 0) 137 | } 138 | 139 | { 140 | let x = y.proj ([1, null, 2]) 141 | t.true (x.get ([0]) === 3) 142 | t.true (x.get ([1]) === 0) 143 | x = x.copy () 144 | t.true (x.get ([0]) === 3) 145 | t.true (x.get ([1]) === 0) 146 | } 147 | }) 148 | 149 | test ("slice", t => { 150 | let y = nd.array_t.from_3darray ([ 151 | [[0, 1, 2], [3, 4, 5]], 152 | [[5, 5, 0], [6, 6, 0]], 153 | [[5, 4, 3], [2, 1, 0]], 154 | ]) 155 | 156 | t.throws (() => { 157 | y.slice ([null, [0, 2]]) 158 | }) 159 | 160 | { 161 | let x = y 162 | .proj ([1, null, null]) 163 | .slice ([null, [0, 2]]) 164 | t.true (x.get ([0, 0]) === 5) 165 | t.true (x.get ([0, 1]) === 5) 166 | t.true (x.get ([1, 0]) === 6) 167 | t.true (x.get ([1, 1]) === 6) 168 | x = x.copy () 169 | t.true (x.get ([0, 0]) === 5) 170 | t.true (x.get ([0, 1]) === 5) 171 | t.true (x.get ([1, 0]) === 6) 172 | t.true (x.get ([1, 1]) === 6) 173 | } 174 | 175 | { 176 | let x = y 177 | .slice ([null, null, [0, 2]]) 178 | .proj ([1, null, null]) 179 | t.true (x.get ([0, 0]) === 5) 180 | t.true (x.get ([0, 1]) === 5) 181 | t.true (x.get ([1, 0]) === 6) 182 | t.true (x.get ([1, 1]) === 6) 183 | x = x.copy () 184 | t.true (x.get ([0, 0]) === 5) 185 | t.true (x.get ([0, 1]) === 5) 186 | t.true (x.get ([1, 0]) === 6) 187 | t.true (x.get ([1, 1]) === 6) 188 | } 189 | }) 190 | 191 | test ("put", t => { 192 | let y = nd.array_t.from_3darray ([ 193 | [[0, 1, 2], [3, 4, 5]], 194 | [[5, 5, 0], [6, 6, 0]], 195 | [[5, 4, 3], [2, 1, 0]], 196 | ]) 197 | 198 | { 199 | y.put ( 200 | [null, null, [0, 2]], 201 | nd.array_t.from_3darray ([ 202 | [[9, 9], [9, 9]], 203 | [[9, 9], [9, 9]], 204 | [[9, 9], [9, 9]], 205 | ])) 206 | let x = y 207 | .slice ([null, null, [0, 2]]) 208 | .proj ([1, null, null]) 209 | t.true (x.get ([0, 0]) === 9) 210 | t.true (x.get ([0, 1]) === 9) 211 | t.true (x.get ([1, 0]) === 9) 212 | t.true (x.get ([1, 1]) === 9) 213 | x = x.copy () 214 | t.true (x.get ([0, 0]) === 9) 215 | t.true (x.get ([0, 1]) === 9) 216 | t.true (x.get ([1, 0]) === 9) 217 | t.true (x.get ([1, 1]) === 9) 218 | } 219 | }) 220 | 221 | test ("constant", t => { 222 | { 223 | let x = nd.array_t.constant (6, [2, 2]) 224 | t.true (x.get ([0, 0]) === 6) 225 | t.true (x.get ([0, 1]) === 6) 226 | t.true (x.get ([1, 0]) === 6) 227 | t.true (x.get ([1, 1]) === 6) 228 | } 229 | 230 | { 231 | let x = nd.array_t.zeros ([2, 2]) 232 | t.true (x.get ([0, 0]) === 0) 233 | t.true (x.get ([0, 1]) === 0) 234 | t.true (x.get ([1, 0]) === 0) 235 | t.true (x.get ([1, 1]) === 0) 236 | } 237 | 238 | { 239 | let x = nd.array_t.ones ([2, 2]) 240 | t.true (x.get ([0, 0]) === 1) 241 | t.true (x.get ([0, 1]) === 1) 242 | t.true (x.get ([1, 0]) === 1) 243 | t.true (x.get ([1, 1]) === 1) 244 | } 245 | }) 246 | 247 | test ("fill", t => { 248 | let x = nd.array_t.constant (6, [2, 2]) 249 | t.true (x.get ([0, 0]) === 6) 250 | t.true (x.get ([0, 1]) === 6) 251 | t.true (x.get ([1, 0]) === 6) 252 | t.true (x.get ([1, 1]) === 6) 253 | x.fill (0) 254 | t.true (x.get ([0, 0]) === 0) 255 | t.true (x.get ([0, 1]) === 0) 256 | t.true (x.get ([1, 0]) === 0) 257 | t.true (x.get ([1, 1]) === 0) 258 | }) 259 | 260 | test ("print", t => { 261 | nd.array_t.zeros ([10]) .print () 262 | nd.array_t.zeros ([2, 3]) .print () 263 | nd.array_t.zeros ([2, 3, 4]) .print () 264 | nd.array_t.zeros ([2, 3, 4, 5]) .print () 265 | nd.array_t.zeros ([2, 3, 4, 5]) .print () 266 | t.pass () 267 | }) 268 | 269 | test ("map", t => { 270 | let x = nd.array_t.constant (3, [2, 2]) 271 | t.true (x.get ([0, 0]) === 3) 272 | t.true (x.get ([0, 1]) === 3) 273 | t.true (x.get ([1, 0]) === 3) 274 | t.true (x.get ([1, 1]) === 3) 275 | x = x.map (n => n + 3) 276 | t.true (x.get ([0, 0]) === 6) 277 | t.true (x.get ([0, 1]) === 6) 278 | t.true (x.get ([1, 0]) === 6) 279 | t.true (x.get ([1, 1]) === 6) 280 | }) 281 | 282 | test ("indexes", t => { 283 | let y = nd.array_t.from_3darray ([ 284 | [[0, 1, 2], [3, 4, 5]], 285 | [[5, 4, 3], [2, 1, 0]], 286 | ]) 287 | 288 | t.deepEqual (Array.from (y.indexes ()), [ 289 | [ 0, 0, 0 ], 290 | [ 0, 0, 1 ], 291 | [ 0, 0, 2 ], 292 | [ 0, 1, 0 ], 293 | [ 0, 1, 1 ], 294 | [ 0, 1, 2 ], 295 | [ 1, 0, 0 ], 296 | [ 1, 0, 1 ], 297 | [ 1, 0, 2 ], 298 | [ 1, 1, 0 ], 299 | [ 1, 1, 1 ], 300 | [ 1, 1, 2 ], 301 | ]) 302 | 303 | let x = y.proj ([0, null, null]) 304 | 305 | t.deepEqual (Array.from (x.indexes ()), [ 306 | [ 0, 0 ], 307 | [ 0, 1 ], 308 | [ 0, 2 ], 309 | [ 1, 0 ], 310 | [ 1, 1 ], 311 | [ 1, 2 ], 312 | ]) 313 | 314 | x = x.copy () 315 | 316 | t.deepEqual (Array.from (x.indexes ()), [ 317 | [ 0, 0 ], 318 | [ 0, 1 ], 319 | [ 0, 2 ], 320 | [ 1, 0 ], 321 | [ 1, 1 ], 322 | [ 1, 2 ], 323 | ]) 324 | }) 325 | 326 | test ("reshape", t => { 327 | let y = nd.array_t.from_3darray ([ 328 | [[0, 1, 2], [3, 4, 5]], 329 | [[5, 4, 3], [2, 1, 0]], 330 | ]) 331 | 332 | 333 | t.deepEqual ( 334 | y.reshape (new permutation_t ([1, 0, 2])) .shape, 335 | [2, 2, 3], 336 | ) 337 | 338 | let x = y.proj ([1, null, null]) 339 | 340 | t.deepEqual ( 341 | x.reshape (new permutation_t ([1, 0])) .shape, 342 | [3, 2], 343 | ) 344 | 345 | let z = nd.array_t.from_2darray ([ 346 | [5, 2], 347 | [4, 1], 348 | [3, 0], 349 | ]) 350 | 351 | t.true ( 352 | x.reshape (new permutation_t ([1, 0])) .eq (z) 353 | ) 354 | 355 | t.pass () 356 | }) 357 | 358 | test ("append", t => { 359 | let x = nd.array_t.from_2darray ([ 360 | [0, 1, 2], 361 | [3, 4, 5], 362 | ]) 363 | 364 | let y = nd.array_t.from_2darray ([ 365 | [0, 1, 2], 366 | [3, 4, 5], 367 | ]) 368 | 369 | t.true ( 370 | x.append (0, y) .eq (nd.array_t.from_2darray ([ 371 | [0, 1, 2], 372 | [3, 4, 5], 373 | [0, 1, 2], 374 | [3, 4, 5], 375 | ])) 376 | ) 377 | 378 | t.true ( 379 | x.append (1, y) .eq (nd.array_t.from_2darray ([ 380 | [0, 1, 2, 0, 1, 2], 381 | [3, 4, 5, 3, 4, 5], 382 | ])) 383 | ) 384 | 385 | t.pass () 386 | }) 387 | 388 | test ("permute", t => { 389 | let x = nd.array_t.from_3darray ([ 390 | [[0, 1, 2], [3, 4, 5]], 391 | [[5, 4, 3], [2, 1, 0]], 392 | ]) 393 | 394 | t.true ( 395 | x.permute ( 396 | 0, new permutation_t ([1, 0]) 397 | ) .eq (nd.array_t.from_3darray ([ 398 | [[5, 4, 3], [2, 1, 0]], 399 | [[0, 1, 2], [3, 4, 5]], 400 | ])) 401 | ) 402 | 403 | t.true ( 404 | x.permute ( 405 | 2, new permutation_t ([1, 0, 2]) 406 | ) .eq (nd.array_t.from_3darray ([ 407 | [[1, 0, 2], [4, 3, 5]], 408 | [[4, 5, 3], [1, 2, 0]], 409 | ])) 410 | ) 411 | 412 | t.pass () 413 | }) 414 | 415 | test ("from_lower_order", t => { 416 | let x = nd.array_t.from_lower_order ([ 417 | nd.array ([1, 2, 3]), 418 | nd.array ([4, 5, 6]), 419 | ]) 420 | 421 | let y = nd.array ([ 422 | [1, 2, 3], 423 | [4, 5, 6], 424 | ]) 425 | 426 | t.true (x.eq (y)) 427 | }) 428 | 429 | test ("nd.array_t.add", t => { 430 | let x = nd.array_t.ones ([2, 3]) 431 | let y = nd.array ([ 432 | [2, 2, 2], 433 | [2, 2, 2], 434 | ]) 435 | 436 | t.true (x.add (x) .eq (y)) 437 | }) 438 | -------------------------------------------------------------------------------- /tests/int.test.js: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | import * as ut from "@cicadoidea/basic/lib/util" 4 | import * as int from "../lib/int" 5 | 6 | test ("int.divmod", t => { 7 | t.deepEqual (int.divmod (BigInt (17), BigInt (4)), [BigInt (4), BigInt (1)]) 8 | t.deepEqual (int.divmod (BigInt (19), BigInt (30)), [BigInt (0), BigInt (19)]) 9 | t.deepEqual (int.divmod (BigInt (-17), BigInt (-10)), [BigInt (2), BigInt (3)]) 10 | t.deepEqual (int.divmod (BigInt (7), BigInt (1)), [BigInt (7), BigInt (0)]) 11 | }) 12 | 13 | test ("int.ring.gcd", t => { 14 | t.true (int.ring.gcd (BigInt (6), BigInt (7)) === BigInt (1)) 15 | t.true (int.ring.gcd (BigInt (1), BigInt (7)) === BigInt (1)) 16 | t.true (int.ring.gcd (BigInt (0), BigInt (7)) === BigInt (7)) 17 | t.true (int.ring.gcd (BigInt (6), BigInt (6)) === BigInt (6)) 18 | t.true (int.ring.gcd (BigInt (1071), BigInt (462)) === BigInt (21)) 19 | t.true (int.ring.gcd (BigInt (1071), BigInt (-462)) === BigInt (21)) 20 | t.true (int.ring.gcd (BigInt (-1071), BigInt (462)) === BigInt (21)) 21 | }) 22 | 23 | function test_gcd_ext (t, x, y) { 24 | let res = int.ring.gcd_ext (x, y) 25 | // ut.log ([x, y, res]) 26 | t.true (int.ring.gcd_ext_p (x, y, res)) 27 | } 28 | 29 | test ("int.ring.gcd_ext", t => { 30 | test_gcd_ext (t, BigInt (6), BigInt (7)) 31 | test_gcd_ext (t, BigInt (1), BigInt (7)) 32 | test_gcd_ext (t, BigInt (0), BigInt (7)) 33 | test_gcd_ext (t, BigInt (6), BigInt (6)) 34 | test_gcd_ext (t, BigInt (1071), BigInt (462)) 35 | test_gcd_ext (t, BigInt (-1071), -BigInt (462)) 36 | test_gcd_ext (t, BigInt (-1071), BigInt (462)) 37 | test_gcd_ext (t, BigInt (-1071), BigInt (0)) 38 | test_gcd_ext (t, BigInt (0), BigInt (123)) 39 | test_gcd_ext (t, BigInt (0), -BigInt (123)) 40 | }) 41 | 42 | test ("int.matrix", t => { 43 | let x = int.matrix ([ 44 | [1, 2, 4], 45 | [4, 5, 6], 46 | [7, 8, 9], 47 | ]) 48 | t.deepEqual (x.shape, [3, 3]) 49 | }) 50 | 51 | 52 | test ("int.vector", t => { 53 | let x = int.vector ([1, 2, 4]) 54 | t.deepEqual (x.size, 3) 55 | }) 56 | 57 | function test_row_canonical_form (t, A, E) { 58 | let U = A.row_canonical_form () 59 | if (! U.eq (E)) { 60 | console.log ("test_row_canonical_form fail") 61 | console.log ("A:"); A.print () 62 | console.log ("computed U:"); U.print () 63 | console.log ("expected E:"); E.print () 64 | t.fail () 65 | } 66 | t.pass () 67 | } 68 | 69 | test ("int.matrix_t.row_canonical_form", t => { 70 | test_row_canonical_form ( 71 | t, 72 | int.matrix ([ 73 | [2, 3, 6, 2], 74 | [5, 6, 1, 6], 75 | [8, 3, 1, 1], 76 | ]), 77 | int.matrix ([ 78 | [1, 0, -11, 2], 79 | [0, 3, 28, -2], 80 | [0, 0, 61, -13], 81 | ]), 82 | ) 83 | 84 | test_row_canonical_form ( 85 | t, 86 | int.matrix ([ 87 | [3, 3, 1, 4], 88 | [0, 1, 0, 0], 89 | [0, 0, 19, 16], 90 | [0, 0, 0, 3], 91 | ]), 92 | int.matrix ([ 93 | [3, 0, 1, 1], 94 | [0, 1, 0, 0], 95 | [0, 0, 19, 1], 96 | [0, 0, 0, 3], 97 | ]), 98 | ) 99 | 100 | test_row_canonical_form ( 101 | t, 102 | int.matrix ([ 103 | [9, -36, 30], 104 | [-36, 192, -180], 105 | [30, -180, 180], 106 | ]), 107 | int.matrix ([ 108 | [3, 0, -30], 109 | [0, 12, 0], 110 | [0, 0, -60], 111 | ]), 112 | ) 113 | 114 | test_row_canonical_form ( 115 | t, 116 | int.matrix ([ 117 | [0, 0, 5, 0, 1, 4], 118 | [0, 0, 0, -1, -4, 99], 119 | [0, 0, 0, 20, 19, 16], 120 | [0, 0, 0, 0, 2, 1], 121 | [0, 0, 0, 0, 0, 3], 122 | [0, 0, 0, 0, 0, 0], 123 | ]), 124 | int.matrix ([ 125 | [0, 0, 5, 0, 0, 2], 126 | [0, 0, 0, -1, 0, 2], 127 | [0, 0, 0, 0, 1, 2], 128 | [0, 0, 0, 0, 0, 3], 129 | [0, 0, 0, 0, 0, 0], 130 | [0, 0, 0, 0, 0, 0], 131 | ]), 132 | ) 133 | 134 | t.pass () 135 | }) 136 | 137 | function test_row_canonical_decomposition (t, m) { 138 | let { 139 | row_trans, 140 | row_canonical, 141 | } = m.row_canonical_decomposition () 142 | t.true ( 143 | row_trans.mul (m) .eq (row_canonical) 144 | ) 145 | } 146 | 147 | test ("int.matrix_t.row_canonical_decomposition", t => { 148 | test_row_canonical_decomposition (t, int.matrix ([ 149 | [2, 3, 6, 2], 150 | [5, 6, 1, 6], 151 | [8, 3, 1, 1], 152 | ])) 153 | 154 | test_row_canonical_decomposition (t, int.matrix ([ 155 | [3, 3, 1, 4], 156 | [0, 1, 0, 0], 157 | [0, 0, 19, 16], 158 | [0, 0, 0, 3], 159 | ])) 160 | 161 | test_row_canonical_decomposition (t, int.matrix ([ 162 | [9, -36, 30], 163 | [-36, 192, -180], 164 | [30, -180, 180], 165 | ])) 166 | 167 | test_row_canonical_decomposition (t, int.matrix ([ 168 | [0, 0, 5, 0, 1, 4], 169 | [0, 0, 0, -1, -4, 99], 170 | [0, 0, 0, 20, 19, 16], 171 | [0, 0, 0, 0, 2, 1], 172 | [0, 0, 0, 0, 0, 3], 173 | [0, 0, 0, 0, 0, 0], 174 | ])) 175 | 176 | t.pass () 177 | }) 178 | 179 | 180 | function test_diag_canonical_form (t, m) { 181 | t.true ( 182 | m.diag_canonical_form () .diag_canonical_form_p () 183 | ) 184 | t.pass () 185 | } 186 | 187 | let INTEGER_MATRICES = { 188 | // Examples taken from 189 | // "INTEGER MATRICES AND ABELIAN GROUPS", 190 | // by George Havas and Leon S. Sterling. 191 | R1: [ 192 | [2, 0, -1, 0, -1, 2, 1, -1, 2, -1, -1, 0, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 193 | [0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], 194 | [2, 1, -1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, -1, -1, -1, 0, -1, 0, 1, -1, 0, 0, 0, 0, 0, -1], 195 | [2, 0, -1, 2, -1, 0, -1, 1, 0, 0, 0, 0, -2, 2, 0, 0, 0, 0, 1, -2, 1, 1, -1, 1, -1, 0, 0], 196 | [1, -1, 1, 0, 0, 0, -1, 0, -1, 1, 0, 0, 0, 1, 1, -1, 0, 0, 0, 0, 2, -1, 0, 1, 2, 0, -1], 197 | [0, 0, 0, 2, -2, 1, 1, -2, -1, 1, 1, 0, 0, 0, -1, 0, 1, -1, 1, 0, 0, 0, 2, 0, 0, -1, 0], 198 | [0, 0, 0, 0, 1, 0, 1, -1, 1, -1, -1, -1, -1, -1, 0, 1, 1, 0, 0, 0, 0, -1, 0, -1, -1, 1, 0], 199 | [0, 0, 0, 1, -2, 0, 2, -2, 0, 1, 2, 0, 1, 0, 2, 0, -2, 1, 1, -1, -1, -1, 1, 1, -1, 0, 0], 200 | [2, 2, 1, -2, 1, 1, 0, 0, 0, 0, 1, 0, 1, -2, 0, 0, -1, -1, 0, 1, -2, 0, 0, -1, 0, 1, -2], 201 | [1, 1, 0, 0, -1, 2, -1, 0, 2, -1, -1, -1, -1, -1, 1, -1, 0, 0, 1, 0, 1, 0, -1, 1, 0, 0, -1], 202 | [1, -2, -1, 1, 1, 0, 1, -1, 0, 0, -2, -1, 0, 1, 0, 0, 1, 1, 1, -2, 1, -1, 0, 1, 0, 1, 1], 203 | [1, 0, 1, 1, -1, 0, -3, 1, 0, 0, 0, 0, -2, -1, 2, -2, 0, 0, 1, 1, 2, -1, -1, 1, 1, 0, -1], 204 | [0, 0, 0, 1, -2, 0, -1, 1, 1, 0, 1, 0, 0, 0, 2, 0, -2, 1, 1, -2, 0, 1, -1, 1, -1, 1, 0], 205 | [1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 206 | [3, 2, 0, 0, -2, 2, 0, 0, 1, 0, 1, 0, -1, -2, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, -1, -2], 207 | [1, 0, -1, 0, -1, 1, 0, 1, 1, 0, 0, 0, -2, -1, 1, 0, 0, 0, 1, -1, 1, -1, -1, 2, -1, 0, 0], 208 | [1, 1, -1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, -1, -1, -1, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, -1], 209 | [2, -1, 0, -1, 1, 0, 0, 0, 0, -1, -1, 0, -1, -1, 0, -1, 0, -1, 0, 1, 2, -1, -1, 0, 1, -1, 0], 210 | [1, 2, 1, -1, 0, 1, -1, 1, 1, 0, 1, 0, -2, -1, 1, 2, 0, -1, 1, 0, 2, 0, 0, -1, 1, 0, -2], 211 | [2, 1, 0, -1, 0, 1, 0, -1, -1, 0, 0, 0, -2, 0, -1, -1, 1, -1, 0, 2, 2, 0, 0, -1, 1, -2, -1], 212 | [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, -2, 0, 1, 0, -2, -1, 0, -1, 2, -2, 0, 1], 213 | [0, 0, -1, 0, -1, 1, 2, -2, 0, 0, -1, 0, -1, -1, 2, 0, 0, 2, 1, -2, 2, -2, 0, 2, 0, 0, 0], 214 | [1, 1, 0, 1, -2, 2, 2, -2, 2, -1, 0, 0, -2, -1, 0, 1, 1, -1, 0, 0, 1, 0, 2, -1, -1, -1, -1], 215 | [2, -1, 0, 0, 0, 0, 0, 0, 1, -1, 1, 0, -1, -1, -1, 0, 0, -3, 0, 1, 1, 0, 0, -1, 0, 0, 0], 216 | [1, 0, 0, 0, 0, -1, -2, -1, -3, 2, 1, 1, 0, 2, 1, -2, 0, 0, -1, 2, 1, 0, -1, -2, 2, 0, -1], 217 | [2, 0, 0, 1, -2, 0, 0, -1, -1, 0, 1, 1, -2, 2, 1, -1, -1, 0, -1, 1, 2, 0, 0, -1, 1, -1, -1], 218 | ] 219 | } 220 | 221 | test ("int.matrix_t.diag_canonical_form", t => { 222 | test_diag_canonical_form (t, int.matrix ([ 223 | [2, 4, 4], 224 | [-6, 6, 12], 225 | [10, -4, -16], 226 | ])) 227 | 228 | test_diag_canonical_form (t, int.matrix ([ 229 | [2, 3, 6, 2], 230 | [5, 6, 1, 6], 231 | [8, 3, 1, 1], 232 | ])) 233 | 234 | test_diag_canonical_form (t, int.matrix ([ 235 | [3, 3, 1, 4], 236 | [0, 1, 0, 0], 237 | [0, 0, 19, 16], 238 | [0, 0, 0, 3], 239 | ])) 240 | 241 | test_diag_canonical_form (t, int.matrix ([ 242 | [9, -36, 30], 243 | [-36, 192, -180], 244 | [30, -180, 180], 245 | ])) 246 | 247 | test_diag_canonical_form (t, int.matrix ([ 248 | [0, 0, 5, 0, 1, 4], 249 | [0, 0, 0, -1, -4, 99], 250 | [0, 0, 0, 20, 19, 16], 251 | [0, 0, 0, 0, 2, 1], 252 | [0, 0, 0, 0, 0, 3], 253 | [0, 0, 0, 0, 0, 0], 254 | ])) 255 | 256 | test_diag_canonical_form (t, int.matrix ([ 257 | [1, 0, 0], 258 | [0, 4, 0], 259 | [0, 0, 6], 260 | ])) 261 | 262 | test_diag_canonical_form (t, int.matrix ([ 263 | [0, 0, 0], 264 | [0, 0, 0], 265 | [0, 0, 0], 266 | ])) 267 | 268 | test_diag_canonical_form (t, int.matrix ([ 269 | [0, 0, 0], 270 | [0, 4, 3], 271 | [0, 1, 6], 272 | ])) 273 | 274 | test_diag_canonical_form (t, int.matrix ([ 275 | [0, 0, 0], 276 | [0, 4, 0], 277 | [0, 0, 6], 278 | ])) 279 | 280 | test_diag_canonical_form (t, int.matrix (INTEGER_MATRICES.R1)) 281 | 282 | t.pass () 283 | }) 284 | 285 | function test_diag_canonical_decomposition (t, m) { 286 | let { 287 | row_trans, 288 | col_trans, 289 | diag_canonical, 290 | } = m.diag_canonical_decomposition () 291 | 292 | let res = row_trans.mul (m) .mul (col_trans) .eq (diag_canonical) 293 | 294 | if (! res) { 295 | console.log ("test_diag_canonical_decomposition fail:") 296 | console.log ("row_trans:") 297 | row_trans.print () 298 | console.log ("col_trans:") 299 | col_trans.print () 300 | console.log ("row_trans.mul (m) .mul (col_trans):") 301 | row_trans.mul (m) .mul (col_trans) .print () 302 | console.log ("diag_canonical:") 303 | diag_canonical.print () 304 | } 305 | 306 | t.true (res) 307 | 308 | t.true ( 309 | diag_canonical.diag_canonical_form_p () 310 | ) 311 | } 312 | 313 | test ("int.matrix_t.diag_canonical_decomposition", t => { 314 | test_diag_canonical_decomposition (t, int.matrix ([ 315 | [2, 4, 4], 316 | [-6, 6, 12], 317 | [10, -4, -16], 318 | ])) 319 | 320 | test_diag_canonical_decomposition (t, int.matrix ([ 321 | [2, 3, 6, 2], 322 | [5, 6, 1, 6], 323 | [8, 3, 1, 1], 324 | ])) 325 | 326 | test_diag_canonical_decomposition (t, int.matrix ([ 327 | [3, 3, 1, 4], 328 | [0, 1, 0, 0], 329 | [0, 0, 19, 16], 330 | [0, 0, 0, 3], 331 | ])) 332 | 333 | test_diag_canonical_decomposition (t, int.matrix ([ 334 | [9, -36, 30], 335 | [-36, 192, -180], 336 | [30, -180, 180], 337 | ])) 338 | 339 | test_diag_canonical_decomposition (t, int.matrix ([ 340 | [0, 0, 5, 0, 1, 4], 341 | [0, 0, 0, -1, -4, 99], 342 | [0, 0, 0, 20, 19, 16], 343 | [0, 0, 0, 0, 2, 1], 344 | [0, 0, 0, 0, 0, 3], 345 | [0, 0, 0, 0, 0, 0], 346 | ])) 347 | 348 | test_diag_canonical_decomposition (t, int.matrix ([ 349 | [1, 0, 0], 350 | [0, 4, 0], 351 | [0, 0, 6], 352 | ])) 353 | 354 | test_diag_canonical_decomposition (t, int.matrix ([ 355 | [0, 0, 0], 356 | [0, 0, 0], 357 | [0, 0, 0], 358 | ])) 359 | 360 | test_diag_canonical_decomposition (t, int.matrix ([ 361 | [0, 0, 0], 362 | [0, 4, 3], 363 | [0, 1, 6], 364 | ])) 365 | 366 | test_diag_canonical_decomposition (t, int.matrix ([ 367 | [0, 0, 0], 368 | [0, 4, 0], 369 | [0, 0, 6], 370 | ])) 371 | 372 | test_diag_canonical_decomposition (t, int.matrix ( 373 | INTEGER_MATRICES.R1 374 | )) 375 | 376 | t.pass () 377 | }) 378 | 379 | function test_kernel (t, m) { 380 | let kernel = m.kernel () 381 | 382 | t.true ( 383 | m.mul (kernel) .zero_p () 384 | ) 385 | } 386 | 387 | test ("int.matrix_t.int_kernel", t => { 388 | let A = int.matrix ([ 389 | [1, 2, 3, 4, 5, 6, 7], 390 | [1, 0, 1, 0, 1, 0, 1], 391 | [2, 4, 5, 6, 1, 1, 1], 392 | [1, 4, 2, 5, 2, 0, 0], 393 | [0, 0, 1, 1, 2, 2, 3], 394 | ]) 395 | 396 | test_kernel (t, A) 397 | }) 398 | 399 | function test_solve (t, m, b) { 400 | let solution = m.solve (b) 401 | 402 | t.true ( 403 | m.act (solution) .eq (b) 404 | ) 405 | } 406 | 407 | function test_solve_non (t, m, b) { 408 | let solution = m.solve (b) 409 | 410 | t.true ( 411 | solution === null 412 | ) 413 | } 414 | 415 | test ("int.matrix_t.solve", t => { 416 | let A = int.matrix ([ 417 | [1, 2, 3, 4, 5, 6, 7], 418 | [1, 0, 1, 0, 1, 0, 1], 419 | [2, 4, 5, 6, 1, 1, 1], 420 | [1, 4, 2, 5, 2, 0, 0], 421 | [0, 0, 1, 1, 2, 2, 3], 422 | ]) 423 | 424 | let b = int.vector ([ 425 | 28, 426 | 4, 427 | 20, 428 | 14, 429 | 9, 430 | ]) 431 | 432 | test_solve (t, A, b) 433 | 434 | { 435 | let A = int.matrix ([ 436 | [1, 0], 437 | [0, 1], 438 | ]) 439 | 440 | let b = int.vector ([ 441 | 2, 442 | 2, 443 | ]) 444 | 445 | test_solve (t, A, b) 446 | 447 | t.true ( 448 | A.solve (b) .eq ( 449 | int.vector ([2, 2]) 450 | ) 451 | ) 452 | } 453 | 454 | { 455 | let A = int.matrix ([ 456 | [1, 1], 457 | [1, 1], 458 | [0, 1], 459 | [1, 0], 460 | ]) 461 | 462 | let b = int.vector ([ 463 | 0, 464 | 0, 465 | 1, 466 | -1, 467 | ]) 468 | 469 | test_solve (t, A, b) 470 | 471 | t.true ( 472 | A.act (int.vector ([-1, 1])) .eq (b) 473 | ) 474 | 475 | t.true ( 476 | A.solve (b) .eq ( 477 | int.vector ([-1, 1]) 478 | ) 479 | ) 480 | } 481 | 482 | t.pass () 483 | }) 484 | -------------------------------------------------------------------------------- /src/nbe/simple.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert" 2 | import * as ut from "@cicadoidea/basic/lib/util" 3 | 4 | /** 5 | * Normalization by Evaluation: 6 | * Dependent Types and Impredicativity 7 | * by Andreas Abel 8 | */ 9 | 10 | /** 11 | * Chapter 2: 12 | * Simple Types: From Evaluation to Normalization 13 | */ 14 | 15 | abstract class type_t { 16 | abstract eq (that: type_t): boolean 17 | } 18 | 19 | class nat_t extends type_t { 20 | eq (that: nat_t): boolean { 21 | return that instanceof nat_t 22 | } 23 | } 24 | 25 | class arrow_t extends type_t { 26 | arg_type: type_t 27 | ret_type: type_t 28 | 29 | constructor ( 30 | arg_type: type_t, 31 | ret_type: type_t, 32 | ) { 33 | super () 34 | this.arg_type = arg_type 35 | this.ret_type = ret_type 36 | } 37 | 38 | eq (that: nat_t): boolean { 39 | return that instanceof arrow_t 40 | && this.arg_type.eq (that.arg_type) 41 | && this.ret_type.eq (that.ret_type) 42 | } 43 | } 44 | 45 | class ctx_t { 46 | map: Map 47 | 48 | constructor ( 49 | map: Map 50 | ) { 51 | this.map = map 52 | } 53 | 54 | static empty_context (): ctx_t { 55 | return new ctx_t (new Map ()) 56 | } 57 | 58 | ext ( 59 | name: string, 60 | t: type_t, 61 | ): ctx_t { 62 | if (this.map.has (name)) { 63 | throw new Error ( 64 | `name: ${name} already exists in this context` 65 | ) 66 | } 67 | return new ctx_t ( 68 | new Map (this.map) .set (name, t) 69 | ) 70 | } 71 | } 72 | 73 | /** 74 | * Constant value of type `this.T`. 75 | */ 76 | abstract class const_t { 77 | abstract T: type_t 78 | abstract eq (that: const_t): boolean 79 | } 80 | 81 | class zero_t extends const_t { 82 | T: type_t 83 | 84 | constructor () { 85 | super () 86 | this.T = new nat_t () 87 | } 88 | 89 | eq (that: const_t): boolean { 90 | return that instanceof zero_t 91 | } 92 | } 93 | 94 | class suc_t extends const_t { 95 | T: type_t 96 | 97 | constructor () { 98 | super () 99 | this.T = new arrow_t ( 100 | new nat_t (), 101 | new nat_t (), 102 | ) 103 | } 104 | 105 | eq (that: const_t): boolean { 106 | return that instanceof suc_t 107 | } 108 | } 109 | 110 | /** 111 | * Primitive recursion into type `this.into_type`. 112 | */ 113 | class rec_t extends const_t { 114 | T: type_t 115 | into_type: type_t 116 | 117 | constructor (into_type: type_t) { 118 | super () 119 | this.into_type = into_type 120 | 121 | this.T = new arrow_t ( 122 | this.into_type, 123 | new arrow_t ( 124 | new arrow_t ( 125 | new nat_t (), 126 | new arrow_t ( 127 | this.into_type, 128 | this.into_type, 129 | ) 130 | ), 131 | new arrow_t ( 132 | new nat_t (), 133 | this.into_type, 134 | ), 135 | ), 136 | ) 137 | } 138 | 139 | eq (that: const_t): boolean { 140 | return that instanceof rec_t 141 | && this.into_type.eq (that.into_type) 142 | } 143 | } 144 | 145 | /** 146 | * Well-typed terms, in context. 147 | * - We need a class `term_t` 148 | * and a `well_typed` judgment method on `term_t`. 149 | * - `term_t` does not have a `T` field, 150 | * for the type of a term depends context. 151 | */ 152 | abstract class term_t { 153 | abstract well_typed_p (ctx: ctx_t, T: type_t): boolean 154 | abstract eq (that: term_t): boolean 155 | abstract normal_p (ctx: ctx_t, T: type_t): boolean 156 | abstract neutral_p (ctx: ctx_t, T: type_t): boolean 157 | } 158 | 159 | class constant_t extends term_t { 160 | c: const_t 161 | 162 | constructor ( 163 | c: const_t, 164 | ) { 165 | super () 166 | this.c = c 167 | } 168 | 169 | eq (that: term_t): boolean { 170 | return that instanceof constant_t 171 | && this.c.eq (that.c) 172 | } 173 | 174 | well_typed_p (_ctx: ctx_t, T: type_t): boolean { 175 | return this.c.T.eq (T) 176 | } 177 | 178 | normal_p (ctx: ctx_t, T: type_t): boolean { 179 | if (this.neutral_p (ctx, T)) { 180 | return true 181 | } else if (this.c instanceof zero_t) { 182 | return this.well_typed_p (ctx, T) 183 | } else { 184 | return false 185 | } 186 | } 187 | 188 | neutral_p (ctx: ctx_t, T: type_t): boolean { 189 | return false 190 | } 191 | } 192 | 193 | class variable_t extends term_t { 194 | name: string 195 | 196 | constructor ( 197 | name: string, 198 | ) { 199 | super () 200 | this.name = name 201 | } 202 | 203 | eq (that: term_t): boolean { 204 | return that instanceof variable_t 205 | && this.name === that.name 206 | } 207 | 208 | well_typed_p (ctx: ctx_t, T: type_t): boolean { 209 | let X = ctx.map.get (name) 210 | if (X !== undefined && (X.eq (T))) { 211 | return true 212 | } else { 213 | return false 214 | } 215 | } 216 | 217 | normal_p (ctx: ctx_t, T: type_t): boolean { 218 | if (this.neutral_p (ctx, T)) { 219 | return true 220 | } else { 221 | return false 222 | } 223 | } 224 | 225 | neutral_p (ctx: ctx_t, T: type_t): boolean { 226 | return this.well_typed_p (ctx, T) 227 | } 228 | } 229 | 230 | class lambda_t extends term_t { 231 | name: string 232 | term: term_t 233 | 234 | constructor ( 235 | name: string, 236 | term: term_t, 237 | ) { 238 | super () 239 | this.name = name 240 | this.term = term 241 | } 242 | 243 | eq (that: term_t): boolean { 244 | return that instanceof lambda_t 245 | && this.name === that.name 246 | && this.term === that.term 247 | } 248 | 249 | well_typed_p (ctx: ctx_t, T: type_t): boolean { 250 | if (T instanceof arrow_t) { 251 | return this.term.well_typed_p ( 252 | ctx.ext (this.name, T.arg_type), 253 | T.ret_type 254 | ) 255 | } else { 256 | return false 257 | } 258 | } 259 | 260 | normal_p (ctx: ctx_t, T: type_t): boolean { 261 | if (this.neutral_p (ctx, T)) { 262 | return true 263 | } else if (T instanceof arrow_t) { 264 | return this.term.normal_p ( 265 | ctx.ext (this.name, T.arg_type), 266 | T.ret_type 267 | ) 268 | } else { 269 | return false 270 | } 271 | } 272 | 273 | neutral_p (ctx: ctx_t, T: type_t): boolean { 274 | return false 275 | } 276 | } 277 | 278 | class apply_t extends term_t { 279 | fun: term_t 280 | arg: term_t 281 | arg_type: type_t 282 | 283 | constructor ( 284 | fun: term_t, 285 | arg: term_t, 286 | arg_type: type_t, 287 | ) { 288 | super () 289 | this.fun = fun 290 | this.arg = arg 291 | this.arg_type = arg_type 292 | } 293 | 294 | eq (that: term_t): boolean { 295 | return that instanceof apply_t 296 | && this.fun === that.fun 297 | && this.arg === that.arg 298 | && this.arg_type === that.arg_type 299 | } 300 | 301 | well_typed_p (ctx: ctx_t, T: type_t): boolean { 302 | return this.arg.well_typed_p (ctx, this.arg_type) 303 | && this.fun.well_typed_p ( 304 | ctx, new arrow_t (this.arg_type, T) 305 | ) 306 | } 307 | 308 | normal_p (ctx: ctx_t, T: type_t): boolean { 309 | if (this.neutral_p (ctx, T)) { 310 | return true 311 | } else if (T.eq (new nat_t ()) && 312 | this.fun instanceof constant_t && 313 | this.fun.c instanceof suc_t) { 314 | return this.arg.normal_p (ctx, new nat_t ()) 315 | } else { 316 | return false 317 | } 318 | } 319 | 320 | neutral_p (ctx: ctx_t, T: type_t): boolean { 321 | if (this.arg.normal_p (ctx, this.arg_type) && 322 | this.fun.neutral_p (ctx, new arrow_t (this.arg_type, T))) { 323 | return true 324 | } else if (this.arg.neutral_p (ctx, new nat_t ()) && 325 | this.fun instanceof apply_t && 326 | this.fun.arg.normal_p ( 327 | ctx, new arrow_t ( 328 | new nat_t (), new arrow_t (T, T) 329 | ) 330 | ) && 331 | this.fun.fun instanceof apply_t && 332 | this.fun.fun.arg.normal_p (ctx, T) && 333 | this.fun.fun.fun instanceof constant_t && 334 | this.fun.fun.fun.c instanceof rec_t) { 335 | return true 336 | } else { 337 | return false 338 | } 339 | } 340 | } 341 | 342 | /** 343 | * Definitional Equality (a.k.a. sameness in "the little typer") 344 | * - not complete -- does not capture the semantic equality 345 | * - but decidable -- we can write a checker for this equality 346 | */ 347 | 348 | class definitional_equality_t { 349 | x: term_t 350 | y: term_t 351 | 352 | constructor ( 353 | x: term_t, 354 | y: term_t, 355 | ) { 356 | this.x = x 357 | this.y = y 358 | } 359 | 360 | swap (): definitional_equality_t { 361 | return new definitional_equality_t ( 362 | this.y, 363 | this.x, 364 | ) 365 | } 366 | 367 | check_same (ctx: ctx_t, T: type_t): boolean { 368 | return this.check_same_directed (ctx, T) 369 | || this.swap () .check_same_directed (ctx, T) 370 | } 371 | 372 | check_same_directed (ctx: ctx_t, T: type_t): boolean { 373 | return this.reflexivity (ctx, T) 374 | || this.beta_equality (ctx, T) 375 | || this.eta_equality (ctx, T) 376 | || this.compatibility (ctx, T) 377 | } 378 | 379 | reflexivity (ctx: ctx_t, T: type_t): boolean { 380 | return this.x.eq (this.y) 381 | && this.x.well_typed_p (ctx, T) 382 | } 383 | 384 | // TODO handle lambda applied to arg 385 | beta_equality (ctx: ctx_t, T: type_t): boolean { 386 | if (this.x instanceof apply_t && 387 | this.x.arg.eq (new constant_t (new zero_t ())) && 388 | this.x.fun instanceof apply_t && 389 | this.x.fun.fun instanceof apply_t && 390 | this.x.fun.fun.fun instanceof constant_t && 391 | this.x.fun.fun.fun.c instanceof rec_t && 392 | this.y.eq (this.x.fun.fun.arg) && 393 | this.x.fun.arg.well_typed_p ( 394 | ctx, new arrow_t ( 395 | new nat_t (), new arrow_t (T, T) 396 | ) 397 | ) && 398 | this.x.fun.fun.arg.well_typed_p (ctx, T)) { 399 | return true 400 | } else if (this.x instanceof apply_t && 401 | this.x.arg instanceof apply_t && 402 | this.x.arg.fun.eq (new constant_t (new suc_t ())) && 403 | this.x.arg.arg.well_typed_p (ctx, new nat_t ()) && 404 | this.x.fun instanceof apply_t && 405 | this.x.fun.fun instanceof apply_t && 406 | this.x.fun.fun.fun instanceof constant_t && 407 | this.x.fun.fun.fun.c instanceof rec_t && 408 | this.y.eq (this.x.fun.fun.arg) && 409 | this.x.fun.arg.well_typed_p ( 410 | ctx, new arrow_t ( 411 | new nat_t (), new arrow_t (T, T) 412 | ) 413 | ) && 414 | this.x.fun.fun.arg.well_typed_p (ctx, T) && 415 | this.y instanceof apply_t && 416 | this.y.fun instanceof apply_t && 417 | this.y.fun.fun.eq (this.x.fun.arg) && 418 | this.y.fun.arg.eq (this.x.fun.fun.arg) && 419 | this.y.arg instanceof apply_t && 420 | this.y.arg.fun instanceof apply_t && 421 | this.y.arg.fun.fun instanceof apply_t && 422 | this.y.arg.arg.eq (this.x.arg.arg) && 423 | this.y.arg.fun.arg.eq (this.x.fun.arg) && 424 | this.y.arg.fun.fun.arg.eq (this.x.fun.fun.arg) && 425 | this.y.arg.fun.fun.fun instanceof constant_t && 426 | this.y.arg.fun.fun.fun.c instanceof rec_t) { 427 | return true 428 | } else { 429 | return false 430 | } 431 | } 432 | 433 | eta_equality (ctx: ctx_t, T: type_t): boolean { 434 | if (this.x instanceof lambda_t && 435 | this.x.term instanceof apply_t && 436 | this.x.term.arg instanceof variable_t && 437 | this.x.term.arg.name === this.x.name && 438 | this.x.term.fun.eq (this.y) && 439 | T instanceof arrow_t && 440 | this.y.well_typed_p (ctx, T)) { 441 | return true 442 | } else { 443 | return false 444 | } 445 | } 446 | 447 | compatibility (ctx: ctx_t, T: type_t): boolean { 448 | if (this.x.eq (this.y) && 449 | this.x.well_typed_p (ctx, T)) { 450 | return true 451 | } else if (this.x instanceof lambda_t && 452 | this.y instanceof lambda_t && 453 | T instanceof arrow_t && 454 | this.x.name === this.y.name) { 455 | return new definitional_equality_t ( 456 | this.x.term, 457 | this.y.term, 458 | ) .check_same ( 459 | ctx.ext (this.x.name, T.arg_type), 460 | T.ret_type, 461 | ) 462 | } else if (this.x instanceof apply_t && 463 | this.y instanceof apply_t && 464 | this.x.arg_type.eq (this.y.arg_type)) { 465 | return new definitional_equality_t ( 466 | this.x.fun, 467 | this.y.fun, 468 | ) .check_same ( 469 | ctx, new arrow_t (this.x.arg_type, T) 470 | ) && new definitional_equality_t ( 471 | this.x.arg, 472 | this.y.arg, 473 | ) .check_same ( 474 | ctx, this.x.arg_type, 475 | ) 476 | } else { 477 | return false 478 | } 479 | } 480 | } 481 | 482 | function reflection (ctx: ctx_t, T: type_t, u: term_t): term_t { 483 | if (! u.neutral_p (ctx, T)) { 484 | throw new Error ("! u.neutral_p (ctx, T)") 485 | } 486 | 487 | if (T instanceof nat_t) { 488 | return u 489 | } else if (T instanceof arrow_t) { 490 | let v: any = "TODO" 491 | return reflection ( 492 | ctx, 493 | T.ret_type, 494 | new apply_t ( 495 | u, 496 | v, 497 | T.ret_type, 498 | ), 499 | ) 500 | } else { 501 | throw new Error ("TODO") 502 | } 503 | } 504 | 505 | function reification (ctx: ctx_t, T: type_t, u: term_t): term_t { 506 | throw new Error ("TODO") 507 | } 508 | -------------------------------------------------------------------------------- /src/panel-data.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash" 2 | import assert from "assert" 3 | 4 | import * as ut from "@cicadoidea/basic/lib/util" 5 | import * as nd from "./ndarray" 6 | 7 | import { permutation_t } from "./permutation" 8 | 9 | /** 10 | * The order matters. 11 | */ 12 | export 13 | class axes_t { 14 | readonly map: Map 15 | readonly length: number 16 | readonly shape: Array 17 | readonly order: number 18 | 19 | constructor ( 20 | map: Map 21 | ) { 22 | this.map = map 23 | this.length = map.size 24 | let shape = new Array () 25 | for (let axis of map.values ()) { 26 | shape.push (axis.length) 27 | } 28 | this.shape = shape 29 | this.order = shape.length 30 | } 31 | 32 | static from_array ( 33 | array: Array <[string, axis_t]> 34 | ): axes_t { 35 | return new axes_t (new Map (array)) 36 | } 37 | 38 | copy (): axes_t { 39 | return new axes_t (new Map (this.map)) 40 | } 41 | 42 | arg (name: string): number { 43 | let i = 0 44 | for (let k of this.map.keys ()) { 45 | if (k === name) { 46 | return i 47 | } 48 | i += 1 49 | } 50 | throw new Error ("name not in key of map") 51 | } 52 | 53 | name_array (): Array { 54 | return Array.from (this.map.keys ()) 55 | } 56 | 57 | axis_array (): Array { 58 | return Array.from (this.map.values ()) 59 | } 60 | 61 | get_name_by_index (i: number): string { 62 | return this.name_array () [i] 63 | } 64 | 65 | get_axis_by_index (i: number): axis_t { 66 | return this.axis_array () [i] 67 | } 68 | 69 | get (name: string): axis_t { 70 | let axis = this.map.get (name) 71 | if (axis === undefined) { 72 | throw new Error (`name: ${name} undefined`) 73 | } else { 74 | return axis 75 | } 76 | } 77 | 78 | array_index (index: index_t): nd.index_t { 79 | let array_index = new Array () 80 | // the order is used here 81 | for (let [name, axis] of this.map) { 82 | array_index.push (axis.get (index.get (name))) 83 | } 84 | return array_index 85 | } 86 | 87 | array_index_to_index (array_index: nd.index_t): index_t { 88 | let map = new Map () 89 | for (let k of ut.range (0, array_index.length)) { 90 | let i = array_index [k] 91 | let name = this.get_name_by_index (k) 92 | let axis = this.get_axis_by_index (k) 93 | map.set (name, axis.arg_label (i)) 94 | } 95 | return new index_t (map) 96 | } 97 | 98 | array_proj_index (index: index_t): nd.proj_index_t { 99 | let array_index = new Array () 100 | // the order is used here 101 | for (let [name, axis] of this.map) { 102 | if (index.map.has (name)) { 103 | array_index.push (axis.get (index.get (name))) 104 | } else { 105 | array_index.push (null) 106 | } 107 | } 108 | return array_index 109 | } 110 | 111 | array_proj_index_to_index ( 112 | array_proj_index: nd.proj_index_t 113 | ): index_t { 114 | let map = new Map () 115 | for (let k of ut.range (0, array_proj_index.length)) { 116 | let i = array_proj_index [k] 117 | if (i !== null) { 118 | let name = this.get_name_by_index (k) 119 | let axis = this.get_axis_by_index (k) 120 | map.set (name, axis.arg_label (i)) 121 | } 122 | } 123 | return new index_t (map) 124 | } 125 | 126 | print () { 127 | for (let [name, axis] of this.map) { 128 | console.log (name) 129 | console.table (axis.map) 130 | } 131 | } 132 | } 133 | 134 | export 135 | let axes = axes_t.from_array 136 | 137 | export 138 | class axis_t { 139 | readonly map: Map 140 | readonly length: number 141 | 142 | constructor ( 143 | map: Map = new Map (), 144 | ) { 145 | this.map = map 146 | this.length = map.size 147 | } 148 | 149 | static from_array (array: Array ): axis_t { 150 | let map = new Map () 151 | for (let i of ut.range (0, array.length)) { 152 | let v = array [i] 153 | map.set (v, i) 154 | } 155 | return new axis_t (map) 156 | } 157 | 158 | copy (): axis_t { 159 | return new axis_t (new Map (this.map)) 160 | } 161 | 162 | arg (name: string): number { 163 | let i = 0 164 | for (let k of this.map.keys ()) { 165 | if (k === name) { 166 | return i 167 | } 168 | i += 1 169 | } 170 | throw new Error ("name not in key of map") 171 | } 172 | 173 | arg_label (index: number): string { 174 | for (let [label, i] of this.map) { 175 | if (i === index) { 176 | return label 177 | } 178 | } 179 | throw new Error ("label not in key of map") 180 | } 181 | 182 | get (label: string): number { 183 | let i = this.map.get (label) 184 | if (i === undefined) { 185 | throw new Error ("label undefined") 186 | } else { 187 | return i 188 | } 189 | } 190 | 191 | eq (that: axis_t): boolean { 192 | if (this.length !== that.length) { 193 | return false 194 | } else { 195 | return _.isEqual (this.map, that.map) 196 | } 197 | } 198 | 199 | print () { 200 | console.table (this.map) 201 | } 202 | } 203 | 204 | export 205 | function axis (array: Array ): axis_t { 206 | return axis_t.from_array (array) 207 | } 208 | 209 | export 210 | class index_t { 211 | map: Map 212 | 213 | constructor (map: Map ) { 214 | this.map = map 215 | } 216 | 217 | get (name: string): string { 218 | let label = this.map.get (name) 219 | if (label === undefined) { 220 | throw new Error (`name: ${name} undefined`) 221 | } else { 222 | return label 223 | } 224 | } 225 | 226 | static from_array ( 227 | array: Array <[string, string]> 228 | ): index_t { 229 | return new index_t (new Map (array)) 230 | } 231 | 232 | to_array (): Array <[string, string]> { 233 | return Array.from (this.map) 234 | } 235 | } 236 | 237 | export 238 | let index = index_t.from_array 239 | 240 | export 241 | class slice_index_t { 242 | map: Map > 243 | 244 | constructor (map: Map >) { 245 | this.map = map 246 | } 247 | 248 | get (name: string): Array { 249 | let label_array = this.map.get (name) 250 | if (label_array === undefined) { 251 | throw new Error (`name: ${name} undefined`) 252 | } else { 253 | return label_array 254 | } 255 | } 256 | } 257 | 258 | /** 259 | * ndarray + named axes, 260 | * where an axis maps labels to indexes. 261 | 262 | * Do not implicitly generate data when there are missing data. 263 | * Missing data should be handled explicitly. 264 | */ 265 | export 266 | class data_t { 267 | axes: axes_t 268 | array: nd.array_t 269 | readonly shape: Array 270 | readonly order: number 271 | 272 | constructor ( 273 | axes: axes_t, 274 | array: nd.array_t, 275 | ) { 276 | if (! _.isEqual (axes.shape, array.shape)) { 277 | console.log ("axes.shape:", axes.shape) 278 | console.log ("array.shape:", array.shape) 279 | throw new Error ("shape mismatch") 280 | } 281 | this.axes = axes 282 | this.array = array 283 | this.shape = axes.shape 284 | this.order = axes.order 285 | } 286 | 287 | get (index: index_t): number { 288 | let array_index = this.axes.array_index (index) 289 | return this.array.get (array_index) 290 | } 291 | 292 | set (index: index_t, x: number): data_t { 293 | let array_index = this.axes.array_index (index) 294 | this.array.set (array_index, x) 295 | return this 296 | } 297 | 298 | update_at ( 299 | index: index_t, 300 | f: (v: number) => number, 301 | ): data_t { 302 | return this.set (index, f (this.get (index))) 303 | } 304 | 305 | copy (): data_t { 306 | return new data_t ( 307 | this.axes.copy (), 308 | this.array.copy (), 309 | ) 310 | } 311 | 312 | *entries () { 313 | for (let [array_index, v] of this.array.entries ()) { 314 | let index = this.axes.array_index_to_index (array_index) 315 | yield [index, v] as [index_t, number] 316 | } 317 | } 318 | 319 | add (that: data_t): data_t { 320 | let data = this.copy () 321 | for (let [i, v] of that.entries ()) { 322 | data.update_at (i, x => x + v) 323 | } 324 | return data 325 | } 326 | 327 | print () { 328 | if (this.order === 1) { 329 | new series_t (this) .print () 330 | } else if (this.order === 2) { 331 | new frame_t (this) .print () 332 | } else { 333 | let array_proj_index: nd.proj_index_t = 334 | this.shape.slice () .fill (0) 335 | array_proj_index.pop () 336 | array_proj_index.pop () 337 | array_proj_index.push (null) 338 | array_proj_index.push (null) 339 | while (true) { 340 | let index = 341 | this.axes.array_proj_index_to_index ( 342 | array_proj_index) 343 | console.log ("data index:", index.to_array ()) 344 | console.log ("array index:", array_proj_index) 345 | this.proj (index) .print () 346 | array_proj_index = 347 | nd.array_t.proj_index_inc_with_shape ( 348 | array_proj_index, 349 | this.shape) 350 | if ( 351 | nd.array_t.proj_index_max_p ( 352 | array_proj_index, 353 | this.shape) 354 | ) { 355 | console.log ("data index:", index.to_array ()) 356 | console.log ("array index:", array_proj_index) 357 | this.proj (index) .print () 358 | return 359 | } 360 | } 361 | } 362 | } 363 | 364 | proj (index: index_t): data_t { 365 | let axes_array = new Array () 366 | for (let [name, axis] of this.axes.map) { 367 | if (! index.map.has (name)) { 368 | axes_array.push ([name, axis]) 369 | } 370 | } 371 | return new data_t ( 372 | axes_t.from_array (axes_array), 373 | this.array.proj ( 374 | this.axes.array_proj_index (index))) 375 | } 376 | 377 | // TODO 378 | // slice (index: slice_index_t): data_t { 379 | // } 380 | 381 | contract ( 382 | that: data_t, 383 | left_name: string, 384 | right_name: string, 385 | ): data_t { 386 | let left_arg = this.axes.arg (left_name) 387 | let right_arg = that.axes.arg (right_name) 388 | let array = this.array.contract ( 389 | that.array, left_arg, right_arg) 390 | let map = new Map () 391 | for (let [name, axis] of this.axes.map) { 392 | if (name !== left_name) { 393 | map.set (name, axis) 394 | } 395 | } 396 | for (let [name, axis] of that.axes.map) { 397 | if (name !== right_name) { 398 | map.set (name, axis) 399 | } 400 | } 401 | let axes = new axes_t (map) 402 | return new data_t (axes, array) 403 | } 404 | } 405 | 406 | export 407 | interface series_exp_t { 408 | [k: string]: number 409 | } 410 | 411 | export 412 | class series_t { 413 | data: data_t 414 | readonly name: string 415 | readonly axis: axis_t 416 | readonly array: nd.array_t 417 | 418 | constructor ( 419 | data: data_t, 420 | ) { 421 | assert (data.order === 1) 422 | this.data = data 423 | this.name = data.axes.get_name_by_index (0) 424 | this.axis = data.axes.get_axis_by_index (0) 425 | this.array = data.array 426 | } 427 | 428 | copy (): series_t { 429 | return new series_t (this.data) 430 | } 431 | 432 | rename (name: string) { 433 | return new_series (name, this.axis, this.array) 434 | } 435 | 436 | label_to_index (label: string): index_t { 437 | return index_t.from_array ([ 438 | [this.name, label], 439 | ]) 440 | } 441 | 442 | get (label: string): number { 443 | return this.data.get (this.label_to_index (label)) 444 | } 445 | 446 | add (that: series_t): series_t { 447 | return new series_t (this.data.add (that.data)) 448 | } 449 | 450 | *entries () { 451 | for (let label of this.axis.map.keys ()) { 452 | yield [label, this.get (label)] as [string, number] 453 | } 454 | } 455 | 456 | to_exp (): series_exp_t { 457 | let exp: series_exp_t = {} 458 | for (let [k, v] of this.entries ()) { 459 | exp [k] = v 460 | } 461 | return exp 462 | } 463 | 464 | print () { 465 | console.group (`axis_name: ${this.name}`) 466 | console.table (this.to_exp ()) 467 | console.groupEnd () 468 | } 469 | } 470 | 471 | export 472 | function new_series ( 473 | name: string, 474 | axis: axis_t, 475 | array: nd.array_t, 476 | ): series_t { 477 | let axes = axes_t.from_array ([ 478 | [name, axis], 479 | ]) 480 | return new series_t (new data_t (axes, array)) 481 | } 482 | 483 | export 484 | let series = new_series 485 | 486 | export 487 | interface frame_exp_t { 488 | [k: string]: series_exp_t 489 | } 490 | 491 | export 492 | class frame_t { 493 | data: data_t 494 | readonly array: nd.array_t 495 | readonly row_name: string 496 | readonly row_axis: axis_t 497 | readonly col_name: string 498 | readonly col_axis: axis_t 499 | 500 | constructor ( 501 | data: data_t, 502 | row_col_index: [number, number] = [0, 1], 503 | ) { 504 | assert (data.order === 2) 505 | this.data = data 506 | this.array = data.array 507 | let [row_index, col_index] = row_col_index 508 | this.row_name = data.axes.get_name_by_index (row_index) 509 | this.row_axis = data.axes.get_axis_by_index (row_index) 510 | this.col_name = data.axes.get_name_by_index (col_index) 511 | this.col_axis = data.axes.get_axis_by_index (col_index) 512 | } 513 | 514 | copy (): frame_t { 515 | return new frame_t (this.data) 516 | } 517 | 518 | static from_rows ( 519 | row_name: string, 520 | col_name: string, 521 | rows: Array , 522 | ): frame_t { 523 | assert (rows.length !== 0) 524 | let first_row = rows [0] 525 | let label_array = new Array () 526 | let col_axis = first_row.axis 527 | let lower = new Array () 528 | for (let row of rows) { 529 | assert (row.axis.eq (col_axis)) 530 | label_array.push (row.name) 531 | lower.push (row.array) 532 | } 533 | let row_axis = axis_t.from_array (label_array) 534 | return new_frame ( 535 | row_name, row_axis, 536 | col_name, col_axis, 537 | nd.array_t.from_lower_order (lower), 538 | ) 539 | } 540 | 541 | static from_cols ( 542 | row_name: string, 543 | col_name: string, 544 | cols: Array , 545 | ): frame_t { 546 | return frame_t.from_rows ( 547 | col_name, 548 | row_name, 549 | cols, 550 | ) .transpose () 551 | } 552 | 553 | transpose (): frame_t { 554 | return new frame_t (this.data, [1, 0]) 555 | } 556 | 557 | row (label: string): series_t { 558 | return new series_t (this.data.proj ( 559 | index_t.from_array ([ 560 | [this.row_name, label], 561 | ]) 562 | )) 563 | } 564 | 565 | col (label: string): series_t { 566 | return new series_t (this.data.proj ( 567 | index_t.from_array ([ 568 | [this.col_name, label], 569 | ]) 570 | )) 571 | } 572 | 573 | *rows () { 574 | for (let label of this.row_axis.map.keys ()) { 575 | yield this.row (label) as series_t 576 | } 577 | } 578 | 579 | *row_entries () { 580 | for (let label of this.row_axis.map.keys ()) { 581 | yield [label, this.row (label)] as [string, series_t] 582 | } 583 | } 584 | 585 | *cols () { 586 | for (let label of this.col_axis.map.keys ()) { 587 | yield this.col (label) as series_t 588 | } 589 | } 590 | 591 | *col_entries () { 592 | for (let label of this.col_axis.map.keys ()) { 593 | yield [label, this.col (label)] as [string, series_t] 594 | } 595 | } 596 | 597 | add (that: frame_t): frame_t { 598 | return new frame_t (this.data.add (that.data)) 599 | } 600 | 601 | mul (that: frame_t): frame_t { 602 | return new frame_t ( 603 | this.data.contract ( 604 | that.data, this.col_name, that.row_name)) 605 | } 606 | 607 | /** 608 | * row is always the major. 609 | */ 610 | to_exp (): frame_exp_t { 611 | let exp: frame_exp_t = {} 612 | for (let [label, row] of this.row_entries ()) { 613 | exp [label] = row.to_exp () 614 | } 615 | return exp 616 | } 617 | 618 | print () { 619 | console.log (`row_name: ${this.row_name}`) 620 | console.log (`col_name: ${this.col_name}`) 621 | console.group () 622 | console.table (this.to_exp ()) 623 | console.groupEnd () 624 | } 625 | } 626 | 627 | export 628 | function new_frame ( 629 | row_name: string, row_axis: axis_t, 630 | col_name: string, col_axis: axis_t, 631 | array: nd.array_t, 632 | ): frame_t { 633 | let axes = axes_t.from_array ([ 634 | [row_name, row_axis], 635 | [col_name, col_axis], 636 | ]) 637 | return new frame_t (new data_t (axes, array)) 638 | } 639 | 640 | export 641 | let frame = new_frame 642 | -------------------------------------------------------------------------------- /src/ndarray.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash" 2 | import assert from "assert" 3 | 4 | import * as ut from "@cicadoidea/basic/lib/util" 5 | 6 | import { permutation_t } from "./permutation" 7 | 8 | export type Array1d = Array 9 | export type Array2d = Array > 10 | export type Array3d = Array >> 11 | 12 | export type index_t = Array 13 | export type proj_index_t = Array 14 | export type slice_index_t = Array <[number, number] | null> 15 | 16 | /** 17 | * strides based row-major ndarray of number 18 | */ 19 | export 20 | class array_t { 21 | readonly size: number 22 | readonly order: number 23 | 24 | /** 25 | * I keep the basic constructor low level, 26 | * and mainly use specific constructors in client modules. 27 | */ 28 | constructor ( 29 | protected buffer: Float64Array, 30 | readonly shape: Array , 31 | readonly strides: Array , 32 | readonly offset: number = 0, 33 | ) { 34 | this.order = shape.length 35 | this.size = array_t.shape_to_size (shape) 36 | if (strides.length !== shape.length) { 37 | throw new Error ("strides shape length mismatch") 38 | } 39 | if (buffer.length < this.size + offset) { 40 | throw new Error ("buffer not large enough") 41 | } 42 | } 43 | 44 | static shape_to_size (shape: Array ): number { 45 | return shape.reduce ((acc, cur) => acc * cur) 46 | } 47 | 48 | static shape_to_strides ( 49 | shape: Array 50 | ): Array { 51 | let strides: Array = [] 52 | let acc = 1 53 | shape.slice () .reverse () .forEach ((x) => { 54 | strides.push (acc) 55 | acc *= x 56 | }) 57 | return strides.reverse () 58 | } 59 | 60 | get_linear_index (index: index_t): number { 61 | if (index.length !== this.shape.length) { 62 | throw new Error ("index length mismatch") 63 | } 64 | let linear_index = this.offset 65 | for (let i = 0; i < index.length; i += 1) { 66 | linear_index += index [i] * this.strides [i] 67 | } 68 | return linear_index 69 | } 70 | 71 | linear_get (i: number): number { 72 | return this.buffer [i] 73 | } 74 | 75 | linear_set (i: number, v: number): array_t { 76 | this.buffer [i] = v 77 | return this 78 | } 79 | 80 | get (index: index_t): number { 81 | let i = this.get_linear_index (index) 82 | return this.linear_get (i) 83 | } 84 | 85 | set (index: index_t, x: number): array_t { 86 | let i = this.get_linear_index (index) 87 | this.linear_set (i, x) 88 | return this 89 | } 90 | 91 | update_at ( 92 | index: index_t, 93 | f: (v: number) => number, 94 | ): array_t { 95 | return this.set (index, f (this.get (index))) 96 | } 97 | 98 | *values () { 99 | for (let index of indexes_of_shape (this.shape)) { 100 | yield this.get (index) as number 101 | } 102 | } 103 | 104 | *entries () { 105 | for (let index of indexes_of_shape (this.shape)) { 106 | yield [ 107 | index.slice (), 108 | this.get (index), 109 | ] as [ index_t, number ] 110 | } 111 | } 112 | 113 | *indexes () { 114 | for (let index of indexes_of_shape (this.shape)) { 115 | yield index.slice () as index_t 116 | } 117 | } 118 | 119 | reduce_with ( 120 | init: number, 121 | f: (acc: number, cur: number) => number, 122 | ): number { 123 | let acc = init 124 | for (let v of this.values ()) { 125 | acc = f (acc, v) 126 | } 127 | return acc 128 | } 129 | 130 | copy (): array_t { 131 | let buffer = new Float64Array (this.size) 132 | let array = new array_t ( 133 | buffer, this.shape, 134 | array_t.shape_to_strides (this.shape)) 135 | for (let [i, x] of this.entries ()) { 136 | array.set (i, x) 137 | } 138 | return array 139 | } 140 | 141 | proj (index: proj_index_t): array_t { 142 | if (index.length !== this.shape.length) { 143 | throw new Error ("index length mismatch") 144 | } 145 | let shape = new Array () 146 | let strides = new Array () 147 | let offset = this.offset 148 | for (let [k, v] of index.entries ()) { 149 | if (v === null) { 150 | shape.push (this.shape [k]) 151 | strides.push (this.strides [k]) 152 | } else { 153 | offset += v * this.strides [k] 154 | } 155 | } 156 | return new array_t (this.buffer, shape, strides, offset) 157 | } 158 | 159 | slice (index: slice_index_t): array_t { 160 | if (index.length !== this.shape.length) { 161 | throw new Error ("index length mismatch") 162 | } 163 | let shape = this.shape.slice () 164 | let offset = this.offset 165 | for (let [k, v] of index.entries ()) { 166 | if (v !== null) { 167 | let [start, end] = v 168 | shape [k] = end - start 169 | offset += start * this.strides [k] 170 | } 171 | } 172 | return new array_t (this.buffer, shape, this.strides, offset) 173 | } 174 | 175 | /** 176 | * the order of `src` array can be higher or lower, 177 | * as long as its `.values` match `tar.indexes`. 178 | */ 179 | put ( 180 | index: slice_index_t, 181 | src: array_t, 182 | ): array_t { 183 | let tar = this.slice (index) 184 | let index_array = Array.from (tar.indexes ()) 185 | let value_array = Array.from (src.values ()) 186 | if (index_array.length !== value_array.length) { 187 | throw new Error ("size mismatch") 188 | } 189 | for (let k in index_array) { 190 | let i = index_array [k] 191 | let v = value_array [k] 192 | tar.set (i, v) 193 | } 194 | return this 195 | } 196 | 197 | put_porj ( 198 | index: proj_index_t, 199 | src: array_t, 200 | ): array_t { 201 | let slice_index = index.map (i => { 202 | if (i === null) { 203 | return null 204 | } else { 205 | return [i, i+1] 206 | } 207 | }) as slice_index_t 208 | return this.put (slice_index, src) 209 | } 210 | 211 | add (that: array_t): array_t { 212 | let array = this.copy () 213 | for (let [i, v] of that.entries ()) { 214 | array.update_at (i, x => x + v) 215 | } 216 | return array 217 | } 218 | 219 | static from_1darray (array: Array1d): array_t { 220 | let buffer = Float64Array.from (array) 221 | let shape = [array.length] 222 | let strides = array_t.shape_to_strides (shape) 223 | return new array_t (buffer, shape, strides) 224 | } 225 | 226 | to_1darray (): Array1d { 227 | return Array.from (this.values ()) 228 | } 229 | 230 | static from_2darray (array: Array2d): array_t { 231 | let y_length = array.length 232 | let x_length = array[0].length 233 | for (let a of array) { 234 | if (a.length !== x_length) { 235 | throw new Error ("inner array length mismatch") 236 | } 237 | } 238 | let buffer = Float64Array.from (array.flat ()) 239 | let shape = [y_length, x_length] 240 | let strides = array_t.shape_to_strides (shape) 241 | return new array_t (buffer, shape, strides) 242 | } 243 | 244 | to_2darray (): Array2d { 245 | let array = [] 246 | let [x, _] = this.shape 247 | for (let i = 0; i < x; i++) { 248 | array.push (this.proj ([i, null]) .to_1darray ()) 249 | } 250 | return array 251 | } 252 | 253 | static from_3darray (array: Array3d): array_t { 254 | let z_length = array.length 255 | let y_length = array[0].length 256 | let x_length = array[0][0].length 257 | for (let a of array) { 258 | if (a.length !== y_length) { 259 | throw new Error ("inner array length mismatch") 260 | } else { 261 | for (let b of a) { 262 | if (b.length !== x_length) { 263 | throw new Error ("inner inner array length mismatch") 264 | } 265 | } 266 | } 267 | } 268 | let buffer = Float64Array.from (array.flat (2)) 269 | let shape = [z_length, y_length, x_length] 270 | let strides = array_t.shape_to_strides (shape) 271 | return new array_t (buffer, shape, strides) 272 | } 273 | 274 | static from_buffer ( 275 | buffer: Float64Array, 276 | shape: Array , 277 | ): array_t { 278 | let strides = array_t.shape_to_strides (shape) 279 | return new array_t (buffer, shape, strides) 280 | } 281 | 282 | static constant (n: number, shape: Array ): array_t { 283 | let size = array_t.shape_to_size (shape) 284 | let buffer = new Float64Array (size) 285 | buffer.fill (n) 286 | let strides = array_t.shape_to_strides (shape) 287 | return new array_t (buffer, shape, strides) 288 | } 289 | 290 | static zeros (shape: Array ): array_t { 291 | return array_t.constant (0, shape) 292 | } 293 | 294 | static ones (shape: Array ): array_t { 295 | return array_t.constant (1, shape) 296 | } 297 | 298 | fill (x: number): array_t { 299 | this.buffer.fill (x) 300 | return this 301 | } 302 | 303 | static proj_index_max_p ( 304 | index: proj_index_t, 305 | shape: Array , 306 | ): boolean { 307 | if (index.length !== shape.length) { 308 | throw new Error ("index length mismatch") 309 | } 310 | let flag = true 311 | for (let k in index) { 312 | let i = index [k] 313 | let s = shape [k] 314 | if (i !== null && i < (s - 1)) { 315 | flag = false 316 | } 317 | } 318 | return flag 319 | } 320 | 321 | static proj_index_inc_with_shape ( 322 | index: proj_index_t, 323 | shape: Array , 324 | ): proj_index_t { 325 | if (index.length !== shape.length) { 326 | throw new Error ("index length mismatch") 327 | } 328 | if (array_t.proj_index_max_p (index, shape)) { 329 | throw new Error ("index out of shape") 330 | } 331 | let [i] = index.slice (-1) 332 | let [s] = shape.slice (-1) 333 | if (i === null) { 334 | let new_index = index.slice () 335 | let new_shape = shape.slice () 336 | new_index.pop () 337 | new_shape.pop () 338 | new_index = array_t.proj_index_inc_with_shape ( 339 | new_index, 340 | new_shape, 341 | ) 342 | new_index.push (null) 343 | return new_index 344 | } else if (i >= s - 1) { 345 | let new_index = index.slice () 346 | let new_shape = shape.slice () 347 | new_index.pop () 348 | new_shape.pop () 349 | new_index = array_t.proj_index_inc_with_shape ( 350 | new_index, 351 | new_shape, 352 | ) 353 | new_index.push (0) 354 | return new_index 355 | } else { 356 | let new_index = index.slice () 357 | new_index.pop () 358 | new_index.push (i + 1) 359 | return new_index 360 | } 361 | } 362 | 363 | print () { 364 | if (this.order === 1) { 365 | console.table (this.to_1darray ()) 366 | } else if (this.order === 2) { 367 | console.table (this.to_2darray ()) 368 | } else { 369 | let index: proj_index_t = this.shape.slice () .fill (0) 370 | index.pop () 371 | index.pop () 372 | index.push (null) 373 | index.push (null) 374 | while (true) { 375 | console.log ("array index:", index) 376 | this.proj (index) .print () 377 | index = 378 | array_t.proj_index_inc_with_shape ( 379 | index, 380 | this.shape) 381 | if (array_t.proj_index_max_p (index, this.shape)) { 382 | console.log ("array index:", index) 383 | this.proj (index) .print () 384 | return 385 | } 386 | } 387 | } 388 | } 389 | 390 | *zip (that: array_t) { 391 | let this_iter = this.values () 392 | let that_iter = that.values () 393 | while (true) { 394 | let this_next = this_iter.next () 395 | let that_next = that_iter.next () 396 | if (this_next.done || that_next.done) { 397 | return 398 | } else { 399 | yield [this_next.value, that_next.value] 400 | } 401 | } 402 | } 403 | 404 | eq (that: array_t): boolean { 405 | if (this.size !== that.size) { return false } 406 | if (this.order !== that.order) { return false } 407 | if (! _.isEqual (this.shape, that.shape)) { return false } 408 | for (let [x, y] of this.zip (that)) { 409 | if (x !== y) { 410 | return false 411 | } 412 | } 413 | return true 414 | } 415 | 416 | map (f: (x: number) => number): array_t { 417 | let buffer = new Float64Array (this.size) 418 | let array = new array_t ( 419 | buffer, this.shape, 420 | array_t.shape_to_strides (this.shape)) 421 | for (let [i, x] of this.entries ()) { 422 | array.set (i, f (x)) 423 | } 424 | return array 425 | } 426 | 427 | for_each (f: (x: number) => any): array_t { 428 | for (let x of this.values ()) { 429 | f (x) 430 | } 431 | return this 432 | } 433 | 434 | append (k: number, that: array_t): array_t { 435 | assert (this.order === that.order) 436 | for (let j of ut.range (0, this.order)) { 437 | if (j !== k) { 438 | assert (this.shape [j] === that.shape [j]) 439 | } 440 | } 441 | let shape = this.shape.slice () 442 | shape [k] = this.shape [k] + that.shape [k] 443 | let buffer = new Float64Array (array_t.shape_to_size (shape)) 444 | let strides = array_t.shape_to_strides (shape) 445 | let array = new array_t (buffer, shape, strides) 446 | let offset = this.shape [k] 447 | for (let i of array.indexes ()) { 448 | if (i [k] < offset) { 449 | array.set (i, this.get (i)) 450 | } else { 451 | let j = i.slice () 452 | j [k] = j [k] - offset 453 | array.set (i, that.get (j)) 454 | } 455 | } 456 | return array 457 | } 458 | 459 | reshape ( 460 | permutation: permutation_t 461 | ): array_t { 462 | let shape = new Array () 463 | let strides = new Array () 464 | for (let i of permutation) { 465 | shape.push (this.shape [i]) 466 | strides.push (this.strides [i]) 467 | } 468 | return new array_t ( 469 | this.buffer, shape, strides, 470 | this.offset) 471 | } 472 | 473 | permute ( 474 | k: number, 475 | permutation: permutation_t, 476 | ): array_t { 477 | assert (permutation.size === this.shape [k]) 478 | let array = this.copy () 479 | for (let i of this.indexes ()) { 480 | let j = i.slice () 481 | j [k] = permutation.get (j [k]) 482 | array.set (j, this.get (i)) 483 | } 484 | return array 485 | } 486 | 487 | // [1, 2, <3>, 4] 488 | // [2, <3>, 4, 5] 489 | // => [1, 2, 4, 2, 4, 5] 490 | contract ( 491 | that: array_t, 492 | i: number, 493 | j: number, 494 | ): array_t { 495 | let this_size = this.shape [i] 496 | let that_size = that.shape [j] 497 | if (this_size !== that_size) { 498 | throw new Error ("size mismatch") 499 | } 500 | let shape = new Array () 501 | this.shape.forEach ((s, k) => { 502 | if (k !== i) { 503 | shape.push (s) 504 | } 505 | }) 506 | that.shape.forEach ((s, k) => { 507 | if (k !== j) { 508 | shape.push (s) 509 | } 510 | }) 511 | let size = array_t.shape_to_size (shape) 512 | let buffer = new Float64Array (size) 513 | let strides = array_t.shape_to_strides (shape) 514 | let array = new array_t (buffer, shape, strides) 515 | let split_index = ( 516 | index: index_t 517 | ): [proj_index_t, proj_index_t] => { 518 | let left = index.slice (0, this.order - 1) as proj_index_t 519 | left.splice (i, 0, null) 520 | let right = index.slice (this.order - 1) as proj_index_t 521 | right.splice (j, 0, null) 522 | return [left, right] 523 | } 524 | for (let index of array.indexes ()) { 525 | let [left, right] = split_index (index) 526 | let sum = 0 527 | let zip = this.proj (left) .zip (that.proj (right)) 528 | for (let [x, y] of zip) { 529 | sum += x * y 530 | } 531 | array.set (index, sum) 532 | } 533 | return array 534 | } 535 | 536 | static from_lower_order (lower: Array ): array_t { 537 | assert (lower.length !== 0) 538 | let first_array = lower [0] 539 | let first_shape = first_array.shape 540 | let shape = [lower.length] .concat (first_array.shape) 541 | let size = array_t.shape_to_size (shape) 542 | let buffer = new Float64Array (size) 543 | let higher = new array_t ( 544 | buffer, shape, 545 | array_t.shape_to_strides (shape)) 546 | for (let i of ut.range (0, lower.length)) { 547 | let array = lower [i] 548 | assert (_.isEqual (array.shape, first_shape)) 549 | let index: proj_index_t = [i] 550 | index = index.concat (ut.repeats (() => null, shape.length-1)) 551 | higher.put_porj (index, array) 552 | } 553 | return higher 554 | } 555 | } 556 | 557 | export 558 | function array1d_p (array: Array ): boolean { 559 | if (array.length === 0) { 560 | return true 561 | } else { 562 | return typeof array [0] === 'number' 563 | } 564 | } 565 | 566 | export 567 | function array2d_p (array: Array ): boolean { 568 | if (array.length === 0) { 569 | return true 570 | } else { 571 | return array1d_p (array [0]) 572 | } 573 | } 574 | 575 | export 576 | function array3d_p (array: Array ): boolean { 577 | if (array.length === 0) { 578 | return true 579 | } else { 580 | return array2d_p (array [0]) 581 | } 582 | } 583 | 584 | export 585 | function array (array: Array1d | Array2d | Array3d): array_t { 586 | if (array1d_p (array)) { 587 | return array_t.from_1darray (array as Array1d) 588 | } else if (array2d_p (array)) { 589 | return array_t.from_2darray (array as Array2d) 590 | } else if (array3d_p (array)) { 591 | return array_t.from_3darray (array as Array3d) 592 | } else { 593 | throw new Error ("can only handle Array1d | Array2d | Array3d") 594 | } 595 | } 596 | 597 | export 598 | function index_max_p ( 599 | index: index_t, 600 | shape: Array , 601 | ): boolean { 602 | for (let k in index) { 603 | let i = index [k] 604 | let s = shape [k] 605 | if (i < s - 1) { 606 | return false 607 | } 608 | } 609 | return true 610 | } 611 | 612 | /** 613 | * recursive side-effect over index 614 | */ 615 | export 616 | function index_step ( 617 | index: index_t, 618 | shape: Array , 619 | cursor: number, 620 | ): index_t { 621 | let i = index [cursor] 622 | let s = shape [cursor] 623 | if (i < s - 1) { 624 | index [cursor] = i + 1 625 | return index 626 | } else { 627 | index [cursor] = 0 628 | return index_step (index, shape, cursor - 1) 629 | } 630 | } 631 | 632 | export 633 | function* indexes_of_shape (shape: Array ) { 634 | let size = shape.length 635 | let index = new Array (size) .fill (0) 636 | yield index 637 | while (true) { 638 | if (index_max_p (index, shape)) { 639 | return 640 | } else { 641 | yield index_step (index, shape, size - 1) 642 | } 643 | } 644 | } 645 | --------------------------------------------------------------------------------