diff --git a/.factory/automation.yml b/.factory/automation.yml index 7f880da6..2258324c 100644 --- a/.factory/automation.yml +++ b/.factory/automation.yml @@ -69,13 +69,27 @@ build: sudo systemd-run ./dist/typedb-all-linux/typedb server sleep 60 bazel test //telecom/phone_calls/python:test --test_output=errors + test-sudoku: + image: vaticle-ubuntu-22.04 + command: | + export PYENV_ROOT="/opt/pyenv" + pyenv install 3.7.12 + pyenv global 3.7.12 + sudo unlink /usr/bin/python3 + sudo ln -s $(which python3) /usr/bin/python3 + sudo ln -s /usr/bin/python3 /usr/bin/python + sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.7.12/lib/python3.7/site-packages/lsb_release.py + bazel run //:typedb-extractor -- dist/typedb-all-linux + sudo systemd-run ./dist/typedb-all-linux/typedb server + sleep 60 + bazel test //games/sudoku:test --test_output=errors test-xcom: image: vaticle-ubuntu-22.04 command: | bazel run //:typedb-extractor -- dist/typedb-all-linux sudo systemd-run ./dist/typedb-all-linux/typedb server sleep 60 - bazel test //gaming/xcom:test --test_output=errors + bazel test //games/xcom:test --test_output=errors performance: test-performance-typedb-core-server: machine: 16-core-64-gb @@ -159,10 +173,24 @@ build: sudo systemd-run ./dist/typedb-all-linux/typedb server sleep 60 bazel test //telecom/phone_calls/python:test --test_output=errors --runs_per_test=20 --jobs=1 + benchmark-sudoku-test: + image: vaticle-ubuntu-22.04 + command: | + export PYENV_ROOT="/opt/pyenv" + pyenv install 3.7.12 + pyenv global 3.7.12 + sudo unlink /usr/bin/python3 + sudo ln -s $(which python3) /usr/bin/python3 + sudo ln -s /usr/bin/python3 /usr/bin/python + sudo ln -s /usr/share/pyshared/lsb_release.py /opt/pyenv/versions/3.7.12/lib/python3.7/site-packages/lsb_release.py + bazel run //:typedb-extractor -- dist/typedb-all-linux + sudo systemd-run ./dist/typedb-all-linux/typedb server + sleep 60 + bazel test //games/sudoku:test --test_output=errors --runs_per_test=20 --jobs=1 benchmark-xcom-test: image: vaticle-ubuntu-22.04 command: | bazel run //:typedb-extractor -- dist/typedb-all-linux sudo systemd-run ./dist/typedb-all-linux/typedb server sleep 60 - bazel test //gaming/xcom:test --test_output=errors --runs_per_test=20 --jobs=1 + bazel test //games/xcom:test --test_output=errors --runs_per_test=20 --jobs=1 diff --git a/README.md b/README.md index efdfdd42..5154c6d4 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,15 @@ Read the [README](commerce/bookstore/README.md) file for instructions. Check [th or the initial [dataset](commerce/bookstore/python/data) for additional information. All logic accessible in the script files in the [python](commerce/bookstore/python) directory. -### [Gaming: XCOM Project](gaming/xcom) +### [Games: Sudoku](games/sudoku) + +The sudoku example demonstrates the ability of TypeDB's reasoner to solve a smaller 6x6 version of the classic puzzle. +The sudoku is passed to the driver program in a text file and solved by querying a simple TypeDB database. + +### [Games: XCOM Project](games/xcom) The XCOM 2 example contains a database of interdependent research tasks in the game XCOM 2, featuring automatic -inference of available research based on completed tasks and available items. See [the schema](gaming/xcom/schema.tql) +inference of available research based on completed tasks and available items. See [the schema](games/xcom/schema.tql) for the examples of reasoner rules inferring attributes. ### [Software: GitHub](software/github) diff --git a/WORKSPACE b/WORKSPACE index f36b8e91..1ffc4be0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -145,6 +145,11 @@ pip_install( requirements = "//telecom/phone_calls/python:requirements.txt" ) +pip_install( + name = "sudoku_pip", + requirements = "//games/sudoku:requirements.txt" +) + # Load java example dependencies load("//dependencies/maven:artifacts.bzl", vaticle_typedb_examples_maven_artifacts = "artifacts") diff --git a/games/sudoku/BUILD b/games/sudoku/BUILD new file mode 100644 index 00000000..4c693f3f --- /dev/null +++ b/games/sudoku/BUILD @@ -0,0 +1,66 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +exports_files(["requirements.txt"]) + +load("@rules_python//python:python.bzl", "py_binary", "py_test") +load("@sudoku_pip//:requirements.bzl", sudoku_requirement = "requirement") + +# Define targets +py_library( + name = "solver", + srcs = ["typedb_sudoku.py"], + deps = [ + sudoku_requirement("typedb-client") + ], + data = [ + ":sudoku6x6_schema.tql", + ":sudoku6x6_data.tql", + ] +) + +py_test( + name = "test", + srcs = ["test.py"], + deps = [ + "solver", + sudoku_requirement("typedb-client") + ], + data = [ + "sample/sudoku1.txt", + "sample/sudoku2.txt", + "sample/sudoku3.txt", + "sample/sudoku4.txt", + "sample/solution1.txt", + "sample/solution2.txt", + "sample/solution3.txt", + "sample/solution4.txt", + ] +) + +# Define checkstyle +load("@vaticle_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") +checkstyle_test( + name = "checkstyle", + include = glob(["*"]), + exclude = glob(["*.txt", "*.md", "sample/*"]), + license_type = "apache-header", + size = "small", +) diff --git a/games/sudoku/readme.md b/games/sudoku/readme.md new file mode 100644 index 00000000..03ecd372 --- /dev/null +++ b/games/sudoku/readme.md @@ -0,0 +1,133 @@ +# Sudoku +In this example, we consider a smaller 6x6 version of the sudoku. +The aim is to fill in the grid with numbers from 1 to 6 such that no row, column or box contains the same number twice. +Boxes are 2 rows tall x 3 columns wide. +Sudoku is a popular puzzle involving a 9x9 grid. +Some of the numbers will be filled in as part of the puzzle. + + +## Usage +The driver program requires an instance of typedb server to be running. +If you are not running TypeDB locally on the default port, specify the address as the second commandline parameter. + +### Setup +1. Install the dependencies: + * `python -m pip requirements.txt` +2. Create the database with required schema and data: + * `python driver.py setup [typedb-address]` + +### Running the solver +`python driver.py [typedb-address]` + +### Running tests +`bazel test //games/sudoku:test` or `python test.py` + +## Defining the sudoku file +The file specified through the `sudoku-path` argument must contain a space +delimited list of cells. Empty cells should be filled with `0`. + +Example, for the sudoku: + +| | | 3 | 6 | | | +|---|---|---|---|---|---| +| | 2 | | | | 4 | +| 5 | | | | 6 | | +| | 3 | | | | 5 | +| 3 | | | | 1 | | +| | | 1 | 4 | | | + +The file would contain: +``` +0 0 3 6 0 0 +0 2 0 0 0 4 +5 0 0 0 6 0 +0 3 0 0 0 5 +3 0 0 0 1 0 +0 0 1 4 0 0 +``` + +## Approach +The work is done in the `solution-rule`. + +Each row, column and box is constrained to be a permutation of the numbers 1 to 6. +We have one variable corresponding to each position in the sudoku. + +| - | a | b | c | d | e | f | +|---|---|---|---|---|---|---| +| 1 |$a1|$b1|$c1|$d1|$e1|$f1| +| 2 |$a2|$b2|$c2|$d2|$e2|$f2| +| 3 |$a3|$b3|$c3|$d3|$e3|$f3| +| 4 |$a4|$b4|$c4|$d4|$e4|$f4| +| 5 |$a5|$b5|$c5|$d5|$e5|$f5| +| 6 |$a6|$b6|$c6|$d6|$e6|$f6| + + +```typeql +rule solution-rule: +when { + # Each row must be a valid permutation + (mem: $a1, mem: $b1, mem: $c1, mem: $d1, mem: $e1, mem: $f1) isa permutation; + (mem: $a2, mem: $b2, mem: $c2, mem: $d2, mem: $e2, mem: $f2) isa permutation; + (mem: $a3, mem: $b3, mem: $c3, mem: $d3, mem: $e3, mem: $f3) isa permutation; + (mem: $a4, mem: $b4, mem: $c4, mem: $d4, mem: $e4, mem: $f4) isa permutation; + (mem: $a5, mem: $b5, mem: $c5, mem: $d5, mem: $e5, mem: $f5) isa permutation; + (mem: $a6, mem: $b6, mem: $c6, mem: $d6, mem: $e6, mem: $f6) isa permutation; + + # Each column must be a valid permutation + (mem: $a1, mem: $a2, mem: $a3, mem: $a4, mem: $a5, mem: $a6) isa permutation; + (mem: $b1, mem: $b2, mem: $b3, mem: $b4, mem: $b5, mem: $b6) isa permutation; + (mem: $c1, mem: $c2, mem: $c3, mem: $c4, mem: $c5, mem: $c6) isa permutation; + (mem: $d1, mem: $d2, mem: $d3, mem: $d4, mem: $d5, mem: $d6) isa permutation; + (mem: $e1, mem: $e2, mem: $e3, mem: $e4, mem: $e5, mem: $e6) isa permutation; + (mem: $f1, mem: $f2, mem: $f3, mem: $f4, mem: $f5, mem: $f6) isa permutation; + + # Each box must be a valid permutation + (mem: $a1, mem: $b1, mem: $c1, mem: $a2, mem: $b2, mem: $c2) isa permutation; + (mem: $a3, mem: $b3, mem: $c3, mem: $a4, mem: $b4, mem: $c4) isa permutation; + (mem: $a5, mem: $b5, mem: $c5, mem: $a6, mem: $b6, mem: $c6) isa permutation; + (mem: $d1, mem: $e1, mem: $f1, mem: $d2, mem: $e2, mem: $f2) isa permutation; + (mem: $d3, mem: $e3, mem: $f3, mem: $d4, mem: $e4, mem: $f4) isa permutation; + (mem: $d5, mem: $e5, mem: $f5, mem: $d6, mem: $e6, mem: $f6) isa permutation; +} then { + ( + pos11: $a1, pos12: $b1, pos13: $c1, pos14: $d1, pos15: $e1, pos16: $f1, + pos21: $a2, pos22: $b2, pos23: $c2, pos24: $d2, pos25: $e2, pos26: $f2, + pos31: $a3, pos32: $b3, pos33: $c3, pos34: $d3, pos35: $e3, pos36: $f3, + pos41: $a4, pos42: $b4, pos43: $c4, pos44: $d4, pos45: $e4, pos46: $f4, + pos51: $a5, pos52: $b5, pos53: $c5, pos54: $d5, pos55: $e5, pos56: $f5, + pos61: $a6, pos62: $b6, pos63: $c6, pos64: $d6, pos65: $e6, pos66: $f6 + ) isa solution; +}; +``` + +In our database, we have a single permutation instance inserted as follows: +```typeql +insert +$v1 = 1 isa number; +$v2 = 2 isa number; +$v3 = 3 isa number; +$v4 = 4 isa number; +$v5 = 5 isa number; +$v6 = 6 isa number; +(mem: $v1, mem: $v2, mem: $v3, mem: $v4, mem: $v5, mem: $v6) isa permutation; +``` + + +To query it, We add a statement for the variables which we know the value of (e.g. `$v13 = 3 isa number;`) and query the rule: +```typeql +match + $v13 = 3 isa number; + $v15 = 1 isa number; + # ... + $v62 = 4 isa number; + $v64 = 1 isa number; + ( + pos11: $v11, pos12: $v12, pos13: $v13, pos14: $v14, pos15: $v15, pos16: $v16, + pos21: $v21, pos22: $v22, pos23: $v23, pos24: $v24, pos25: $v25, pos26: $v26, + pos31: $v31, pos32: $v32, pos33: $v33, pos34: $v34, pos35: $v35, pos36: $v36, + pos41: $v41, pos42: $v42, pos43: $v43, pos44: $v44, pos45: $v45, pos46: $v46, + pos51: $v51, pos52: $v52, pos53: $v53, pos54: $v54, pos55: $v55, pos56: $v56, + pos61: $v61, pos62: $v62, pos63: $v63, pos64: $v64, pos65: $v65, pos66: $v66 + ) isa solution; + limit 1; +``` \ No newline at end of file diff --git a/games/sudoku/requirements.txt b/games/sudoku/requirements.txt new file mode 100644 index 00000000..45b43c04 --- /dev/null +++ b/games/sudoku/requirements.txt @@ -0,0 +1 @@ +typedb-client==2.17.0 diff --git a/games/sudoku/sample/solution1.txt b/games/sudoku/sample/solution1.txt new file mode 100644 index 00000000..2aad1d06 --- /dev/null +++ b/games/sudoku/sample/solution1.txt @@ -0,0 +1,6 @@ +4 5 3 6 2 1 +1 2 6 5 3 4 +5 1 4 3 6 2 +6 3 2 1 4 5 +3 4 5 2 1 6 +2 6 1 4 5 3 \ No newline at end of file diff --git a/games/sudoku/sample/solution2.txt b/games/sudoku/sample/solution2.txt new file mode 100644 index 00000000..79798a87 --- /dev/null +++ b/games/sudoku/sample/solution2.txt @@ -0,0 +1,6 @@ +4 2 3 5 1 6 +5 6 1 3 2 4 +1 5 4 2 6 3 +2 3 6 4 5 1 +3 1 2 6 4 5 +6 4 5 1 3 2 diff --git a/games/sudoku/sample/solution3.txt b/games/sudoku/sample/solution3.txt new file mode 100644 index 00000000..586353cb --- /dev/null +++ b/games/sudoku/sample/solution3.txt @@ -0,0 +1,6 @@ +2 1 6 4 5 3 +4 3 5 1 2 6 +3 6 2 5 4 1 +1 5 4 3 6 2 +6 4 3 2 1 5 +5 2 1 6 3 4 \ No newline at end of file diff --git a/games/sudoku/sample/solution4.txt b/games/sudoku/sample/solution4.txt new file mode 100644 index 00000000..1926bdd0 --- /dev/null +++ b/games/sudoku/sample/solution4.txt @@ -0,0 +1,6 @@ +4 2 3 5 1 6 +5 6 1 3 2 4 +1 5 4 2 6 3 +2 3 6 4 5 1 +3 1 2 6 4 5 +6 4 5 1 3 2 \ No newline at end of file diff --git a/games/sudoku/sample/sudoku1.txt b/games/sudoku/sample/sudoku1.txt new file mode 100644 index 00000000..f724a10e --- /dev/null +++ b/games/sudoku/sample/sudoku1.txt @@ -0,0 +1,6 @@ +0 0 3 6 0 0 +0 2 0 0 0 4 +5 0 0 0 6 0 +0 3 0 0 0 5 +3 0 0 0 1 0 +0 0 1 4 0 0 \ No newline at end of file diff --git a/games/sudoku/sample/sudoku2.txt b/games/sudoku/sample/sudoku2.txt new file mode 100644 index 00000000..2b804e5a --- /dev/null +++ b/games/sudoku/sample/sudoku2.txt @@ -0,0 +1,6 @@ +0 0 3 0 1 0 +5 6 0 3 2 0 +0 5 4 2 0 3 +2 0 6 4 5 0 +0 1 2 0 4 5 +0 4 0 1 0 0 \ No newline at end of file diff --git a/games/sudoku/sample/sudoku3.txt b/games/sudoku/sample/sudoku3.txt new file mode 100644 index 00000000..46b546fe --- /dev/null +++ b/games/sudoku/sample/sudoku3.txt @@ -0,0 +1,6 @@ +0 0 6 0 0 0 +0 0 0 1 0 0 +0 0 2 0 4 1 +0 5 0 0 6 0 +6 4 0 0 0 0 +0 0 1 0 0 0 \ No newline at end of file diff --git a/games/sudoku/sample/sudoku4.txt b/games/sudoku/sample/sudoku4.txt new file mode 100644 index 00000000..2b804e5a --- /dev/null +++ b/games/sudoku/sample/sudoku4.txt @@ -0,0 +1,6 @@ +0 0 3 0 1 0 +5 6 0 3 2 0 +0 5 4 2 0 3 +2 0 6 4 5 0 +0 1 2 0 4 5 +0 4 0 1 0 0 \ No newline at end of file diff --git a/games/sudoku/sudoku6x6_data.tql b/games/sudoku/sudoku6x6_data.tql new file mode 100644 index 00000000..24ebbb3c --- /dev/null +++ b/games/sudoku/sudoku6x6_data.tql @@ -0,0 +1,30 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +insert +$connector-hack = -1 isa connector-hack; +$v1 = 1 isa number; +$v2 = 2 isa number; +$v3 = 3 isa number; +$v4 = 4 isa number; +$v5 = 5 isa number; +$v6 = 6 isa number; +(mem: $v1, mem: $v2, mem: $v3, mem: $v4, mem: $v5, mem: $v6) isa permutation; diff --git a/games/sudoku/sudoku6x6_schema.tql b/games/sudoku/sudoku6x6_schema.tql new file mode 100644 index 00000000..02e129c2 --- /dev/null +++ b/games/sudoku/sudoku6x6_schema.tql @@ -0,0 +1,87 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +define + +# A temporary hack for the reasoner planner to consider all given values in a single retrievable. +connector-hack sub attribute, value long; +permutation sub relation, relates mem; +permutation sub relation, relates mem; +permutation sub relation, relates mem; + +number sub attribute, value long, + plays permutation:mem, + plays solution:pos11, plays solution:pos12, plays solution:pos13, plays solution:pos14, plays solution:pos15, plays solution:pos16, + plays solution:pos21, plays solution:pos22, plays solution:pos23, plays solution:pos24, plays solution:pos25, plays solution:pos26, + plays solution:pos31, plays solution:pos32, plays solution:pos33, plays solution:pos34, plays solution:pos35, plays solution:pos36, + plays solution:pos41, plays solution:pos42, plays solution:pos43, plays solution:pos44, plays solution:pos45, plays solution:pos46, + plays solution:pos51, plays solution:pos52, plays solution:pos53, plays solution:pos54, plays solution:pos55, plays solution:pos56, + plays solution:pos61, plays solution:pos62, plays solution:pos63, plays solution:pos64, plays solution:pos65, plays solution:pos66; + +solution sub relation, + relates pos11, relates pos12, relates pos13, relates pos14, relates pos15, relates pos16, + relates pos21, relates pos22, relates pos23, relates pos24, relates pos25, relates pos26, + relates pos31, relates pos32, relates pos33, relates pos34, relates pos35, relates pos36, + relates pos41, relates pos42, relates pos43, relates pos44, relates pos45, relates pos46, + relates pos51, relates pos52, relates pos53, relates pos54, relates pos55, relates pos56, + relates pos61, relates pos62, relates pos63, relates pos64, relates pos65, relates pos66; + + +# These rules always fail, but makes each of them "concludable" and we hit the the reasoner instead of traversal +rule dummy-triggers-reasoner: +when { + $v = -1 isa number; +} then { + (mem: $v, mem: $v, mem: $v, mem: $v, mem: $v, mem: $v) isa permutation; +}; + +rule solution-rule: +when { + (mem: $a1, mem: $b1, mem: $c1, mem: $d1, mem: $e1, mem: $f1) isa permutation; + (mem: $a2, mem: $b2, mem: $c2, mem: $d2, mem: $e2, mem: $f2) isa permutation; + (mem: $a3, mem: $b3, mem: $c3, mem: $d3, mem: $e3, mem: $f3) isa permutation; + (mem: $a4, mem: $b4, mem: $c4, mem: $d4, mem: $e4, mem: $f4) isa permutation; + (mem: $a5, mem: $b5, mem: $c5, mem: $d5, mem: $e5, mem: $f5) isa permutation; + (mem: $a6, mem: $b6, mem: $c6, mem: $d6, mem: $e6, mem: $f6) isa permutation; + + (mem: $a1, mem: $a2, mem: $a3, mem: $a4, mem: $a5, mem: $a6) isa permutation; + (mem: $b1, mem: $b2, mem: $b3, mem: $b4, mem: $b5, mem: $b6) isa permutation; + (mem: $c1, mem: $c2, mem: $c3, mem: $c4, mem: $c5, mem: $c6) isa permutation; + (mem: $d1, mem: $d2, mem: $d3, mem: $d4, mem: $d5, mem: $d6) isa permutation; + (mem: $e1, mem: $e2, mem: $e3, mem: $e4, mem: $e5, mem: $e6) isa permutation; + (mem: $f1, mem: $f2, mem: $f3, mem: $f4, mem: $f5, mem: $f6) isa permutation; + + (mem: $a1, mem: $b1, mem: $c1, mem: $a2, mem: $b2, mem: $c2) isa permutation; + (mem: $a3, mem: $b3, mem: $c3, mem: $a4, mem: $b4, mem: $c4) isa permutation; + (mem: $a5, mem: $b5, mem: $c5, mem: $a6, mem: $b6, mem: $c6) isa permutation; + (mem: $d1, mem: $e1, mem: $f1, mem: $d2, mem: $e2, mem: $f2) isa permutation; + (mem: $d3, mem: $e3, mem: $f3, mem: $d4, mem: $e4, mem: $f4) isa permutation; + (mem: $d5, mem: $e5, mem: $f5, mem: $d6, mem: $e6, mem: $f6) isa permutation; +} then { + ( + pos11: $a1, pos12: $b1, pos13: $c1, pos14: $d1, pos15: $e1, pos16: $f1, + pos21: $a2, pos22: $b2, pos23: $c2, pos24: $d2, pos25: $e2, pos26: $f2, + pos31: $a3, pos32: $b3, pos33: $c3, pos34: $d3, pos35: $e3, pos36: $f3, + pos41: $a4, pos42: $b4, pos43: $c4, pos44: $d4, pos45: $e4, pos46: $f4, + pos51: $a5, pos52: $b5, pos53: $c5, pos54: $d5, pos55: $e5, pos56: $f5, + pos61: $a6, pos62: $b6, pos63: $c6, pos64: $d6, pos65: $e6, pos66: $f6 + ) isa solution; +}; \ No newline at end of file diff --git a/games/sudoku/sudoku_solver.py b/games/sudoku/sudoku_solver.py new file mode 100644 index 00000000..556cc9e1 --- /dev/null +++ b/games/sudoku/sudoku_solver.py @@ -0,0 +1,61 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import timeit +from sys import argv + +from typedb_sudoku import Solver +from typedb.client import TypeDB + +from utils import read_sudoku, format_sudoku + +DATABASE_NAME = "sudoku6x6" + +def main(): + if len(argv) < 2: + print("Usage:") + print("python3 %s setup [typedb_address] Loads required schema & data" % argv[0]) + print("python3 %s [typedb_address] Reads & solves the sudoku in " % argv[0]) + return + + host = argv[2] if len(argv) >= 3 else TypeDB.DEFAULT_ADDRESS + solver = Solver(host, DATABASE_NAME) + if argv[1] == "setup": + solver.setup(force=True) + return + + solver.setup() + + sudoku = read_sudoku(argv[1]) + print("Solving:") + print(format_sudoku(sudoku), "\n") + + time_start = timeit.default_timer() + solution = solver.solve(sudoku) + time_taken_ms = int((timeit.default_timer() - time_start) * 1000 + 1) + if solution: + print("Found solution in " + str(time_taken_ms) + " ms:") + print(format_sudoku(solution)) + else: + print("No solution (took " + str(time_taken_ms) + " ms)") + + solver.cleanup() + +if __name__=="__main__": main() diff --git a/games/sudoku/test.py b/games/sudoku/test.py new file mode 100644 index 00000000..e0951131 --- /dev/null +++ b/games/sudoku/test.py @@ -0,0 +1,58 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import timeit +import os.path +import unittest + +from typedb.client import TypeDB + +from typedb_sudoku import Solver +from utils import read_sudoku + +class Test(unittest.TestCase): + DATABASE_NAME = "test_sudoku6x6" + SAMPLE_PATH = os.path.join(os.path.dirname(__file__), "sample") + SAMPLES = [("sudoku1.txt", "solution1.txt"), + ("sudoku2.txt", "solution2.txt"), + ("sudoku3.txt", "solution3.txt"), + ("sudoku4.txt", "solution4.txt") + ] + + def setUp(self): + self.solver = Solver(TypeDB.DEFAULT_ADDRESS, Test.DATABASE_NAME) + self.solver.setup(True) + print("Loaded the " + Test.DATABASE_NAME + " schema") + + def test_samples(self): + for (sample_file, solution_file) in Test.SAMPLES: + sudoku = read_sudoku(os.path.join(Test.SAMPLE_PATH, sample_file)) + time_start = timeit.default_timer() + solver_solution = self.solver.solve(sudoku) + print("Solved %s in %d ms"% (sample_file, int(1 + 1000 * (timeit.default_timer() - time_start)))) + expected_solution = read_sudoku(os.path.join(Test.SAMPLE_PATH, solution_file)) + self.assertEqual(expected_solution, solver_solution) + + def tearDown(self): + self.solver.cleanup(True) + + +if __name__ == '__main__': + unittest.main() diff --git a/games/sudoku/typedb_sudoku.py b/games/sudoku/typedb_sudoku.py new file mode 100644 index 00000000..cffd7751 --- /dev/null +++ b/games/sudoku/typedb_sudoku.py @@ -0,0 +1,96 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os.path +from typing import List + +from typedb.client import TypeDB, TypeDBOptions, SessionType, TransactionType + +class Solver: + SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "sudoku6x6_schema.tql") + DATA_FILE = os.path.join(os.path.dirname(__file__), "sudoku6x6_data.tql") + QUERY_TEMPLATE = """ + match + $connector-hack = -1 isa connector-hack; + {0} + ( + {1} + ) isa solution; + limit 1; + """ + + def __init__(self, host: str, db_name: str): + self.client = TypeDB.core_client(host) + self.db_name = db_name + + def database_exists(self): + return self.client.databases().contains(self.db_name) + + def setup(self, force=False): + if self.client.databases().contains(self.db_name): + if force: + self.client.databases().get(self.db_name).delete() + else: + return + + print("Setting up in database: '%s'..." % self.db_name) + self.client.databases().create(self.db_name) + + with open(Solver.SCHEMA_FILE) as f: + schema = f.read() + + with self.client.session(self.db_name, SessionType.SCHEMA) as session: + with session.transaction(TransactionType.WRITE) as tx: + tx.query().define(schema) + tx.commit() + + with open(Solver.DATA_FILE) as f: + data = f.read() + + with self.client.session(self.db_name, SessionType.DATA) as session: + with session.transaction(TransactionType.WRITE) as tx: + tx.query().insert(data) + tx.commit() + + def cleanup(self, delete_database=False): + if delete_database and self.client.databases().contains(self.db_name): + self.client.databases().get(self.db_name).delete() + self.client.close() + + def solve(self, sudoku: List[List[int]]): + # create_query + non_zero = [(i,j,v) for i,row in enumerate(sudoku, 1) for j,v in enumerate(row, 1) if v != 0] + value_assignments = ["$v%d%d = %d isa number; $v%d%d != $connector-hack;"%(i,j,v,i,j) for (i,j,v) in non_zero] + role_players = [ ["pos%d%d: $v%d%d"%(i,j,i,j) for j in range(1,7)] for i in range(1,7) ] + + query = Solver.QUERY_TEMPLATE.format( + "\n ".join(value_assignments), + ",\n ".join(", ".join(rp) for rp in role_players) + ) + + with self.client.session(self.db_name, SessionType.DATA) as session: + with session.transaction(TransactionType.READ, TypeDBOptions().set_infer(True)) as tx: + result = list(tx.query().match(query)) + + if result: + return [ [result[0].get("v%d%d"%(i,j)).get_value() for j in range(1,7)] for i in range(1,7) ] + else: + return None diff --git a/games/sudoku/utils.py b/games/sudoku/utils.py new file mode 100644 index 00000000..8b9a24b7 --- /dev/null +++ b/games/sudoku/utils.py @@ -0,0 +1,30 @@ +# +# Copyright (C) 2022 Vaticle +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +from typing import List + +def read_sudoku(filename: str): + with open(filename) as sudoku_file: + sudoku = [list(map(int, row.split())) for row in sudoku_file if row.strip()] + assert len(sudoku) == 6 and all(len(row)==6 for row in sudoku) + return sudoku + +def format_sudoku(sudoku: List[List[int]]): + return "\n".join(" ".join(map(str, row)) for row in sudoku) diff --git a/gaming/xcom/BUILD b/games/xcom/BUILD similarity index 88% rename from gaming/xcom/BUILD rename to games/xcom/BUILD index 4fbe46c8..ddf1e4e9 100644 --- a/gaming/xcom/BUILD +++ b/games/xcom/BUILD @@ -45,8 +45,8 @@ java_library( java_test( name = "test", - classpath_resources = ["//gaming/xcom/config:logback-xml"], - test_class = "com.vaticle.typedb.example.gaming.xcom.XCOMTest", + classpath_resources = ["//games/xcom/config:logback-xml"], + test_class = "com.vaticle.typedb.example.games.xcom.XCOMTest", srcs = [ "XCOMTest.java" ], @@ -68,8 +68,8 @@ java_test( java_binary( name = "migration", - classpath_resources = ["//gaming/xcom/config:logback-xml"], - main_class = "com.vaticle.typedb.example.gaming.xcom.Migration", + classpath_resources = ["//games/xcom/config:logback-xml"], + main_class = "com.vaticle.typedb.example.games.xcom.Migration", srcs = ["Migration.java"], deps = [ "@vaticle_typedb_client_java//:client-java", @@ -84,8 +84,8 @@ java_binary( java_binary( name = "queries", - classpath_resources = ["//gaming/xcom/config:logback-xml"], - main_class = "com.vaticle.typedb.example.gaming.xcom.Queries", + classpath_resources = ["//games/xcom/config:logback-xml"], + main_class = "com.vaticle.typedb.example.games.xcom.Queries", srcs = ["Queries.java", "TransactionMode.java", "ResearchTask.java", "InventoryItem.java", "Result.java"], deps = [ "@vaticle_typedb_client_java//:client-java", diff --git a/gaming/xcom/InventoryItem.java b/games/xcom/InventoryItem.java similarity index 95% rename from gaming/xcom/InventoryItem.java rename to games/xcom/InventoryItem.java index 7aef620f..7c9526c8 100644 --- a/gaming/xcom/InventoryItem.java +++ b/games/xcom/InventoryItem.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; public class InventoryItem { public InventoryItem(String name, long quantity) { diff --git a/gaming/xcom/Migration.java b/games/xcom/Migration.java similarity index 96% rename from gaming/xcom/Migration.java rename to games/xcom/Migration.java index 1cee3968..a3f4b66c 100644 --- a/gaming/xcom/Migration.java +++ b/games/xcom/Migration.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; import com.univocity.parsers.csv.CsvParser; import com.univocity.parsers.csv.CsvParserSettings; @@ -82,7 +82,7 @@ static void connectAndMigrate(Collection inputs, String databaseName) thr TypeDBSession schemaSession = client.session(databaseName, TypeDBSession.Type.SCHEMA); TypeDBTransaction schemaTransaction = schemaSession.transaction(TypeDBTransaction.Type.WRITE); Writer queryBuffer = new StringWriter(); - getReader("gaming/xcom/schema.tql").transferTo(queryBuffer); + getReader("games/xcom/schema.tql").transferTo(queryBuffer); schemaTransaction.query().define(queryBuffer.toString()); schemaTransaction.commit(); System.out.println("\nCreated the database.\n"); @@ -111,7 +111,7 @@ static Collection initialiseInputs() { /** define template for constructing a research project TypeQL insert query */ static Input initialiseTechInput() { - return new Input("gaming/xcom/data/tech") { + return new Input("games/xcom/data/tech") { @Override public String template(Json researchProject) { return "insert $research_project isa research-project, has name " + researchProject.at("name") + ";"; @@ -121,7 +121,7 @@ public String template(Json researchProject) { /** define template for constructing a research project tech requirement TypeQL insert query */ static Input initialiseResearchProjectTechRequirementInput() { - return new Input("gaming/xcom/data/tech_required_tech") { + return new Input("games/xcom/data/tech_required_tech") { @Override public String template(Json techRequirement) { // match tech @@ -137,7 +137,7 @@ public String template(Json techRequirement) { /** define template for constructing an item TypeQL insert query */ static Input initialiseItemInput() { - return new Input("gaming/xcom/data/resource") { + return new Input("games/xcom/data/resource") { @Override public String template(Json item) { return "insert $item isa item, has name " + item.at("name") + ";"; @@ -147,7 +147,7 @@ public String template(Json item) { /** define template for constructing a research project resource cost TypeQL insert query */ static Input initialiseResearchResourceCostInput() { - return new Input("gaming/xcom/data/tech_required_resource") { + return new Input("games/xcom/data/tech_required_resource") { @Override public String template(Json researchCost) { // match tech diff --git a/gaming/xcom/Queries.java b/games/xcom/Queries.java similarity index 99% rename from gaming/xcom/Queries.java rename to games/xcom/Queries.java index 1acbe8fd..16737e05 100644 --- a/gaming/xcom/Queries.java +++ b/games/xcom/Queries.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; import com.vaticle.typedb.client.TypeDB; import com.vaticle.typedb.client.api.TypeDBClient; diff --git a/gaming/xcom/README.md b/games/xcom/README.md similarity index 94% rename from gaming/xcom/README.md rename to games/xcom/README.md index 86bee209..34eb2ad3 100644 --- a/gaming/xcom/README.md +++ b/games/xcom/README.md @@ -55,6 +55,6 @@ is able to fetch the correct answer in one simple query. 1. Checkout this repository: `git clone https://github.com/vaticle/typedb-examples && cd typedb-examples` 2. Start the [TypeDB Server](http://docs.vaticle.com/docs/running-typedb/install-and-run#start-the-typedb-server). -3. Build the example: `bazel build //gaming/xcom/...`. -4. Migrate the dataset into TypeDB: `bazel run //gaming/xcom:migration`. -5. Launch the interactive query runner: `bazel run //gaming/xcom:queries`. +3. Build the example: `bazel build //games/xcom/...`. +4. Migrate the dataset into TypeDB: `bazel run //games/xcom:migration`. +5. Launch the interactive query runner: `bazel run //games/xcom:queries`. diff --git a/gaming/xcom/ResearchTask.java b/games/xcom/ResearchTask.java similarity index 95% rename from gaming/xcom/ResearchTask.java rename to games/xcom/ResearchTask.java index 7e58de26..002c9424 100644 --- a/gaming/xcom/ResearchTask.java +++ b/games/xcom/ResearchTask.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; public class ResearchTask { public ResearchTask(String name, double progressPercent) { diff --git a/gaming/xcom/Result.java b/games/xcom/Result.java similarity index 97% rename from gaming/xcom/Result.java rename to games/xcom/Result.java index 7bc7bdf4..6c00fb6e 100644 --- a/gaming/xcom/Result.java +++ b/games/xcom/Result.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; public class Result { public Result() { diff --git a/gaming/xcom/TransactionMode.java b/games/xcom/TransactionMode.java similarity index 94% rename from gaming/xcom/TransactionMode.java rename to games/xcom/TransactionMode.java index 30072291..af579228 100644 --- a/gaming/xcom/TransactionMode.java +++ b/games/xcom/TransactionMode.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; public enum TransactionMode { READ, diff --git a/gaming/xcom/XCOMTest.java b/games/xcom/XCOMTest.java similarity index 99% rename from gaming/xcom/XCOMTest.java rename to games/xcom/XCOMTest.java index 67de12ef..2d158fef 100644 --- a/gaming/xcom/XCOMTest.java +++ b/games/xcom/XCOMTest.java @@ -19,7 +19,7 @@ * under the License. */ -package com.vaticle.typedb.example.gaming.xcom; +package com.vaticle.typedb.example.games.xcom; import com.vaticle.typedb.client.TypeDB; import com.vaticle.typedb.client.api.TypeDBClient; diff --git a/gaming/xcom/config/BUILD b/games/xcom/config/BUILD similarity index 95% rename from gaming/xcom/config/BUILD rename to games/xcom/config/BUILD index ab5694f2..f3ba4cf8 100644 --- a/gaming/xcom/config/BUILD +++ b/games/xcom/config/BUILD @@ -21,7 +21,7 @@ load("@vaticle_dependencies//tool/checkstyle:rules.bzl", "checkstyle_test") -package(default_visibility = ["//gaming/xcom:__pkg__"]) +package(default_visibility = ["//games/xcom:__pkg__"]) filegroup( name = "logback-xml", diff --git a/gaming/xcom/config/logback.xml b/games/xcom/config/logback.xml similarity index 100% rename from gaming/xcom/config/logback.xml rename to games/xcom/config/logback.xml diff --git a/gaming/xcom/data/README.md b/games/xcom/data/README.md similarity index 100% rename from gaming/xcom/data/README.md rename to games/xcom/data/README.md diff --git a/gaming/xcom/data/event.csv b/games/xcom/data/event.csv similarity index 100% rename from gaming/xcom/data/event.csv rename to games/xcom/data/event.csv diff --git a/gaming/xcom/data/resource.csv b/games/xcom/data/resource.csv similarity index 100% rename from gaming/xcom/data/resource.csv rename to games/xcom/data/resource.csv diff --git a/gaming/xcom/data/tech.csv b/games/xcom/data/tech.csv similarity index 100% rename from gaming/xcom/data/tech.csv rename to games/xcom/data/tech.csv diff --git a/gaming/xcom/data/tech_required_event.csv b/games/xcom/data/tech_required_event.csv similarity index 100% rename from gaming/xcom/data/tech_required_event.csv rename to games/xcom/data/tech_required_event.csv diff --git a/gaming/xcom/data/tech_required_resource.csv b/games/xcom/data/tech_required_resource.csv similarity index 100% rename from gaming/xcom/data/tech_required_resource.csv rename to games/xcom/data/tech_required_resource.csv diff --git a/gaming/xcom/data/tech_required_tech.csv b/games/xcom/data/tech_required_tech.csv similarity index 100% rename from gaming/xcom/data/tech_required_tech.csv rename to games/xcom/data/tech_required_tech.csv diff --git a/gaming/xcom/data/xcom-db.csv b/games/xcom/data/xcom-db.csv similarity index 100% rename from gaming/xcom/data/xcom-db.csv rename to games/xcom/data/xcom-db.csv diff --git a/gaming/xcom/data/xcom-db.ods b/games/xcom/data/xcom-db.ods similarity index 100% rename from gaming/xcom/data/xcom-db.ods rename to games/xcom/data/xcom-db.ods diff --git a/gaming/xcom/images/tech-tree-mod.jpg b/games/xcom/images/tech-tree-mod.jpg similarity index 100% rename from gaming/xcom/images/tech-tree-mod.jpg rename to games/xcom/images/tech-tree-mod.jpg diff --git a/gaming/xcom/images/xcom2-research-screen.jpg b/games/xcom/images/xcom2-research-screen.jpg similarity index 100% rename from gaming/xcom/images/xcom2-research-screen.jpg rename to games/xcom/images/xcom2-research-screen.jpg diff --git a/gaming/xcom/schema.tql b/games/xcom/schema.tql similarity index 100% rename from gaming/xcom/schema.tql rename to games/xcom/schema.tql