diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f374670d..6dd85b24 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,6 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + key: ${{ runner.os }}-cache-build-v${{ inputs.cache-version }} - name: Run examples run: | cargo run -r -- check examples/hello_world @@ -29,13 +33,33 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + key: ${{ runner.os }}-cache-tests-v${{ inputs.cache-version }} - name: Run unit tests run: cargo test --verbose --workspace + benchmarks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + key: ${{ runner.os }}-cache-build-v${{ inputs.cache-version }} + - name: Run benchmarks + run: | + cargo run -r -- check benchmarks + checks: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + key: ${{ runner.os }}-cache-unit-v${{ inputs.cache-version }} - name: Format run: cargo fmt --all -- --check - name: Clippy diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore new file mode 100644 index 00000000..ff7811b1 --- /dev/null +++ b/benchmarks/.gitignore @@ -0,0 +1,6 @@ +# Aiken compilation artifacts +artifacts/ +# Aiken's project working directory +build/ +# Aiken's default documentation export +docs/ diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..25cbf09a --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,153 @@ +# (No Fib) Benchmarks + +This folder contains a set of benchmarks inspired and ported from the [plutus-benchmarks](https://github.com/IntersectMBO/plutus/tree/master/plutus-benchmark/nofib#plutus-nofib-benchmarks), written in Haskell. The idea is to provide benchmarks which can capture more realistic patterns and behaviours than usual "Fibonacci" benchmarks often used for benchmarking applications but falling short in capturing real-world scenarios. + +Note that the primary use-case of those benchmarks is to compare Aiken with itself across compiler versions. As optimizations get implemented, it comes as a supplimentary means to assess their impact. + +## Summary + +Results are summarized below, relatively to the previous version. For brevity, we only report versions for which there's a deviation from a previous version. + + + + + +### CPU + +| Benchmark | `v1.0.29` | vs `v1.0.25` | vs `v1.0.23` | vs `v1.0.21` | vs `v1.0.18` | +| --- | ---: | ---: | ---: | ---: | ---: | +| `clausify_f1` | 21594809455 | -6.26% | +11.71% | +11.71% | +11.84% | +| `clausify_f2` | 26864755594 | -6.07% | +11.30% | +11.30% | +11.44% | +| `clausify_f3` | 71814854199 | -5.98% | +11.17% | +11.17% | +11.31% | +| `clausify_f4` | 93024749730 | -5.01% | +9.37% | +9.37% | +9.55% | +| `clausify_f5` | 349894049008 | -6.00% | +11.24% | +11.24% | +11.35% | +| `knights_100_4x4` | 71851995726 | +1.40% | +4.45% | +4.30% | +4.80% | +| `knights_100_6x6` | 159767368294 | +8.93% | +11.92% | +11.85% | +12.70% | +| `knights_100_8x8` | 319834775948 | +11.40% | +14.35% | +14.32% | +15.13% | + +### Mem + +| Benchmark | `v1.0.29` | vs `v1.0.25` | vs `v1.0.23` | vs `v1.0.21` | vs `v1.0.18` | +| --- | ---: | ---: | ---: | ---: | ---: | +| `clausify_f1` | 53769377 | -2.29% | +20.40% | +20.40% | +20.63% | +| `clausify_f2` | 67108683 | -2.54% | +19.63% | +19.63% | +19.87% | +| `clausify_f3` | 179606857 | -2.64% | +19.38% | +19.38% | +19.61% | +| `clausify_f4` | 231444137 | -2.75% | +16.33% | +16.33% | +16.64% | +| `clausify_f5` | 874286879 | -2.63% | +19.53% | +19.53% | +19.72% | +| `knights_100_4x4` | 172256715 | -0.48% | +5.04% | +4.90% | +5.80% | +| `knights_100_6x6` | 321712271 | +10.08% | +16.54% | +16.46% | +18.29% | +| `knights_100_8x8` | 601065675 | +14.48% | +21.30% | +21.26% | +23.15% | diff --git a/benchmarks/aiken.lock b/benchmarks/aiken.lock new file mode 100644 index 00000000..62f2265a --- /dev/null +++ b/benchmarks/aiken.lock @@ -0,0 +1,15 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "1.9.0" +requirements = [] +source = "github" + +[etags] diff --git a/benchmarks/aiken.toml b/benchmarks/aiken.toml new file mode 100644 index 00000000..b246c981 --- /dev/null +++ b/benchmarks/aiken.toml @@ -0,0 +1,14 @@ +name = "aiken/benchmarks" +version = "0.0.0" +license = "Apache-2.0" +description = "Aiken contracts for project 'aiken/benchmarks'" + +[repository] +user = "aiken" +project = "benchmarks" +platform = "github" + +[[dependencies]] +name = "aiken-lang/stdlib" +version = "1.9.0" +source = "github" diff --git a/benchmarks/lib/benchmarks/clausify/benchmark.ak b/benchmarks/lib/benchmarks/clausify/benchmark.ak new file mode 100644 index 00000000..4ffd36c5 --- /dev/null +++ b/benchmarks/lib/benchmarks/clausify/benchmark.ak @@ -0,0 +1,304 @@ +use aiken/int +use aiken/list + +// ------------------------------------------------------------------ Benchmarks + +test bench_clausify_f1() { + run_clausify(F1) == [] +} + +test bench_clausify_f2() { + run_clausify(F2) == [] +} + +test bench_clausify_f3() { + run_clausify(F3) == [([1], [])] +} + +test bench_clausify_f4() { + run_clausify(F4) == [ + ([1], [2, 3, 4, 5, 6, 7]), + ([1, 2, 3], [4, 5, 6, 7]), + ([1, 2, 3, 4, 5], [6, 7]), + ([1, 2, 3, 4, 5, 6, 7], []), + ([1, 2, 3, 4, 6], [5, 7]), + ([1, 2, 3, 4, 7], [5, 6]), + ([1, 2, 3, 5, 6], [4, 7]), + ([1, 2, 3, 5, 7], [4, 6]), + ([1, 2, 3, 6, 7], [4, 5]), + ([1, 2, 4], [3, 5, 6, 7]), + ([1, 2, 4, 5, 6], [3, 7]), + ([1, 2, 4, 5, 7], [3, 6]), + ([1, 2, 4, 6, 7], [3, 5]), + ([1, 2, 5], [3, 4, 6, 7]), + ([1, 2, 5, 6, 7], [3, 4]), + ([1, 2, 6], [3, 4, 5, 7]), + ([1, 2, 7], [3, 4, 5, 6]), + ([1, 3, 4], [2, 5, 6, 7]), + ([1, 3, 4, 5, 6], [2, 7]), + ([1, 3, 4, 5, 7], [2, 6]), + ([1, 3, 4, 6, 7], [2, 5]), + ([1, 3, 5], [2, 4, 6, 7]), + ([1, 3, 5, 6, 7], [2, 4]), + ([1, 3, 6], [2, 4, 5, 7]), + ([1, 3, 7], [2, 4, 5, 6]), + ([1, 4, 5], [2, 3, 6, 7]), + ([1, 4, 5, 6, 7], [2, 3]), + ([1, 4, 6], [2, 3, 5, 7]), + ([1, 4, 7], [2, 3, 5, 6]), + ([1, 5, 6], [2, 3, 4, 7]), + ([1, 5, 7], [2, 3, 4, 6]), + ([1, 6, 7], [2, 3, 4, 5]), + ([2], [1, 3, 4, 5, 6, 7]), + ([2, 3, 4], [1, 5, 6, 7]), + ([2, 3, 4, 5, 6], [1, 7]), + ([2, 3, 4, 5, 7], [1, 6]), + ([2, 3, 4, 6, 7], [1, 5]), + ([2, 3, 5], [1, 4, 6, 7]), + ([2, 3, 5, 6, 7], [1, 4]), + ([2, 3, 6], [1, 4, 5, 7]), + ([2, 3, 7], [1, 4, 5, 6]), + ([2, 4, 5], [1, 3, 6, 7]), + ([2, 4, 5, 6, 7], [1, 3]), + ([2, 4, 6], [1, 3, 5, 7]), + ([2, 4, 7], [1, 3, 5, 6]), + ([2, 5, 6], [1, 3, 4, 7]), + ([2, 5, 7], [1, 3, 4, 6]), + ([2, 6, 7], [1, 3, 4, 5]), + ([3], [1, 2, 4, 5, 6, 7]), + ([3, 4, 5], [1, 2, 6, 7]), + ([3, 4, 5, 6, 7], [1, 2]), + ([3, 4, 6], [1, 2, 5, 7]), + ([3, 4, 7], [1, 2, 5, 6]), + ([3, 5, 6], [1, 2, 4, 7]), + ([3, 5, 7], [1, 2, 4, 6]), + ([3, 6, 7], [1, 2, 4, 5]), + ([4], [1, 2, 3, 5, 6, 7]), + ([4, 5, 6], [1, 2, 3, 7]), + ([4, 5, 7], [1, 2, 3, 6]), + ([4, 6, 7], [1, 2, 3, 5]), + ([5], [1, 2, 3, 4, 6, 7]), + ([5, 6, 7], [1, 2, 3, 4]), + ([6], [1, 2, 3, 4, 5, 7]), + ([7], [1, 2, 3, 4, 5, 6]), + ] +} + +test bench_clausify_f5() { + run_clausify(F5) == [] +} + +// ----------------------------------------------------------------------- Setup + +type Var = + Int + +type LRVars = + (List, List) + +type Formula { + Sym(Var) + Not(Formula) + Dis(Formula, Formula) + Con(Formula, Formula) + Imp(Formula, Formula) + Eqv(Formula, Formula) +} + +type StaticFormula { + F1 + F2 + F3 + F4 + F5 +} + +fn run_clausify(static_formula: StaticFormula) -> List { + static_formula + |> get_formula + |> clauses +} + +fn get_formula(static_formula: StaticFormula) -> Formula { + when static_formula is { + // (a = a) = (a = a) = (a = a) + F1 -> + Eqv(Eqv(Sym(1), Sym(1)), Eqv(Eqv(Sym(1), Sym(1)), Eqv(Sym(1), Sym(1)))) + + // (a = a = a) = (a = a = a) + F2 -> + Eqv(Eqv(Sym(1), Eqv(Sym(1), Sym(1))), Eqv(Sym(1), Eqv(Sym(1), Sym(1)))) + + // (a = a = a) = (a = a) = (a = a) + F3 -> + Eqv( + Eqv(Sym(1), Eqv(Sym(1), Sym(1))), + Eqv(Eqv(Sym(1), Sym(1)), Eqv(Sym(1), Sym(1))), + ) + + // (a = b = c) = (d = e) = (f = g) + F4 -> + Eqv( + Eqv(Sym(1), Eqv(Sym(2), Sym(3))), + Eqv(Eqv(Sym(4), Sym(5)), Eqv(Sym(6), Sym(7))), + ) + + // (a = a = a) = (a = a = a) = (a = a) + F5 -> + Eqv( + Eqv(Sym(1), Eqv(Sym(1), Sym(1))), + Eqv(Eqv(Sym(1), Eqv(Sym(1), Sym(1))), Eqv(Sym(1), Sym(1))), + ) + } +} + +fn clauses(formula: Formula) -> List { + formula + |> elim + |> negin + |> disin + |> split + |> unicl +} + +fn clause(formula: Formula) -> LRVars { + do_clause(formula, ([], [])) +} + +fn do_clause(formula: Formula, vars: LRVars) -> LRVars { + when formula is { + Dis(p, q) -> do_clause(p, do_clause(q, vars)) + Sym(s) -> (insert_ordered(vars.1st, s, int.compare), vars.2nd) + Not(Sym(s)) -> (vars.1st, insert_ordered(vars.2nd, s, int.compare)) + _ -> fail + } +} + +fn conjunct(formula: Formula) -> Bool { + when formula is { + Con(_, _) -> True + _ -> False + } +} + +/// eliminate connectives other than not, disjunction and conjunction +fn elim(formula: Formula) -> Formula { + when formula is { + Sym(s) -> Sym(s) + Not(p) -> Not(elim(p)) + Dis(p, q) -> Dis(elim(p), elim(q)) + Con(p, q) -> Con(elim(p), elim(q)) + Imp(p, q) -> Dis(Not(elim(p)), elim(q)) + Eqv(f1, f2) -> Con(elim(Imp(f1, f2)), elim(Imp(f2, f1))) + } +} + +/// -- shift negation to innermost positions +fn negin(formula: Formula) -> Formula { + when formula is { + Not(Not(p)) -> negin(p) + Not(Con(p, q)) -> Dis(negin(Not(p)), negin(Not(q))) + Not(Dis(p, q)) -> Con(negin(Not(p)), negin(Not(q))) + Dis(p, q) -> Dis(negin(p), negin(q)) + Con(p, q) -> Con(negin(p), negin(q)) + p -> p + } +} + +/// shift disjunction within conjunction +fn disin(formula: Formula) -> Formula { + when formula is { + Dis(p, Con(q, r)) -> Con(disin(Dis(p, q)), disin(Dis(p, r))) + Dis(Con(p, q), r) -> Con(disin(Dis(p, r)), disin(Dis(q, r))) + Dis(p, q) -> { + let dp = disin(p) + let dq = disin(q) + if conjunct(dp) || conjunct(dq) { + disin(Dis(dp, dq)) + } else { + Dis(dp, dq) + } + } + Con(p, q) -> Con(disin(p), disin(q)) + p -> p + } +} + +/// split conjunctive proposition into a list of conjuncts +fn split(formula: Formula) -> List { + do_split(formula, []) +} + +fn do_split(f: Formula, fs: List) -> List { + when f is { + Con(p, q) -> do_split(p, do_split(q, fs)) + _ -> + [f, ..fs] + } +} + +/// form unique clausal axioms excluding tautologies +fn unicl(formulas: List) -> List { + list.foldr( + formulas, + [], + fn(p, xs) { + let cp = clause(p) + if tautclause(cp) { + xs + } else { + insert_ordered(xs, cp, compare_lr_vars) + } + }, + ) +} + +/// does any symbol appear in both consequent and antecedent of clause +fn tautclause(var: LRVars) -> Bool { + list.any(var.1st, list.has(var.2nd, _)) +} + +/// insertion of an item into an ordered list +fn insert_ordered(es: List, e: a, compare: fn(a, a) -> Ordering) -> List { + when es is { + [] -> + [e] + [head, ..tail] -> + when compare(e, head) is { + Less -> + [e, ..es] + Greater -> + [head, ..insert_ordered(tail, e, compare)] + Equal -> es + } + } +} + +fn compare_lr_vars(a: LRVars, b: LRVars) -> Ordering { + when compare_list(a.1st, b.1st) is { + Equal -> compare_list(a.2nd, b.2nd) + ord -> ord + } +} + +fn compare_list(xs: List, ys: List) -> Ordering { + when xs is { + [] -> + when ys is { + [] -> Equal + _ -> Less + } + [x, ..xs] -> + when ys is { + [] -> Greater + [y, ..ys] -> { + let ord = int.compare(x, y) + if ord == Equal { + compare_list(xs, ys) + } else { + ord + } + } + } + } +} diff --git a/benchmarks/lib/benchmarks/knights/benchmark.ak b/benchmarks/lib/benchmarks/knights/benchmark.ak new file mode 100644 index 00000000..15bea26e --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/benchmark.ak @@ -0,0 +1,496 @@ +use aiken/list +use benchmarks/knights/heuristic.{descendants, start_tour, tour_finished} +use benchmarks/knights/queue.{Queue} +use benchmarks/knights/types.{ChessSet, Solution} + +// ------------------------------------------------------------------ Benchmarks + +test bench_knights_100_4x4() { + run_knights(100, 4) == [] +} + +test bench_knights_100_6x6() { + run_knights(100, 6) == solution_100_6x6() +} + +test bench_knights_100_8x8() { + run_knights(100, 8) == solution_100_8x8() +} + +// ----------------------------------------------------------------------- Setup + +fn run_knights(depth: Int, board_size: Int) -> Solution { + depth_search(depth, root(board_size), grow, done) |> queue.to_list +} + +fn depth_search( + depth: Int, + xs: Queue, + grow: fn(a) -> List, + done: fn(a) -> Bool, +) -> Queue { + if depth == 0 || queue.is_empty(xs) { + queue.new() + } else if done(queue.head(xs)) { + depth_search(depth - 1, queue.remove_front(xs), grow, done) + |> queue.append_front(queue.head(xs)) + } else { + queue.append_all_front(queue.remove_front(xs), grow(queue.head(xs))) + |> depth_search(depth - 1, _, grow, done) + } +} + +fn root(size: Int) -> Queue<(Int, ChessSet)> { + queue.append_all_front(queue.new(), mk_starts(size)) +} + +fn mk_starts(size: Int) -> List<(Int, ChessSet)> { + let it = interval(1, size) + + let l = + list.flat_map( + it, + fn(x) { list.map(it, fn(y) { start_tour((x, y), size) }) }, + ) + + let length = list.length(l) + + expect length == size * size + + list.zip(list.repeat(1 - length, length), l) +} + +fn interval(a: Int, b: Int) -> List { + if a > b { + [] + } else { + [a, ..interval(a + 1, b)] + } +} + +fn grow(item: (Int, ChessSet)) -> List<(Int, ChessSet)> { + let (x, y) = item + descendants(y) |> list.map(fn(list_item) { (x + 1, list_item) }) +} + +fn done(item: (Int, ChessSet)) -> Bool { + tour_finished(item.2nd) +} + +// ------------------------------------------------------------------ Fixtures + +fn solution_100_6x6() -> Solution { + [ + ( + 0, + ChessSet { + size: 6, + move_number: 36, + start: Some((1, 1)), + visited: [ + (3, 2), + (5, 3), + (6, 1), + (4, 2), + (3, 4), + (2, 6), + (4, 5), + (6, 6), + (5, 4), + (6, 2), + (4, 1), + (2, 2), + (1, 4), + (3, 3), + (2, 1), + (1, 3), + (2, 5), + (4, 6), + (6, 5), + (4, 4), + (5, 2), + (6, 4), + (5, 6), + (3, 5), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (4, 3), + (5, 1), + (6, 3), + (5, 5), + (3, 6), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ( + 0, + ChessSet { + size: 6, + move_number: 36, + start: Some((1, 1)), + visited: [ + (3, 2), + (5, 3), + (6, 1), + (4, 2), + (3, 4), + (2, 2), + (4, 1), + (6, 2), + (5, 4), + (6, 6), + (4, 5), + (2, 6), + (1, 4), + (3, 3), + (2, 1), + (1, 3), + (2, 5), + (4, 6), + (6, 5), + (4, 4), + (5, 2), + (6, 4), + (5, 6), + (3, 5), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (4, 3), + (5, 1), + (6, 3), + (5, 5), + (3, 6), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ( + 0, + ChessSet { + size: 6, + move_number: 36, + start: Some((1, 1)), + visited: [ + (3, 2), + (5, 3), + (6, 1), + (4, 2), + (3, 4), + (2, 2), + (1, 4), + (2, 6), + (4, 5), + (6, 6), + (5, 4), + (6, 2), + (4, 1), + (3, 3), + (2, 1), + (1, 3), + (2, 5), + (4, 6), + (6, 5), + (4, 4), + (5, 2), + (6, 4), + (5, 6), + (3, 5), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (4, 3), + (5, 1), + (6, 3), + (5, 5), + (3, 6), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ( + 0, + ChessSet { + size: 6, + move_number: 36, + start: Some((1, 1)), + visited: [ + (3, 2), + (5, 3), + (6, 1), + (4, 2), + (3, 4), + (2, 6), + (1, 4), + (2, 2), + (4, 1), + (6, 2), + (5, 4), + (6, 6), + (4, 5), + (3, 3), + (2, 1), + (1, 3), + (2, 5), + (4, 6), + (6, 5), + (4, 4), + (5, 2), + (6, 4), + (5, 6), + (3, 5), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (4, 3), + (5, 1), + (6, 3), + (5, 5), + (3, 6), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ] +} + +fn solution_100_8x8() -> Solution { + [ + ( + 0, + ChessSet { + size: 8, + move_number: 64, + start: Some((1, 1)), + visited: [ + (3, 2), + (4, 4), + (5, 6), + (6, 4), + (8, 5), + (7, 7), + (6, 5), + (8, 4), + (7, 2), + (5, 3), + (3, 4), + (4, 6), + (5, 8), + (6, 6), + (4, 5), + (3, 7), + (1, 8), + (2, 6), + (4, 7), + (5, 5), + (6, 3), + (5, 1), + (4, 3), + (3, 5), + (5, 4), + (7, 3), + (8, 1), + (6, 2), + (4, 1), + (2, 2), + (1, 4), + (3, 3), + (2, 5), + (1, 3), + (2, 1), + (4, 2), + (6, 1), + (8, 2), + (7, 4), + (8, 6), + (7, 8), + (5, 7), + (3, 8), + (1, 7), + (3, 6), + (2, 8), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (5, 2), + (7, 1), + (8, 3), + (7, 5), + (8, 7), + (6, 8), + (7, 6), + (8, 8), + (6, 7), + (4, 8), + (2, 7), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ( + 0, + ChessSet { + size: 8, + move_number: 64, + start: Some((1, 1)), + visited: [ + (3, 2), + (4, 4), + (5, 6), + (7, 7), + (8, 5), + (6, 4), + (7, 2), + (8, 4), + (6, 5), + (5, 3), + (3, 4), + (4, 6), + (5, 8), + (6, 6), + (4, 5), + (3, 7), + (1, 8), + (2, 6), + (4, 7), + (5, 5), + (6, 3), + (5, 1), + (4, 3), + (3, 5), + (5, 4), + (7, 3), + (8, 1), + (6, 2), + (4, 1), + (2, 2), + (1, 4), + (3, 3), + (2, 5), + (1, 3), + (2, 1), + (4, 2), + (6, 1), + (8, 2), + (7, 4), + (8, 6), + (7, 8), + (5, 7), + (3, 8), + (1, 7), + (3, 6), + (2, 8), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (5, 2), + (7, 1), + (8, 3), + (7, 5), + (8, 7), + (6, 8), + (7, 6), + (8, 8), + (6, 7), + (4, 8), + (2, 7), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ( + 0, + ChessSet { + size: 8, + move_number: 64, + start: Some((1, 1)), + visited: [ + (3, 2), + (4, 4), + (6, 5), + (8, 4), + (7, 2), + (5, 3), + (3, 4), + (4, 6), + (5, 8), + (7, 7), + (5, 6), + (6, 4), + (8, 5), + (6, 6), + (4, 5), + (3, 7), + (1, 8), + (2, 6), + (4, 7), + (5, 5), + (6, 3), + (5, 1), + (4, 3), + (3, 5), + (5, 4), + (7, 3), + (8, 1), + (6, 2), + (4, 1), + (2, 2), + (1, 4), + (3, 3), + (2, 5), + (1, 3), + (2, 1), + (4, 2), + (6, 1), + (8, 2), + (7, 4), + (8, 6), + (7, 8), + (5, 7), + (3, 8), + (1, 7), + (3, 6), + (2, 8), + (1, 6), + (2, 4), + (1, 2), + (3, 1), + (5, 2), + (7, 1), + (8, 3), + (7, 5), + (8, 7), + (6, 8), + (7, 6), + (8, 8), + (6, 7), + (4, 8), + (2, 7), + (1, 5), + (2, 3), + (1, 1), + ], + }, + ), + ] +} diff --git a/benchmarks/lib/benchmarks/knights/chess_set.ak b/benchmarks/lib/benchmarks/knights/chess_set.ak new file mode 100644 index 00000000..5639dacc --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/chess_set.ak @@ -0,0 +1,56 @@ +use aiken/list +use benchmarks/knights/types.{ChessSet, Tile} + +pub fn create_board(size: Int, init_square: Tile) -> ChessSet { + ChessSet { + size, + move_number: 1, + start: Some(init_square), + visited: [init_square], + } +} + +pub fn add_piece(board: ChessSet, tile: Tile) -> ChessSet { + ChessSet { + ..board, + move_number: board.move_number + 1, + visited: [tile, ..board.visited], + } +} + +pub fn first_piece(board: ChessSet) -> Tile { + expect Some(tile) = board.start + tile +} + +pub fn last_piece(board: ChessSet) -> Tile { + when board.visited is { + [] -> fail + [x, ..] -> x + } +} + +pub fn delete_first(board: ChessSet) -> ChessSet { + let ChessSet { move_number, visited, .. } = board + + expect Some(new_visited) = list.init(visited) + + ChessSet { + ..board, + move_number: move_number - 1, + start: second_last(visited), + visited: new_visited, + } +} + +fn second_last(visited: List) -> Option { + when list.reverse(visited) is { + [] -> fail + [_] -> None + [_, a, ..] -> Some(a) + } +} + +pub fn is_square_free(board: ChessSet, tile: Tile) -> Bool { + !list.has(board.visited, tile) +} diff --git a/benchmarks/lib/benchmarks/knights/heuristic.ak b/benchmarks/lib/benchmarks/knights/heuristic.ak new file mode 100644 index 00000000..efcfb2b1 --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/heuristic.ak @@ -0,0 +1,172 @@ +use aiken/builtin +use aiken/int +use aiken/list +use benchmarks/knights/chess_set.{ + add_piece, create_board, delete_first, first_piece, is_square_free, last_piece, +} +use benchmarks/knights/sort.{quicksort} +use benchmarks/knights/types.{ChessSet, Tile} + +type Direction { + UL + UR + DL + DR + LU + LD + RU + RD +} + +fn direction_list() { + [UL, UR, DL, DR, LU, LD, RU, RD] +} + +fn move(direction: Direction, tile: Tile) -> Tile { + let (x, y) = tile + when direction is { + UL -> (x - 1, y - 2) + UR -> (x + 1, y - 2) + DL -> (x - 1, y + 2) + DR -> (x + 1, y + 2) + LU -> (x - 2, y - 1) + LD -> (x - 2, y + 1) + RU -> (x + 2, y - 1) + RD -> (x + 2, y + 1) + } +} + +pub fn start_tour(st: Tile, size: Int) -> ChessSet { + expect 0 = builtin.remainder_integer(size, 2) + create_board(size, st) +} + +pub fn tour_finished(board: ChessSet) -> Bool { + let ChessSet { move_number, size, .. } = board + move_number == size * size && can_jump_first(board) +} + +pub fn descendants(board: ChessSet) -> List { + if and { + can_jump_first(board), + board + |> first_piece + |> add_piece(board, _) + |> dead_end, + } { + [] + } else { + let singles = single_descend(board) + + when singles is { + [] -> + board + |> desc_and_no + |> quicksort(compare_chess_set) + |> list.map(fn(t) { t.2nd }) + [_] -> singles + _ -> + [] + } + } +} + +pub fn can_jump_first(board: ChessSet) -> Bool { + can_move_to(delete_first(board), first_piece(board)) +} + +pub fn dead_end(board: ChessSet) -> Bool { + board |> possible_moves |> builtin.null_list +} + +pub fn single_descend(board: ChessSet) -> List { + list.filter_map( + desc_and_no(board), + fn(item) { + let (moves, board) = item + if moves == 1 { + Some(board) + } else { + None + } + }, + ) +} + +pub fn desc_and_no(board: ChessSet) -> List<(Int, ChessSet)> { + board + |> all_descend + |> list.map( + fn(item) { + (item |> delete_first |> possible_moves |> list.length, item) + }, + ) +} + +pub fn can_move_to(board: ChessSet, tile: Tile) -> Bool { + let (x, y) = tile + let size = board.size + and { + x >= 1, + x <= size, + y >= 1, + y <= size, + is_square_free(board, tile), + } +} + +fn can_move(board: ChessSet, direction: Direction) -> Bool { + board |> can_move_to(move(direction, last_piece(board))) +} + +pub fn all_descend(board: ChessSet) -> List { + board + |> possible_moves + |> list.map(move_knight(board, _)) +} + +fn move_knight(board: ChessSet, direction: Direction) -> ChessSet { + add_piece(board, move(direction, last_piece(board))) +} + +fn possible_moves(board: ChessSet) -> List { + direction_list() |> list.filter(can_move(board, _)) +} + +fn compare_chess_set(a: (Int, ChessSet), b: (Int, ChessSet)) -> Ordering { + if a.1st != b.1st { + int.compare(a.1st, b.1st) + } else { + compare_list(a.2nd.visited, b.2nd.visited) + } +} + +fn compare_list(xs: List, ys: List) -> Ordering { + when xs is { + [] -> + when ys is { + [] -> Equal + _ -> Less + } + [x, ..xs] -> + when ys is { + [] -> Greater + [y, ..ys] -> { + let ord = compare_tile(x, y) + if ord == Equal { + compare_list(xs, ys) + } else { + ord + } + } + } + } +} + +fn compare_tile(a: Tile, b: Tile) -> Ordering { + if a.1st == b.1st { + int.compare(a.2nd, b.2nd) + } else { + int.compare(a.1st, b.1st) + } +} diff --git a/benchmarks/lib/benchmarks/knights/queue.ak b/benchmarks/lib/benchmarks/knights/queue.ak new file mode 100644 index 00000000..0abd9c76 --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/queue.ak @@ -0,0 +1,38 @@ +use aiken/list + +pub opaque type Queue { + inner: List, +} + +pub fn new() -> Queue { + [] |> Queue +} + +pub fn to_list(self: Queue) -> List { + self.inner +} + +pub fn is_empty(self: Queue) -> Bool { + when self.inner is { + [] -> True + _ -> False + } +} + +pub fn append_front(self: Queue, item: a) -> Queue { + list.push(self.inner, item) |> Queue +} + +pub fn append_all_front(self: Queue, items: List) -> Queue { + list.concat(items, self.inner) |> Queue +} + +pub fn remove_front(self: Queue) -> Queue { + expect [_, ..rest] = self.inner + rest |> Queue +} + +pub fn head(self: Queue) -> a { + expect [q, ..] = self.inner + q +} diff --git a/benchmarks/lib/benchmarks/knights/sort.ak b/benchmarks/lib/benchmarks/knights/sort.ak new file mode 100644 index 00000000..40a59c8f --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/sort.ak @@ -0,0 +1,19 @@ +use aiken/list + +pub fn quicksort(xs: List, compare: fn(a, a) -> Ordering) -> List { + when xs is { + [] -> + [] + [head, ..tail] -> { + let before = + tail + |> list.filter(fn(x) { compare(x, head) == Less }) + |> quicksort(compare) + let after = + tail + |> list.filter(fn(x) { compare(x, head) != Less }) + |> quicksort(compare) + list.concat(before, [head, ..after]) + } + } +} diff --git a/benchmarks/lib/benchmarks/knights/types.ak b/benchmarks/lib/benchmarks/knights/types.ak new file mode 100644 index 00000000..16b50c6d --- /dev/null +++ b/benchmarks/lib/benchmarks/knights/types.ak @@ -0,0 +1,12 @@ +pub type Tile = + (Int, Int) + +pub type ChessSet { + size: Int, + move_number: Int, + start: Option, + visited: List, +} + +pub type Solution = + List<(Int, ChessSet)> diff --git a/benchmarks/plutus.json b/benchmarks/plutus.json new file mode 100644 index 00000000..5c4319e1 --- /dev/null +++ b/benchmarks/plutus.json @@ -0,0 +1,14 @@ +{ + "preamble": { + "title": "aiken/benchmarks", + "description": "Aiken contracts for project 'aiken/benchmarks'", + "version": "0.0.0", + "plutusVersion": "v2", + "compiler": { + "name": "Aiken", + "version": "v1.0.21-alpha+4b04517" + }, + "license": "Apache-2.0" + }, + "validators": [] +} \ No newline at end of file